Feature flags for SvelteKit
Flip features,
not deploys.
Canary resolves every flag on the server, renders the answer into the page, and re-evaluates the moment something changes. Everything on this page is live and running against your visitor ID.
This page's accent colour is an A/B test. You got variant-a. Roll a new
identity in the bar above to try your luck again.
- ON
new-dashboardFeature enabled - ON
gradual-rolloutFeature enabled - OFF
beta-checkoutUser not in required group - OFF
experimental-featureUser not in required group - ON variant-a
ab-test-homepageFeature enabled with variant
Flip the beta and internal switches in the bar and watch this change. No reload, one request.
01 Β· The basics
Wrap it. Ship it.
A flag is one component. The decision is made on the server and rendered into the HTML, so there is no loading spinner and no flash of the wrong thing.
β¨ New Dashboard Enabled
This card came down in the server-rendered HTML. View source and you'll find it.
<Feature flag="new-dashboard">
<NewDashboard />
{#snippet fallback()}
<ClassicDashboard />
{/snippet}
</Feature>02 Β· Percentage rollouts
Start at 1%. Sleep at night.
Your visitor ID is hashed with the flag's name into a bucket between 0 and 100. You are in as soon as the rollout passes your bucket, and you stay in as it grows.
You are in bucket 35.7, so at 50% you would be IN
π² You're in the rollout
The server put you in the half of visitors who get this feature.
{
"features": {
"gradual-rollout": {
"enabled": true,
"rollout": 50
}
}
}03 Β· User groups
Let the right people in early.
List userGroups on a flag and only those groups get it, even while it is switched
off for everyone else. Perfect for a beta programme, or for testing in production before launch.
π Beta Checkout Locked
Only the beta and internal groups get the new checkout.
π Experimental Feature
Disabled globally. The internal group can still get in.
{
"features": {
"beta-checkout": {
"enabled": true,
"userGroups": ["beta", "internal"]
},
"experimental-feature": {
"enabled": false,
"userGroups": ["internal"]
}
}
}import { joinGroup, getUserInfo, checkFeature } from 'sveltekit-canary';
// One round trip: the cookie is set and every
// feature on the page comes back re-evaluated
await joinGroup('beta').updates(getUserInfo, checkFeature);04 Β· A/B testing
Same visitor, same variant. Always.
Variants are assigned by hashing, not by rolling dice on each request, so nobody sees the page change under them. The split below is 3 variants across 3,000 simulated visitors.
The blue hero. Same words, cooler palette. Does calm convert better?
{#snippet control()}<ClassicHero />{/snippet}
{#snippet variantA()}<BlueHero />{/snippet}
{#snippet variantB()}<VioletHero />{/snippet}
<Feature
flag="ab-test-homepage"
variants={{
control,
'variant-a': variantA,
'variant-b': variantB
}}
/>05 Β· Route redirects
Send people to the right version.
One server hook redirects requests before a page ever renders. This is where each route would send you right now. Change your groups and the destinations change with them.
/dashboard/dashboard-v2/checkoutstays put/checkout-beta/checkout/admin/
Click a route to try it for real.
{
"routes": {
"/dashboard": {
"feature": "new-dashboard",
"enabled": "/dashboard-v2",
"disabled": "/dashboard-v1"
},
"/checkout": { "beta": "/checkout-beta" },
"/checkout-beta": {
"requiresGroup": "beta",
"disabled": "/checkout"
},
"/admin": { "requiresGroup": "internal" }
}
}Get started
Three steps. Then go flip something.
Install
terminal npm install sveltekit-canaryDescribe your flags
src/features.config.json { "features": { "gradual-rollout": { "enabled": true, "rollout": 50 } } }Add the hook
src/hooks.server.ts import { initCanary, handleFeatureRouting } from 'sveltekit-canary'; import { sequence } from '@sveltejs/kit/hooks'; import config from './features.config.json'; initCanary({ config }); export const handle = sequence(handleFeatureRouting);