Ship the banner to production
In earlier tutorials you pushed public state to the development environment and wired the React SDK to read from it. Real apps serve users from production. This tutorial teaches you the SkyState environment model and walks you through the full promote flow: dry-run to review the diff, then apply to carry the banner key from staging to production.
Estimated time: 15 minutes.
What You'll Build
You will complete the deployment lifecycle for the appConfig.banner key introduced in Tutorial 1: Control an app banner from the CLI:
- Wire
SkyStateProvidersoenvironmentis driven by a build variable instead of a hardcoded string. - Push a staging banner value with the CLI.
- Use
sky state public promote --dry-runto preview the diff before any write. - Apply the promotion interactively and verify the production state.
- Learn where
--yesand--commentfit for automation and audit.
Prerequisites
Before you start, make sure you have:
- Completed Tutorial 1: Control an app banner from the CLI. You need the
tutorial-appproject created,SkyStateProviderwired, andappConfig.bannerbeing read withusePublicState. - The
skystateCLI installed and signed in (sky login). - A terminal and browser.
Step 1: Understand the environment model
SkyState has three canonical environments: development, staging, and production. Each environment stores its own version of public state for a project. They are isolated. Pushing to development never touches staging or production.
The CLI accepts short aliases too: dev, stg, and prod resolve to the full names. In every command, pass the environment with --env:
bash
sky state public push --env development ...
sky state public push --env staging ...
sky state public push --env production ...All three are equivalent to the full canonical name:
bash
sky state public push --env dev ... # same as --env development
sky state public push --env stg ... # same as --env staging
sky state public push --env prod ... # same as --env productionThe promote command copies state between environments:
bash
sky state public promote \
--project your-tutorial-app \
--from staging \
--to productionThe promote flow has three steps. First a dry-run shows what would change without writing anything. Then you review that diff. Then an interactive [y/N] prompt asks you to confirm. Type y to apply, anything else aborts.
When you run apply, the CLI re-fetches both environments from scratch and recomputes the diff at that moment. If production changed after you ran the dry-run but before you applied, the apply uses the latest production state, not the one you reviewed. Conflict detection only covers concurrent writes that race with the apply command itself.
Step 2: Wire a dynamic environment
Tutorial 1 used environment="development" hardcoded in SkyStateProvider. That teaches the wrong instinct for a real app: a production build should connect to the production environment, not development.
The SDK does NOT read environment from any env var automatically. It is always the value you pass explicitly, defaulting to 'development' when the prop is omitted. You are responsible for supplying the right string per build.
The cleanest pattern is a dedicated build variable. Open src/main.tsx and update the provider:
tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { SkyStateProvider } from '@skystate/react';
import { App } from './App';
import './index.css';
// You supply this. The SDK does not read it automatically.
// VITE_SKYSTATE_ENV is just one example name. Pick whatever fits your project.
const skyStateEnv = import.meta.env.VITE_SKYSTATE_ENV ?? 'development';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<SkyStateProvider
account="YOUR_ACCOUNT_ID"
project="your-tutorial-app"
environment={skyStateEnv}
callbackUrl={window.location.origin + '/'}
>
<App />
</SkyStateProvider>
</StrictMode>,
);Replace YOUR_ACCOUNT_ID and your-tutorial-app with your actual account ID and project slug from Tutorial 1. The only change from Tutorial 1's provider is the skyStateEnv variable and the environment prop. Keep your existing account, project, and callbackUrl values.
Create a .env file (or .env.development.local) in the tutorial-app directory for local work:
VITE_SKYSTATE_ENV=developmentFor a staging build you would pass the variable at build time:
bash
VITE_SKYSTATE_ENV=staging npm run build -- --mode stagingFor production:
bash
VITE_SKYSTATE_ENV=production npm run buildThe variable name VITE_SKYSTATE_ENV is not special to SkyState. It is just a convention you pick. What matters is that the string you pass to the environment prop is 'development', 'staging', or 'production'.
Vite's built-in
import.meta.env.MODEis'development'when running the dev server and'production'for anyvite buildinvocation, even staging builds. If you rely onMODEdirectly, both staging and production builds get'production'. A custom variable like the one above lets you distinguish them.
Auth note. Tutorial 2 registered http://localhost:5173/ only for --env development. Sign-in only works in that environment until you register callback URLs for each additional environment you deploy to:
bash
sky project auth callback-urls add \
--project your-tutorial-app \
--url https://your-staging-app.example.com/ \
--env staging
sky project auth callback-urls add \
--project your-tutorial-app \
--url https://your-production-app.example.com/ \
--env productionFor this tutorial the verification steps use public state only, so sign-in is not required. The callback URL registration applies when you deploy a real app with user state to staging or production.
Step 3: Push a staging banner
Use the CLI to set the appConfig key in the staging environment. This is the state you will promote to production in the next steps.
bash
sky state public push \
--json '{"appConfig":{"banner":{"visible":true,"message":"New dashboard is live. Try it now."},"support":{"label":"Email support","email":"support@example.com"},"limits":{"maxSavedViews":5}}}' \
--project your-tutorial-app \
--env staging \
--comment "Prepare banner for production launch"Confirm it was written:
bash
sky state public show --project your-tutorial-app --env stagingYou should see the full config object with the banner visible and the staging comment in the version history.
Step 4: Preview the promotion with --dry-run
Before writing anything to production, run the dry-run to see exactly what will change:
bash
sky state public promote \
--project your-tutorial-app \
--from staging \
--to production \
--keys appConfig \
--dry-run--keys appConfig scopes the promotion to the appConfig top-level key only. The diff output lists each operation that would be applied: in this case, the full appConfig field is being added to the production config because production has no state yet.
The dry-run exits without writing. Inspect the output carefully before proceeding.
Step 5: Apply the promotion
Re-run the same command without --dry-run. The CLI fetches both environments again, recomputes the diff, shows you the unified diff output, and prompts:
bash
sky state public promote \
--project your-tutorial-app \
--from staging \
--to production \
--keys appConfig \
--comment "Promote launch banner to production"The interactive prompt appears:
Apply all changes? [y/N]Type y and press Enter. The CLI applies the changes and on success prints the environment, number of changes, and the new version number.
If you run this again without changing staging first, the CLI reports no differences and exits cleanly. The environments are already in sync for the appConfig key.
Step 6: Verify the production state
Check that production now has the banner:
bash
sky state public show --project your-tutorial-app --env productionThe output should include the banner with visible: true and the message from staging.
To verify the SDK reads it, run a production build and preview it locally, or set VITE_SKYSTATE_ENV=production in .env.local (which the dev server does load) and restart the dev server. The app should display the production banner.
Verify
Dry-run shows a diff
Run the promote with --dry-run again after making a change to staging:
bash
sky state public push \
--json '{"appConfig":{"banner":{"visible":true,"message":"Updated banner text."},"support":{"label":"Email support","email":"support@example.com"},"limits":{"maxSavedViews":5}}}' \
--project your-tutorial-app \
--env staging \
--comment "Update banner text for next push"bash
sky state public promote \
--project your-tutorial-app \
--from staging \
--to production \
--keys appConfig \
--dry-runThe diff should show the banner message change. No production state is written.
--yes skips the prompt
In CI pipelines there is no terminal to type y. Pass --yes to apply without prompting:
bash
sky state public promote \
--project your-tutorial-app \
--from staging \
--to production \
--keys appConfig \
--yes \
--comment "CI: promote banner to production"This is the CI path. The --yes flag requires that you have already reviewed the diff in an earlier step of your pipeline.
Conflict detection
If two operators run promote at the same time against the same target, the second write fails with a conflict error. Re-run sky state public show --project your-tutorial-app --env production to check the current state, then promote again.
What You Learned
You wired SkyStateProvider to read environment from a build variable instead of a hardcoded literal, so development, staging, and production builds connect to their own isolated state stores. You pushed state to staging, used sky state public promote --dry-run to review the diff as a named step, applied the promotion interactively, and confirmed production state updated. You also saw how --yes handles the CI non-interactive path and how conflicts are surfaced.
The promote flow is always: dry-run (review) → apply (confirm) → verify. The --keys flag scopes a promotion to specific top-level keys, so you can ship one key at a time without overwriting unrelated production values.
Where to Go Next
- Read the
promoteCLI reference. - Read the Environments reference.
- Return to the Tutorials overview to see the full path.