Skip to main content
Skip to content

Last updated on 4 June 2026

Installation Guide

Prerequisites

  • Node.js 20+
  • pnpm 10+

The kit pins packageManager in package.json, so the recommended path is Corepack — ships with Node, no global install needed:

bash
corepack enable
# `pnpm` will be provisioned automatically on first invocation

The lockfile includes msw as a transitive test/mock dependency. It is explicitly listed in package.json's approved build dependencies, so a clean install should not require any pnpm approve-builds action from buyers.

If Corepack is unavailable in your environment, use the official standalone pnpm installer from pnpm.io/installation and select the pinned version from package.json.

Setup

bash
# 1. Copy env template (all vars optional, kit builds without them)
cp .env.example .env.local

# 2. Install dependencies
pnpm install

# 3. Fill in your Template Empire licence key
#    Open licence.json and replace the placeholder with your own key/domain.

# 4. Verify the static export builds clean
pnpm build

# 5. Start development server
pnpm dev

Open http://localhost:3000.

To preview the production build before deploying:

bash
pnpm preview   # serves /out at http://localhost:3000

Environment Variables

All variables are optional — the template renders and builds without them. Copy .env.example to .env.local and fill in any values you need.

bash
# .env.local

# Google Analytics 4 measurement ID
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX

If you set NEXT_PUBLIC_GA_ID, also enable and test the cookie-consent banner in src/lib/site-config.ts. Analytics scripts load only after stored analytics consent exists.

Commands

CommandDescription
pnpm devStart dev server with Turbopack
pnpm buildStatic export to /out
pnpm typecheckTypeScript type check
pnpm lintESLint

Deploying

Vercel

  1. Push to GitHub
  2. Import repo in vercel.com
  3. Set Output Directory to out
  4. Deploy

Netlify

  1. Push to GitHub
  2. Import repo in netlify.com
  3. Set Build command to pnpm build
  4. Set Publish directory to out
  5. Deploy

Static hosting (S3, GitHub Pages, etc.)

Run pnpm build and upload the /out directory to your host.

For GitHub Pages with a sub-path (e.g. username.github.io/repo), set basePath in next.config.ts:

ts
basePath: '/repo',

Security Headers

Empire UI kits are static exports (output: 'export') — they emit HTML/CSS/JS with no runtime server, so the app sends no HTTP response headers of its own. Set security headers at your host/CDN (per-host examples below).

Set headers in your host's config, not next.config.ts headers(). For a static export, next.config headers() is inert on a generic static host (S3, nginx, GitHub Pages) — it does anything only on Vercel, so relying on it is a false sense of security. Your host's native config (vercel.json, a _headers file, an nginx block — below) is explicit, portable, and what the examples here use.

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains
  • CSP script-src'unsafe-inline' is required by the inline theme-flash-prevention script (it runs before hydration, so it can't be externalised without a flash); 'unsafe-eval' by some animation-library code paths. A static export can't issue per-request CSP nonces. To harden, try removing 'unsafe-eval' and test (modern GSAP/Three.js often don't need it), and replace the inline theme script with a hashed/external one to drop 'unsafe-inline'.
  • Optional Google Analytics — if you wire up NEXT_PUBLIC_GA_ID, GA is blocked by the CSP above unless you add its origins: https://www.googletagmanager.com https://www.google-analytics.com to script-src, https://www.google-analytics.com to img-src, and https://www.google-analytics.com https://analytics.google.com https://region1.google-analytics.com to connect-src.
  • connect-src is 'self' only — add the specific origins your app actually calls (e.g. the GA hosts above). Avoid a blanket https:: it lets an XSS exfiltrate to any origin.
  • Clickjackingframe-ancestors 'self' blocks cross-origin framing and supersedes the legacy X-Frame-Options; unlike XFO it takes an allow-list (e.g. frame-ancestors 'self' https://your-other-site.com) if you embed your own site. X-XSS-Protection is intentionally omitted (deprecated; CSP replaces it).
  • HSTSincludeSubDomains enforces HTTPS on every subdomain for the full max-age; only keep it if all your subdomains are HTTPS.

The public Empire UI demos deliberately ship no frame protection so our preview tooling + storefront can embed them. Your production site isn't embedded — keep frame-ancestors 'self' (above).

Per host

Vercelvercel.json at the project root:

json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Content-Security-Policy", "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" },
        { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" }
      ]
    }
  ]
}

Netlify / Cloudflare Pages — put a _headers file in public/ (Next.js copies it into the out/ build output; don't edit out/ directly — it's regenerated each build):

/*
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()
  Strict-Transport-Security: max-age=31536000; includeSubDomains

nginx — inside your server { } block:

nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

After deploying, verify with securityheaders.com or DevTools → Network → Headers.