Build and Deploy a Website with Contentstack and Launch
Building a production website with a headless CMS requires a clear, repeatable workflow. This guide walks you through every step, from creating your first stack to deploying a live site on Launch, without relying on AI-assisted tools. Every command, API call, and configuration setting is shown explicitly so you can follow along, reproduce results, and understand how each layer connects to the next.
Additional Resource If you prefer AI-assisted development, refer to our Build a Website with Contentstack and AI guide.
Use this guide when you need:
- A deterministic, step-by-step workflow with no ambiguity
- A setup suitable for QA reproduction, security reviews, or regulated environments
- A clear understanding of how each layer of the stack works before adding automation
What You Will Accomplish
By the end of this guide, you will be able to:
- Create a stack and configure environments
- Model structured content using Content Types and fields
- Create and publish entries to an environment
- Fetch published content using the Content Delivery API
- Integrate that content into a Next.js application
- Deploy the site using Contentstack Launch
Architecture Overview
This workflow consists of four layers. Each layer must work correctly and independently before you move to the next.
Contentstack (content and publish)
↓
Content Delivery API (CDN)
↓
Application (fetch and render)
↓
Launch (build and host)Treat this architecture as a series of gates: validate each layer before building on top of it. A working curl call confirms the API layer before you write a single line of application code. A successful local build confirms the application layer before you configure Launch.
Prerequisites
Before you begin, ensure you have:
- A Contentstack account with permission to create a stack
- Permission to create environments and delivery tokens
- A Git repository connected to your project
- Node.js (LTS recommended) installed on your machine
- Basic familiarity with environment variables and terminal commands
Step 1: Choose a Content Fetching Strategy
The first decision is how your application will retrieve content from Contentstack. The three options differ in where the fetch runs, whether tokens are exposed to end users, and how fresh the content will be.
| Mode | When to Use | Fetch Location | Token Exposure |
|---|---|---|---|
| Client-side | Public content, quick demos | Browser | Exposed |
| Server runtime (SSR / API route) | Fresh content per request | Server | Hidden |
| Build-time (static export) | Static hosting, high performance | CI / build step | Hidden |
Note This guide uses build-time fetching with static export. This approach keeps tokens off the client, works well with Launch's static hosting, and maximizes performance. Do not mix modes accidentally, prefixing a secret with NEXT_PUBLIC_ will expose it in the browser bundle.
Step 2: Set Up Contentstack
Complete all of the following steps in the Contentstack dashboard before writing any application code.
2.1 Create a Stack
A Stack is your project's container. All content types, entries, assets, environments, and tokens live inside a single stack.
To create a stack, log in to your Contentstack account and perform the following steps:
- Navigate to the organization where you want to create the stack and click Headless CMS.
- Click + New Stack and select Create New.
- In the Create New Stack modal, enter the following details:
- Name (required): Enter a name for your stack, for example, My Company Website.
- Description (optional): Provide a short description.
- Click Create.
- Go to Settings → Stack and record your Stack API Key. You will need it in later steps.
Note Your region determines which CDN hostname you use for all Content Delivery API calls. You cannot use a hostname from a different region and expect consistent results. See Step 4 for the correct host per region.
2.2 Create Environments
Environments are publishing targets, typically staging and production, that control where published content is available. Your Delivery Token is scoped to a specific environment, so the environment name you use when publishing an entry must match the environment your token covers.
To create environments, perform the following steps:
- Click the Settings icon, or press S.
- Navigate to Environments.
- Click + New Environment.
- Enter a Name, for example, staging.
- Click Create.
- Repeat for any additional environments, for example, production.
Note Environment names are case-sensitive. Copy them exactly as created, staging and Staging are treated as different environments.
2.3 Design Your Content Model
Plan your content model before creating any fields in Contentstack. Fields should reflect how content appears in your UI, designing them the other way around leads to empty API responses and significant rework later.
Best practice Open your UI design (Figma, wireframe, or mockup) and, for each page, list the sections and SEO metadata you need before touching the dashboard.
A typical marketing site content model looks like this:
| Content Type UID | Purpose |
|---|---|
| homepage | Single homepage entry |
| page | Generic pages, fetched by slug |
| blog_post | Article entries (optional for this guide) |
Reusable sections such as a hero, feature grid, testimonials, and call-to-action are best implemented as Modular Blocks or Group fields, depending on your editorial workflow.
2.4 Create the Homepage Content Type
To create the homepage Content Type, perform the following steps:
- Click the Content Models icon in the navigation panel.
- Click + New Content Type and select Create New.
- Enter the following details:
- Name: Homepage
- UID: homepage — the UID is permanent. Choose it carefully, because renaming it after integration breaks all API responses and frontend mappings.
- Type: Select Single for a one-off page entry.
- Click Save and proceed.
- In the Content Type Builder, add the following fields:
Title
- Type: Single Line Text
- UID: title
- Required: Yes
SEO (Group field)
- Type: Group
- UID: seo
- Fields inside the group: meta_title (Single Line Text), meta_description (Multi Line Text), og_title (Single Line Text), og_description (Multi Line Text), og_image (File / Asset)
Sections (Modular Blocks)
- Type: Modular Blocks
- UID: sections
- Example blocks:
- Hero — fields: headline, description, primary_cta_text, primary_cta_url
- Features — repeatable Group field features with title, description, icon
- Click Save when done.
Note Save your Content Type UIDs and field UIDs in a reference file as you go. You will need the exact UIDs, not the display labels, when making API calls and writing application code.
Warning Do not rename UIDs after you begin integrating with your application. UID changes break API responses and all frontend field mappings immediately.
How Modular Blocks appear in the API response: Each block includes a _content_type_uid discriminator field that identifies the block type. Your application code should switch on block._content_type_uid to render the correct component for each block.
How Asset fields appear in the API response: Image and file fields return an asset object with a url property, not a plain string. Always handle the case where the asset is missing so a build does not crash on an empty field.
2.5 Create and Publish an Entry
Creating an entry and saving it is not enough, only published entries are returned by the Content Delivery API. An entry must be explicitly published to the environment that your Delivery Token is scoped to.
To create and publish an entry, perform the following steps:
- Click the Entries icon in the navigation panel, or press E.
- Click + New Entry and select the Homepage content type.
- Fill in all required fields.
- Click Save, then click Publish.
- In the Publish modal, select your target environment, for example, staging, and the appropriate locale.
- Click Send. Confirm the entry status shows Published.
Additional Resource Refer to the Create an Entry documentation for full details.
Step 3: Create a Delivery Token
A Delivery Token is a read-only, environment-scoped token that your application uses to authenticate Content Delivery API requests. You must use a Delivery Token for the CDA — Management Tokens and Auth Tokens are for the Content Management API and will not work here.
To create a Delivery Token, perform the following steps:
- Click the Settings icon in the navigation panel, or press S.
- Click Tokens in the settings panel.
- Click + Delivery Token.
- Enter a Name and optional Description.
- In the Scope section, select the publishing environment this token will cover, for example, staging. Select the branch.
- Click Generate Token.
- Copy the token value immediately using the Click to copy icon.
Warning Never commit your Stack API Key or Delivery Token to a public repository. Always store credentials in environment variables. Do not pass api_key or delivery_token as URL query parameters — pass them only as HTTP request headers.
Before moving to the next step, confirm you have all of the following values:
| Value | Where to Find It |
|---|---|
| Stack API Key | Settings → Stack |
| Delivery Token | Settings → Tokens → Delivery Tokens |
| Environment name | Settings → Environments (copy exactly) |
| Content Type UID | Content Models → open the content type |
| Entry UID | Content → open the entry → entry details sidebar |
Step 4: Confirm Your CDN Endpoint
The Content Delivery API uses a different hostname from the management API. Use the CDN host that matches your stack's region. Do not use api.contentstack.io for content delivery — that endpoint is for the Content Management API.
| Region | CDN Base URL |
|---|---|
| AWS North America | https://cdn.contentstack.io |
| AWS Europe | https://eu-cdn.contentstack.com |
| AWS Australia | https://au-cdn.contentstack.com |
| Azure North America | https://azure-na-cdn.contentstack.com |
| Azure Europe | https://azure-eu-cdn.contentstack.com |
| GCP North America | https://gcp-na-cdn.contentstack.com |
| GCP Europe | https://gcp-eu-cdn.contentstack.com |
Refer to the API Endpoints by Region documentation for the full list.
When storing the host as an environment variable, include only the hostname without the https:// prefix. Your application code should prepend it when constructing the request URL:
CONTENTSTACK_CDN_HOST=cdn.contentstack.io
Step 5: Validate API Access with curl
Test your credentials and confirm the API returns content before writing any application code. Debugging a failed API call is much simpler at this stage than inside a build pipeline.
Replace the placeholder values and run the following command:
CDN_HOST="cdn.contentstack.io"
API_KEY="<STACK_API_KEY>"
DELIVERY_TOKEN="<DELIVERY_TOKEN>"
ENVIRONMENT="staging"
CONTENT_TYPE_UID="homepage"
ENTRY_UID="<ENTRY_UID>"
curl -sS -o /tmp/cs-entry.json -w "HTTP %{http_code}\n" \
"https://${CDN_HOST}/v3/content_types/${CONTENT_TYPE_UID}/entries/${ENTRY_UID}?environment=${ENVIRONMENT}" \
-H "api_key: ${API_KEY}" \
-H "access_token: ${DELIVERY_TOKEN}"Expected result: The terminal prints only HTTP 200. The response body is written to the file. View it with: cat /tmp/cs-entry.json.
If your stack is configured for multiple locales, append &locale=en-us (or your target locale code) to the URL.
Note Do not proceed to the application code steps until this command returns HTTP 200. A failed curl call here will surface as a cryptic build error later. See the Troubleshooting section for common error codes and their causes.
Step 6: Integrate with Your Application
This guide uses Next.js with the App Router and static export. The same pattern applies to any framework, the only requirements are that you issue an HTTP GET request to the Delivery API, pass the correct headers, and parse the JSON response.
6.1 Create the Next.js App
If you do not already have a Next.js project, run the following commands in your terminal:
npx create-next-app@latest my-site --typescript --eslint --app --src-dir=false --import-alias="@/*" cd my-site
6.2 Enable Static Export
Open next.config.ts (or next.config.js) and add the output setting:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
};
export default nextConfig;If you use next.config.js instead, use module.exports = nextConfig;.
Run npm run build locally and confirm that an out/ directory is generated before configuring Launch.
6.3 Configure Environment Variables
Create a .env.local file in the root of your project and add your Contentstack credentials. Add .env.local to your .gitignore file if it is not already excluded.
CONTENTSTACK_API_KEY="<STACK_API_KEY>" CONTENTSTACK_DELIVERY_TOKEN="<DELIVERY_TOKEN>" CONTENTSTACK_ENVIRONMENT="staging" CONTENTSTACK_CDN_HOST="cdn.contentstack.io" CONTENTSTACK_CONTENT_TYPE_UID="homepage" CONTENTSTACK_ENTRY_UID="<ENTRY_UID>" # Optional — add only if your stack requires an explicit locale: # CONTENTSTACK_LOCALE="en-us"
Warning Never prefix secret variables with NEXT_PUBLIC_. Variables with that prefix are embedded in the browser bundle and are visible to anyone who inspects your site's JavaScript.
6.4 Create a Fetch Helper
Create a lib/contentstack.ts file to centralize all Contentstack API calls. This helper runs only at build time and never in the browser.
type EntryResponse<T> = { entry: T };
function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing env var: ${name}`);
return value;
}
export async function fetchHomepage<T>(): Promise<T> {
const host = requiredEnv("CONTENTSTACK_CDN_HOST");
const apiKey = requiredEnv("CONTENTSTACK_API_KEY");
const token = requiredEnv("CONTENTSTACK_DELIVERY_TOKEN");
const environment = requiredEnv("CONTENTSTACK_ENVIRONMENT");
const contentType = requiredEnv("CONTENTSTACK_CONTENT_TYPE_UID");
const entryUid = requiredEnv("CONTENTSTACK_ENTRY_UID");
const locale = process.env.CONTENTSTACK_LOCALE;
const params = new URLSearchParams({ environment });
if (locale) params.set("locale", locale);
const url = `https://${host}/v3/content_types/${contentType}/entries/${entryUid}?${params}`;
const res = await fetch(url, {
headers: { api_key: apiKey, access_token: token },
cache: "force-cache",
});
if (!res.ok) {
const body = await res.text().catch(() => "");
throw new Error(`Contentstack fetch failed (${res.status}): ${body}`);
}
const json = (await res.json()) as EntryResponse<T>;
return json.entry;
}6.5 Create the Homepage Page Component
Open app/page.tsx and add the following:
import { fetchHomepage } from "@/lib/contentstack";
export default async function HomePage() {
const entry = await fetchHomepage<{ title?: string }>();
return (
<main>
<h1>{entry?.title ?? "Homepage"}</h1>
</main>
);
}If you are rendering Modular Blocks from the sections field, map over the array and switch on block._content_type_uid to render the appropriate component for each block type.
6.6 Understand Rebuild Behavior
With build-time fetching, publishing new or updated content in Contentstack does not automatically update the live site. A new Launch build is required to pull in the latest published content. You can automate this later by configuring a Contentstack webhook to trigger a Launch rebuild whenever an entry is published.
Step 7: Deploy with Launch
Contentstack Launch is a frontend hosting and deployment platform built for Contentstack-powered websites. It connects directly to your Git repository and builds your site on every push.
Note Only the Organization Admin or Owner can create new projects in Launch. Learn more about Administration Roles.
7.1 Create a Launch Project
Tip If Launch offers a Connect and Import Variables option when creating your project, use it. It pulls your Contentstack credentials directly from the linked stack and eliminates manual entry.
To deploy your site, perform the following steps:
- From the Contentstack CMS homepage, click Launch.
- On the Launch Projects screen, click + New Project. A dropdown appears with GitHub, BitBucket, and File Upload options.
- Select GitHub and sign in with your GitHub credentials.
- In the Repository Access section, select All repositories or choose specific ones, then click Save. If this is your first time connecting, click Install & Authorize.
- Once the repository is connected, fill in the following project details:
- Repository (required): Select your Git repository.
- Git Branch (required): Select the branch to deploy from, for example, main.
- Project Name (required): This is auto-populated from your repository name.
- Environment Name (required): Enter a name, for example, production.
- Framework Preset: Select Other. Launch may auto-detect the preset as NextJs; leave it on NextJs and the deployment fails with an Invalid output directory: out error, so change it to Other.
- Build Command: npm run build
- Output Directory: out. Launch may auto-populate this as ./.next for the detected framework; change it to out.
Note This is Launch's deployment environment label and does not need to match your Contentstack environment. Which content is fetched is controlled by the CONTENTSTACK_ENVIRONMENT variable and it must match the environment your Delivery Token covers.
- Click + Add Environment Variable and add every CONTENTSTACK_* variable from your .env.local file. These must be present for both the build step and runtime, or the build will fail.
- Turn off the Enable Contentstack Authentication toggle if you want the deployed site to be publicly accessible. It is enabled by default; leaving it on shows a Contentstack login wall to every visitor.
- Click Deploy.
The screen shows a Deploying status while the build runs. When the deployment completes, your site is live at a unique URL shown in the Domains section.
Tip Auto-deploy is enabled by default. Any push to the connected Git branch triggers a new deployment automatically.
7.2 Verify the Deployment
After deployment completes, open the Launch URL and confirm that content from Contentstack is rendering correctly. If the page appears blank or returns a 404, check the following:
- The output directory is set to out, not dist or .next
- The output: "export" setting is present in next.config.ts
- All CONTENTSTACK_* environment variables are set in Launch — check the build logs for any Missing env var errors thrown by the fetch helper
- If the page shows a Contentstack login screen instead of your content, the Enable Contentstack Authentication toggle is on. Turn it off in the project settings for a public site.
Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
| 401 / 403 | Wrong token, incorrect environment scope, or credentials passed as query parameters | Recreate the Delivery Token scoped to the correct environment. Pass api_key and delivery_token as request headers, not query parameters. |
| 404 | Incorrect Content Type UID, Entry UID, or entry not published to the target environment | Re-copy UIDs from the Contentstack UI. Confirm the entry status shows Published for the environment your token covers. |
| 422 | Malformed query string or unpublished content | Simplify the query and confirm publish status. Compare your URL to the Content Delivery API documentation. |
| 200 but wrong or missing fields | Field UIDs in your code do not match the API response, or a locale is required but not specified | Inspect the raw JSON response. Align your code's field keys with the actual payload. Add the locale query parameter if your stack requires it. |
| 414 | URL query string exceeds the API's size limit (approximately 8 KB) | Shorten the query or split it into multiple requests. |
| Site not updating after publish | Build-time fetching is in use | Trigger a new Launch build manually. Configure a Contentstack webhook to automate rebuilds on publish. |
| Launch 404 or blank page | Wrong output directory, missing output: "export", or build-time fetch failed | Confirm the output directory is out. Check Launch build logs for thrown errors, a missing environment variable or a failed API call at build time are the most common causes. |
| Launch build stops at Creating an optimized production build… with no error | output: "export" ignored because next.config.ts uses module.exports | Use export default nextConfig; in .ts config. Confirm an out/ directory is generated by npm run build locally. |
| Deployment fails with Invalid output directory: out | Framework Preset is set to the auto-detected NextJs | Change the Framework Preset to Other and redeploy, keeping the Output Directory as out. |
| Local build succeeds but Launch build fails | Node version mismatch between local and Launch | Pin the Node version (e.g. add an engines.node field in package.json or set it in Launch) so both environments match. |
Using Other Frameworks
The workflow described in this guide applies to any frontend framework, not just Next.js. Once your curl call succeeds, the application integration follows the same four steps regardless of the framework you use:
- Send an HTTP GET request to the Content Delivery API
- Include api_key and delivery_token as request headers, never as query parameters
- Parse the JSON response
- Deploy the build output folder to Launch
Next Steps
Now that your site is live, you can extend it with the following:
- Dynamic routing: Add routes that fetch pages by slug, for example [slug]/page.tsx, to support multi-page sites without hardcoding Entry UIDs.
- Live Preview: Enable editors to preview draft content before publishing.
- Webhooks: Configure Contentstack to send a webhook to Launch whenever an entry is published, so the site rebuilds automatically with the latest content.
- Separate environments: Use distinct Delivery Tokens and environments for staging and production to keep unpublished content off your live site.
Glossary
| Term | Definition |
|---|---|
| Stack | Your Contentstack project container. All content types, entries, assets, environments, and tokens live inside a single stack. |
| Content Type | A template that defines the structure of a piece of content, similar to a database schema. |
| Entry | A single piece of content created from a Content Type. |
| Asset | An uploaded file, image, PDF, or video, stored on Contentstack's CDN and referenced in entries. |
| Environment | A publishing target such as development, staging, or production. Environment names are case-sensitive. |
| API Key | Identifies your stack in all API requests. Found in Settings → Stack. |
| Delivery Token | A read-only, environment-scoped token for fetching published content via the CDA. |
| CDA | Content Delivery API, the read-only API your frontend uses to fetch published content. |
| UID | The unique identifier for a content type, entry, or field. Auto-generated in snake_case and permanent after creation. |
| Launch | Contentstack's frontend hosting and deployment platform. |