Ship it
Deployment
Featurevisor has no server. Deploying means publishing the datafiles to static hosting you control, from CI, every time a change merges. Applications fetch them from there.
The pipeline#
Run these steps on every push to your main branch, and stop at the first one that fails:
npx featurevisor lintnpx featurevisor testnpx featurevisor buildgit add .featurevisorgit commit -m "[skip ci] Revision $(cat .featurevisor/REVISION)"git push origin mainThen upload the datafiles directory to your hosting.
- Lint and test again after the merge, because two pull requests that passed on their own can conflict once both are in.
- The build writes the datafiles and updates the state files in
.featurevisor/. - Commit only
.featurevisor/. The scaffold's.gitignoreleavesdatafiles/out, since every deployment builds them again. [skip ci]stops the state commit from starting another deployment.- Push the state before uploading, so a rejected push stops the run before anything is published.
GitHub Actions has this pipeline as a ready workflow.
One deployment at a time#
Two builds running at once start from the same state and the same revision, and only one of them can push. Run deployments one at a time, through a single queue such as a GitHub Actions concurrency group. If a push is rejected, run the pipeline again from the latest main branch. Never force push the state.
Where to host#
Any host that serves static files works: a CDN, object storage behind one, Cloudflare Pages or GitHub Pages.
Serve the datafiles directory as the root, so each file keeps its path. datafiles/featurevisor-all.json is then served at https://cdn.example.com/featurevisor-all.json. In a project with environments and targets, datafiles/production/featurevisor-web.json is served at https://cdn.example.com/production/featurevisor-web.json. Upload every environment and target together, so applications never read datafiles from two different builds.
Each build overwrites the same file names. A cache in front of them delays every change by up to its lifetime, so choose one that matches how quickly a change has to reach users, a rollback included.
How applications get updates#
An application keeps evaluating the datafile it has in memory until it is given a newer one. Choose when it fetches one:
| Approach | A change arrives | Suits |
|---|---|---|
| Fetch on start | At the next start or page load | Short lived processes and page loads |
| Refresh on an interval | Within one interval | Long running servers and apps |
| Refresh when told | Soon after each deployment | Changes that must land at once |
| Bundle it | With the next application release | Apps that must work offline |
Fetching on start is the simplest. To refresh on an interval, fetch again every few minutes and replace the datafile, as the JavaScript SDK's interval refresh shows. To refresh when told, fetch when your application receives an event, such as a message over a WebSocket after each deployment. A bundled datafile works offline, and can be combined with fetching a newer one when the network allows.
Every datafile carries the revision it was built with, so an application can tell whether the file it fetched is newer. To load only the datafiles a screen needs, see Loading datafiles on demand.
How long a change takes#
A merged change reaches a user after three waits: the pipeline, the cache in front of the files and the application's next fetch. With a five minute cache and a five minute refresh interval, a change can take ten minutes after the pipeline finishes to reach every user.
Switching a feature off is only as fast as that sum. Agree the cache lifetime and refresh interval with the people who will need the fastest change, usually whoever is on call, before the day they need it.
Roll back#
Revert the change in Git and let the pipeline publish the result. Keep the state files as they are, so users stay in the variations they had. Configuration rollback covers the details.
Examples#
- GitHub Actions for the pipeline
- Cloudflare Pages and GitHub Pages for hosting
- The Cloudflare example repository, which deploys a complete project

