Next Campaign Page Kit is a tool for building campaign funnels that can be hosted with any of your favorite static site hosting providers such as Netlify or Cloudflare Pages.
Most static site generators are designed around a single site. When you need to manage multiple campaign funnels in one repository, you quickly run into problems: shared layouts bleed across campaigns, assets collide, and a change to one campaign can silently break another.
Next Campaign Page Kit solves this by treating each campaign as a fully isolated unit within a single repository. Every campaign lives in its own subdirectory with its own layouts, assets, and configuration — but they're all built, versioned, and deployed together.
The CLI tools (dev, clone, config, compress) and template filters (campaign_asset, campaign_link, campaign_include) enforce this isolation at every step, so developers can work on one campaign without fear of affecting another.
mkdir my-campaigns && cd my-campaignsnpm init -y
npm install next-campaign-page-kitnpx campaign-initThis will:
- Add all CLI scripts to your
package.json - Create
_data/campaigns.json— empty campaign registry to get you started
Each entry is keyed by the campaign slug, which must match the campaign's directory name under src/.
{
"my-campaign": {
"name": "My Campaign",
"description": "My first campaign",
"sdk_version": "0.3.10"
}
}src/
└── my-campaign/
├── _layouts/
│ └── base.html
├── assets/
│ └── config.js
└── presale.html
npm run configImportant
Get your Campaign API key from the Campaigns App in your store. See Campaigns App Guide.
npm run devThis will:
- Show a list of available campaigns
- Let you select which campaign to preview
- Start the dev server
- Open your browser to the selected campaign
By default the dev server starts on port 3000. You can configure the port with:
# Positional argument
npm run dev 8080
# Port Flag
npm run dev --port 8080
npm run dev -p 8080
# Environment variable
PORT=8080 npm run dev| Command | Description |
|---|---|
npm start |
Interactive menu: dev server, compress, clone, configure |
npm run dev |
Start dev server with interactive campaign picker |
npm run build |
Build all campaigns to _site/ |
npm run clone |
Clone an existing campaign to a new slug |
npm run config |
Set the API key for a campaign |
npm run compress |
Compress all images in a campaign directory |
npm run compress:preview |
Preview compression savings without modifying files |
npm run migrate |
Migrate campaigns.json from old array format to key-based format |
Output will be in the _site directory:
npm run buildClone an existing campaign to create a new one:
npm run cloneyour-project/
├── _data/
│ └── campaigns.json # Campaign registry (contains data for all campaigns)
├── src/
│ └── [campaign-slug]/ # Individual campaign directory
│ ├── _layouts/ # Campaign-specific layouts
│ │ └── base.html # Base layout template
│ ├── _includes/ # Reusable campaign components
│ ├── assets/ # Campaign assets (CSS, images, JS, config)
│ │ ├── css/ # Campaign styles
│ │ ├── images/ # Campaign images
│ │ ├── js/ # Campaign scripts
│ │ └── config.js # SDK configuration
│ ├── presale.html # Presale page (Base URL)
│ ├── checkout.html # Checkout page
│ ├── upsell.html # Upsell page
│ ├── receipt.html # Receipt page
│ └── *.html # Any other page
└── package.json
_data/campaigns.json- Register all campaigns and their configuration data here. Uses a key-based format where each key is the campaign slug (see below). If you have an older project using the array format, runnpm run migrateto convert it.src/[campaign]/_layouts/base.html- Campaign's base layoutsrc/[campaign]/assets/config.js- Campaign Cart SDK configuration
Each campaign page uses YAML frontmatter to configure the page for context.
| Field | Type | Required | Description |
|---|---|---|---|
page_layout |
string | No | Layout file in _layouts/. Defaults to base.html |
title |
string | Yes | Page title for <title> tag |
page_type |
string | Yes | Page type: product, checkout, upsell, receipt |
permalink |
string | No | Custom URL path (e.g., /starter/) |
next_success_url |
string | No | Redirect URL after successful checkout |
next_upsell_accept |
string | No | URL when upsell accepted |
next_upsell_decline |
string | No | URL when upsell declined |
styles |
array | No | Page-specific CSS files (relative paths or external URLs) |
scripts |
array | No | Page-specific JS files (relative paths or external URLs) |
footer |
boolean | No | Show footer on this page |
---
page_layout: base.html
title: Checkout
page_type: checkout
next_success_url: upsell.html
styles:
- https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css
- css/offer.css
scripts:
- https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
- js/offer.js
footer: true
---Each page automatically has access to its campaign's data from _data/campaigns.json via the campaign object. This allows you to provide configured context directly to your pages.
You can access any key defined in your campaign's entry in _data/campaigns.json:
<h1>{{ campaign.name }}</h1>
<p>Contact: {{ campaign.support_email }}</p>To add more context across all pages in your campaign, simply add new keys to your campaign in _data/campaigns.json:
{
"starter": {
"name": "Starter Campaign",
"support_email": "support@example.com",
"custom_headline": "Welcome to our Store!"
}
}Then the context is available to use it in your templates:
<h2>{{ campaign.custom_headline }}</h2>The environment variable is available in all templates and indicates the current build mode. This is useful for conditionally including analytics scripts, debug tools, or other environment-specific content.
| Command | Default Value |
|---|---|
npm run dev |
development |
npm run build |
production |
Usage:
{% unless environment == "development" %}
<!-- Google Tag Manager -->
<script>...</script>
{% endunless %}Override with CPK_ENV:
Set the CPK_ENV environment variable to override the default value. This is useful for build pipelines like Netlify or GitHub Pages where you may want a custom environment such as staging.
CPK_ENV=staging npm run buildLayouts are automatically resolved to the campaign's _layouts/ directory:
page_layout: base.html→starter/_layouts/base.htmlpage_layout: custom.html→starter/_layouts/custom.html
No layout specified? Defaults to base.html.
Templates use Liquid syntax. Next Campaign Page Kit provides additional custom filters and tags for campaign-relative includes, assets, and links.
Tip
Use campaign template filters to ensure your includes, assets, and links are automatically handled when cloning templates to a fresh new campaign.
Resolves asset paths to the current campaign.
Syntax:
{{ 'filename' | campaign_asset }}Examples:
<!-- Config -->
<script src="{{ 'config.js' | campaign_asset }}"></script>
<!-- Output: /starter/config.js -->
<!-- CSS -->
<link href="{{ 'css/custom.css' | campaign_asset }}" rel="stylesheet">
<!-- Output: /starter/css/custom.css -->
<!-- Images -->
<img src="{{ 'images/logo.png' | campaign_asset }}" alt="Logo">
<!-- Output: /starter/images/logo.png -->Use for: CSS files, JavaScript files, images, config.js, any campaign asset.
Generates clean URLs for inter-page navigation.
Syntax:
{{ 'filename.html' | campaign_link }}Examples:
<!-- Navigation link -->
<a href="{{ 'checkout.html' | campaign_link }}">Checkout</a>
<!-- Output: /starter/checkout/ -->
<!-- Campaign Cart meta tag -->
<meta name="next-success-url" content="{{ next_success_url | campaign_link }}">
<!-- Output: /starter/upsell/ -->
<!-- Data attribute -->
<button data-next-url="{{ 'upsell.html' | campaign_link }}">Continue</button>
<!-- Output: /starter/upsell/ -->Features:
- Removes
.htmlextension - Adds trailing slash
- Prepends campaign slug
- Handles anchor links (
#section) and absolute URLs
Use for: Page links, navigation URLs, redirect URLs, Campaign Cart SDK meta tags.
Includes a file relative to the current campaign's _includes directory. This is useful for including reusable components that are specific to a campaign.
Syntax:
{% campaign_include 'filename.html' arg=value %}Examples:
<!-- Include a slider component -->
{% campaign_include 'slider.html' images=slider_images %}
<!-- Include with parameters -->
{% campaign_include 'slider.html' images=slider_images show_package_image=true %}Use for: Reusable components within a campaign (e.g., sliders, testimonials).
To connect this campaign to your 29 Next Campaigns App:
- Run
npm run config - Select your campaign
- Enter your API key from the Campaigns App
- Deploy your campaign
For more details, see the Campaigns App documentation.
You can use our test cards to create test orders.
Compress all images in a campaign directory in-place. Supports JPEG, PNG, WebP, and GIF. Only overwrites a file if the compressed output is smaller than the original.
npm run compressThis will:
- Show a list of available campaigns
- Let you select which campaign to compress
- Compress all images found anywhere in the campaign directory (
src/[campaign]/) - Print a before/after table with file sizes and total savings
Preview mode — see what would be saved without modifying any files:
npm run compress:previewExample output:
◇ Found 3 images
◇ 2 images ready to compress
--------------------------------------------+----------+----------+----------+-------+---------
File | Before | After | Saved | % | Status
--------------------------------------------+----------+----------+----------+-------+---------
src/my-campaign/assets/images/hero.jpg | 145.3 KB | 88.2 KB | -57.1 KB | 39.3% | preview
src/my-campaign/assets/images/product.png | 80.0 KB | 54.0 KB | -26.0 KB | 32.5% | preview
--------------------------------------------+----------+----------+----------+-------+---------
TOTAL | 225.3 KB | 142.2 KB | -83.1 KB | 36.9% |
--------------------------------------------+----------+----------+----------+-------+---------
[NEXT] DEBUG 1 image already fully compressed, skipped
└ Preview complete — run without --preview to apply changes.
Already-optimized images are skipped and reported in the debug line above the summary.