deploy-studio-site
When to use
Ship a Studio site to Contentstack Launch: Launch project and environments, production env vars, composition Deploy, Live Preview gating, region config, cache and 401 gotchas.
Use when deploying a Studio site, setting up Launch, configuring production env vars, wiring Live Preview only on the preview environment, or debugging "deployed but visitors see old content / blank page / a login screen / Live Preview chrome in production / 401 from Delivery API". Two layers ship independently: composition (Save+Deploy in canvas) and host (Launch environment variables + deployment).
Mandatory auth preflight: settle the credential before the first API call. Resolve it OAuth-first per
authenticate-cma:CS_OAUTH_ACCESS_TOKEN, else the Contentstack MCP's stored session: Read that skill for where it is, even with no MCP connected. Never ask the user for a sessionauthtoken. If nothing resolves, or a refresh fails with400 invalid_refresh_token, hand them! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth(it needs a TTY and a browser, so it cannot be run for them) and wait.403 error_code 316is a valid credential aimed at another org: fix the org or theapi_key, do not re-authenticate.
Deploy a Studio site
Context
A Studio site ships in two independent layers, and both must be correct for the visitor URL to render the latest composition cleanly.
- Composition Deploy: publishes the composition record through Contentstack's publishing workflow (from the canvas to the target environment). See
docs/15-build/05-templates-and-sections/55-save-preview-deploy.md. - App host: your React/Next app on Contentstack Launch that renders
<StudioComponent />and reads from the Delivery SDK (viasdk.fetchCompositionDataon the server when using SSR composition-by-URL). A Launch project holds environments; each Launch environment is one Git branch (or uploaded ZIP) with its own URL and variables, and is separate from the stack's environments.
Tools. Drive Launch through the Contentstack MCP's launch group (GROUPS includes launch, plus CONTENTSTACK_ORGANIZATION_UID and CONTENTSTACK_LAUNCH_PROJECT_ID; see install-contentstack-mcp). It uses the same OAuth session. The Launch CLI (csdx plugins:install @contentstack/cli-launch) accepts only csdx login, which the user must run.
The Delivery Token, Preview Token, and Environment are configured per Launch environment: production must NOT carry a Preview Token (otherwise visitors see Live Preview overlays), and the preview environment SHOULD carry one (otherwise authors lose the live edit-and-see-it loop).
Goal state: visitor hits the production URL, gets the latest deployed composition rendered cleanly (no Studio chrome, no Live Preview overlays), and authors see real-time updates only in preview contexts. Match the success screenshot in assets/screenshots/template-blog-post-full-stack.webp.
Task
A. Host the app
-
Confirm the build output type. CSR (Vite/CRA, which produces static output) or SSR (Next/Remix). Pick the matching Launch framework preset (
NextJs,CSR, …). Next.js on Launch needs App Router 13.4.6+ (Pages Router 12.2+). -
Create or reuse the Launch project from a Git repository (GitHub, Bitbucket Cloud) or a ZIP upload, with the preset, build command and, for SSR, server command. The MCP will not start the
launchgroup without a project ID (checked in 0.9.0), so create a new project in the Launch UI first (Git or ZIP upload), then set its uid asCONTENTSTACK_LAUNCH_PROJECT_IDand restart the MCP. -
Set production env vars on the Launch production environment (Environments, Settings, Env. Variables, or
update_an_environment). Same names as.env.localperdocs/10-set-up/05-install-studio/25-install-the-delivery-sdk.md. Pick the prefix matching the framework (NEXT_PUBLIC_,VITE_,REACT_APP_,PUBLIC_, or plainprocess.envfor Remix):<PREFIX>_CONTENTSTACK_API_KEY: stack API key (blt…)<PREFIX>_CONTENTSTACK_DELIVERY_TOKEN:cs…, scoped to the production environment + branch<PREFIX>_CONTENTSTACK_ENVIRONMENT:<prodEnvironment>(e.g.production)<PREFIX>_CONTENTSTACK_DEFAULT_LOCALE: e.g.en-us<PREFIX>_CONTENTSTACK_PREVIEW_TOKEN: omit on the production deploy, set only on preview deploys- SSR composition-by-URL ONLY (server-only, no
PUBLICprefix):STUDIO_COMPOSITION_CT_UID,CONTENTSTACK_DELIVERY_TOKEN,CONTENTSTACK_ENVIRONMENT,CONTENTSTACK_BRANCH. Seedocs/10-set-up/05-install-studio/55-render-on-the-server.md.
-
Two Launch environments:
- Production: auto-deploy on commits off,
environment=<prodEnvironment>, no preview token, Live Previewenable: false. Turn off Contentstack Authentication (on by default): it puts a login screen in front of every visitor. - Preview: auto-deploy on,
environment=<previewEnvironment>, preview token set, Live Previewenable: true. Point that stack environment's Base URL at this Launch URL so Studio's canvas and preview load the deployed app (configure-studio).
- Production: auto-deploy on commits off,
-
Gate Live Preview import and init on the preview token. First run
grep -rn 'from "@contentstack/live-preview-utils"' src app lib: every hit is a static import that shows the Edit button in production, howeverinitis gated. Replace it with this (perdocs/10-set-up/05-install-studio/30-install-live-preview.md):const previewToken = process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW_TOKEN;if (previewToken) {import("@contentstack/live-preview-utils").then(({ default: ContentstackLivePreview }) =>ContentstackLivePreview.init({ssr: false,enable: Boolean(previewToken),stackDetails: { apiKey, environment },editButton: { enable: Boolean(previewToken) },}));}Why:
live-preview-utils(4.5.3) draws its Edit button on import, even wheninitnever runs. Remove its re-export too. -
Non-US regions. Set the Live Preview
hostto the regional preview host (e.g.eu-rest-preview.contentstack.com) and the Delivery SDKregionto match. Regional CDN hosts:eu-cdn.contentstack.com,azure-na-cdn.contentstack.com,azure-eu-cdn.contentstack.com,gcp-na-cdn.contentstack.com,au-cdn.contentstack.com. Non-prod (the non-prod domain your team uses) has noregionshortcut: every host is<env>-<service>.<non-prod-domain>and must be set explicitly. The full table isinstall-studio§ Non-prod hosts. Establish which data center you're on first viaanalyze-project-fit§ 2b: Determine the data center. Seedocs/30-look-it-up/05-reference/50-error-message-index.md. -
Deploy: push to the production environment's branch, or redeploy it (
create_deployment; a ZIP project uploads first). Read the build log (get_deployment_logs) for the expected<PREFIX>_*injection. Launch applies variable changes only to new deployments, andNEXT_PUBLIC_*(and equivalents) are inlined at build time: redeploy after every variable change. -
Caching (Next.js). Launch does not support ISR revalidation,
revalidatePath/revalidateTagor the App Router Data Cache. Opt pages out withdynamic = 'force-dynamic'orrevalidate = 0, setCache-Controlheaders innext.config, and after publishing purge withrevalidate_cdn_cache(every redeploy also purges). Statically built pages need a deploy hook registered as a Contentstack webhook on publish.
B. Deploy the composition
-
Save in the canvas. Save persists the draft. Deploy publishes whatever was last saved. See
docs/15-build/05-templates-and-sections/55-save-preview-deploy.md. -
Preview in the canvas with Live Preview, switching the Preview Entry across a few real entries (for linked templates) to confirm bindings resolve. Repeater iteration bindings use
repeater.<field>. Verify each iteration renders the expected entry data. -
Click Deploy in the canvas toolbar (right cluster, next to Save). The Deploy modal opens: pick the target environments, decide "Send with References" vs "Send without References", confirm. The modal title says "Deploy Template" even for Sections. That's a label quirk, not a bug.
-
Publish connected entries to the same environment. For linked templates, the template can deploy fine but the page renders blank if the connected entry isn't published to
<prodEnvironment>. Publish each connected entry to the target environment. -
Wait for publishing workflow. If the stack has approvals or scheduling configured, the deploy is queued, not immediate. Check the Contentstack publish queue.
Inputs needed from the user
In this order. Stop and ask if any required value is missing.
framework: picks the Launch preset and the env-var prefixlaunchProject: an existing Launch project, or create one from a Git repo + branch or a ZIP uploadprodEnvironment: Contentstack environment name for the production Launch environmentpreviewEnvironment: Contentstack environment name for the preview Launch environment (optional, skip if not using one)region: defaults tousssrCompositionByUrl: yes/no, controls whether server-only env vars are required
If the user doesn't know an environment name, send them to app.contentstack.com, where they open the stack, go to Settings, and open Environments.
Acceptance
This skill succeeds only when ALL of the following are true. If any fails, do not claim success. Surface the failure and stop.
- The Launch production environment has
<PREFIX>_CONTENTSTACK_ENVIRONMENTset to<prodEnvironment>, no preview token, and Contentstack Authentication off - The Launch preview environment (if configured) has
<PREFIX>_CONTENTSTACK_PREVIEW_TOKENset andenvironment=<previewEnvironment> - The live deployment was built after the last variable change
-
live-preview-utilsis imported only when the preview token is present, andenable/editButton.enableare gated on it - If SSR composition-by-URL: server-only
STUDIO_COMPOSITION_CT_UID,CONTENTSTACK_DELIVERY_TOKEN,CONTENTSTACK_ENVIRONMENT,CONTENTSTACK_BRANCHare set - Region/host pair matches the stack's region (non-US only)
- Composition is Saved and Deployed to
<prodEnvironment>via the canvas Deploy modal - All connected entries for linked templates are published to
<prodEnvironment> - Visitor URL opened in an incognito session renders the composition with no Studio chrome, no Live Preview edit buttons
- In DevTools, the Network tab shows Delivery API calls hitting
cdn.contentstack.io(or the regional CDN), not the preview host - The preview environment (if configured) updates live on entry edit without reload
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| Deployed but visitors see old content | Launch CDN cache, or a statically built page | force-dynamic / revalidate = 0 with Cache-Control, purge with revalidate_cdn_cache; static pages need a deploy hook on publish. revalidatePath/revalidateTag do nothing on Launch |
| Visitors get a Contentstack login screen | Contentstack Authentication is on by default per Launch environment | Turn it off on the production environment |
| Page renders blank post-deploy | Connected entry not published to target env | Publish the entry to <prodEnvironment> |
| Visitors see draft content after someone previewed | A server with a preview token keeps the last request's preview hash on its shared stack | No preview token on production. On a preview deployment, clear the hash per request: configure-csr-vs-ssr |
| Visitors see Live Preview edit buttons / overlays | enable: true unconditional in prod build, or live-preview-utils imported statically (it draws the button on import) | Import it only when the preview token exists (step 5) |
| Preview environment doesn't update live | onEntryChange not wired to refetch, or preview token mismatch | See docs/10-set-up/05-install-studio/60-verify-your-setup-end-to-end.md. Verify token matches the one in Contentstack |
region / host mismatch error | Non-US stack with US defaults | Set Delivery SDK region and Live Preview host to the region |
| Deploy "succeeded" but composition didn't change | Pending approval / scheduled publish | Check Contentstack publish queue |
| Build fails, or a changed variable has no effect | Launch applies variables only to new deployments, and <PREFIX>_* are inlined at build time | Set vars on the Launch environment, then redeploy. No single quotes in values |
| Delivery token returns 401 in production | Token scoped to wrong environment or branch | Generate a token scoped to the prod environment + branch. Rotate in the Launch environment's variables, then redeploy |
| Search engines don't index the site | The default *.contentstackapps.com domain is served noindex | Add a custom domain to the production environment |
| SSR composition-by-URL 500s | Missing server-only STUDIO_COMPOSITION_CT_UID / CONTENTSTACK_BRANCH | Add server-only vars per docs/10-set-up/05-install-studio/55-render-on-the-server.md |
See also
docs/15-build/05-templates-and-sections/55-save-preview-deploy.md: Save vs Deploy semanticsdocs/10-set-up/05-install-studio/25-install-the-delivery-sdk.md: env var namesdocs/10-set-up/05-install-studio/30-install-live-preview.md: Live Preview initdocs/10-set-up/05-install-studio/55-render-on-the-server.md: SSR composition-by-URL env vars +sdk.fetchCompositionDatadocs/10-set-up/05-install-studio/60-verify-your-setup-end-to-end.md: end-to-end smoke testdocs/30-look-it-up/05-reference/50-error-message-index.md: regional host config