Resources
8Install
npx skillscat add equilibriumpress/routevideo Install via the SkillsCat registry.
RouteVideo Remotion Skill
Use this file as the project knowledge base for ChatGPT, Claude, Copilot or another coding agent working on this repository.
Mission
Build and improve RouteVideo: a focused route animation app made with React, Vite, MapLibre GL JS, Turf.js and Remotion.
The product flow is simple:
- User enters an origin and destination.
- The app geocodes both locations.
- The app creates either a road route or a flight route.
- The user previews the route animation in a Remotion Player.
- The user exports an MP4 through a Remotion render server.
Do not turn this into a general video editor. Keep the MVP focused on fast route videos.
Source of truth
Use the official Remotion documentation as the first source:
- General docs: https://www.remotion.dev/docs/
- Map animations: https://www.remotion.dev/docs/maps
- Player: https://www.remotion.dev/docs/player/player
- Renderer: https://www.remotion.dev/docs/renderer
- API reference: https://www.remotion.dev/docs/api
When changing Remotion code, check the official docs before guessing.
Current stack
- React
- Vite
- TypeScript
- Remotion
- @remotion/player
- @remotion/renderer
- @remotion/bundler
- MapLibre GL JS
- Turf.js
- OSRM for car routes
- Nominatim for geocoding
- GitHub Pages for static preview hosting
- Local Node/Express server for MP4 rendering
Important architecture
The app has two layers.
1. Editor layer
Files:
src/App.tsxsrc/lib/routing.tssrc/defaultProject.tssrc/styles.css
The editor owns route input, geocoding, routing, settings and Player preview.
Use <Player> from @remotion/player for interactive preview.
Use a changing key when project settings change, because a crashed or stale Player can require remounting.
2. Remotion composition layer
Files:
src/remotion/index.tssrc/remotion/Root.tsxsrc/remotion/RouteVideo.tsxsrc/lib/routeMath.tssrc/lib/camera.tssrc/lib/mapStyles.ts
The composition must be deterministic from props. Do not read editor state directly. All render data must come through inputProps.
The shared project object contains:
transportModeoriginNamedestinationNameoriginCoordinatedestinationCoordinaterouteGeoJSONdurationSecondsspeedMultipliercameraModemapStylelabelsEnabledaspectRatio
speedMultiplier is kept for backwards compatibility. The UI no longer exposes speed. Video progress should run from 0% to 100% over the chosen duration.
Current camera modes
cinematic: default. Starts closer to the origin, moves wider through the middle, ends closer to the destination.overview: stable whole-route view.follow: follows the moving marker and rotates with route bearing.
Keep camera logic in src/lib/camera.ts.
Keep Turf route math in src/lib/routeMath.ts.
Do not put camera logic back into RouteVideo.tsx unless it is presentation-only.
Remotion map rules
Use the official Remotion map animation pattern.
For Remotion renders, MapLibre must be initialized with:
interactive: falseattributionControl: falsefadeDuration: 0canvasContextAttributes: { preserveDrawingBuffer: true }
The map container must have explicit width and height from useVideoConfig() and position: 'absolute'.
Use useDelayRender() and continueRender() so Remotion waits until the map and the current frame are ready.
Recommended pattern:
- create map once inside
useEffect() - add GeoJSON sources after
load - continue initial render after
idle - on each frame, update sources with
setData() - move camera with
jumpTo()orcalculateCameraOptionsFromTo() - call
triggerRepaint()after camera/source updates - continue frame render after
idle
Do not rely on MapLibre's own animation methods such as easeTo() for rendered video. Remotion must control animation per frame.
Route animation rules
Flight route
Use Turf great-circle for flight mode.
Rules:
- create a route with
turf.greatCircle() - use enough points, usually 100 to 200
- if Turf returns a MultiLineString, choose the longest segment
- slice the route with
turf.lineSliceAlong() - keep the slice non-empty at progress 0 by using a tiny minimum distance such as
0.001
Car route
Use OSRM for the road polyline.
Rules:
- request
overview=full - request
geometries=geojson - if routing fails, fall back to a straight line or show a clear error
- keep the RouteVideo component agnostic about where the route came from
Marker motion
Move the marker with Turf:
- compute total route length
- compute current distance = route length times progress
- use
turf.along()to get the current coordinate - compute bearing from two close progress points
- rotate the marker using the bearing
The current marker is an SVG overlay, not a MapLibre text symbol. Project it with map.project() after camera updates.
Line reveal
Use a full route layer as a faint base route.
Use a second progress route layer on top. Update the second source each frame.
Never animate line reveal with CSS width. The route is geographic data and must be sliced as GeoJSON.
Rendering rules
Use @remotion/renderer for server-side MP4 export.
Preferred API:
- bundle the Remotion entry with
@remotion/bundler - select the composition with
selectComposition() - render with
renderMedia()
Prefer renderMedia() over manual renderFrames() plus stitching unless a special workflow needs individual frames.
Use one render process at a time for the MVP, because MapLibre plus Chromium is resource-heavy.
GitHub Pages rules
GitHub Pages only hosts the static app.
It cannot run the MP4 render server.
On GitHub Pages, keep export disabled and explain that MP4 export needs the local or hosted render server.
Geocoding rules
The current geocoder is Nominatim.
Use it carefully:
- do explicit search after user action
- avoid autocomplete against the public endpoint
- use
limit=1or a small result count - keep cache and pending-request deduplication
- keep at least roughly one second between public Nominatim requests
- make geocoder endpoint configurable
- for production, prefer a proxy or paid geocoder
Do not hardcode a secret key into frontend code.
Routing rules
The current car routing service is OSRM.
Use:
route/v1/driving- coordinates as
lon,lat overview=fullgeometries=geojson
Always remember GeoJSON uses [longitude, latitude], not [latitude, longitude].
Styling rules
Keep the UI premium and focused.
Editor:
- large video preview
- compact controls
- no duplicate map previews unless they add clear value
- route input and settings stay readable
Video output:
- route should fill the viewport well
- labels must not obscure the route
- overlays should be useful and minimal
- route line needs contrast against the basemap
- vehicle marker should be readable but not toy-like
Common mistakes to avoid
- Do not use MapLibre animations like
flyTo()during render. - Do not use zero-length Turf slices.
- Do not use latitude/longitude order by mistake.
- Do not add random values inside a composition unless seeded and deterministic.
- Do not make network requests inside the Remotion composition during render if the data can be passed through props.
- Do not depend on GitHub Pages for MP4 rendering.
- Do not leave a second static map in the UI without a clear job.
- Do not create many settings before the core route animation feels strong.
Useful commands
Install:
npm install
Run editor:
npm run dev
Run render server:
npm run server
Open Remotion Studio:
npm run remotion:studio
Render sample:
npm run render:sample
Build static site:
npm run build
Format:
npm run format
Acceptance checklist for future changes
Before saying a change is done, check:
- TypeScript builds.
- Vite builds.
- Player preview still mounts.
- Route input still geocodes.
- Car mode still produces an OSRM route or graceful fallback.
- Plane mode still produces a curved route.
- GitHub Pages does not depend on server-only code.
- MP4 export remains server-side.
- Video composition reads only from props.
- Route still uses GeoJSON
[lon, lat]coordinates.