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:
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
# 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:
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.
# .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
| Command | Description |
|---|---|
pnpm dev | Start dev server with Turbopack |
pnpm build | Static export to /out |
pnpm typecheck | TypeScript type check |
pnpm lint | ESLint |
Deploying
Vercel
- Push to GitHub
- Import repo in vercel.com
- Set Output Directory to
out - Deploy
Netlify
- Push to GitHub
- Import repo in netlify.com
- Set Build command to
pnpm build - Set Publish directory to
out - 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:
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.tsheaders(). For a static export,next.configheaders()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_headersfile, an nginx block — below) is explicit, portable, and what the examples here use.
Recommended set
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.comtoscript-src,https://www.google-analytics.comtoimg-src, andhttps://www.google-analytics.com https://analytics.google.com https://region1.google-analytics.comtoconnect-src. connect-srcis'self'only — add the specific origins your app actually calls (e.g. the GA hosts above). Avoid a blankethttps:: it lets an XSS exfiltrate to any origin.- Clickjacking —
frame-ancestors 'self'blocks cross-origin framing and supersedes the legacyX-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-Protectionis intentionally omitted (deprecated; CSP replaces it). - HSTS —
includeSubDomainsenforces HTTPS on every subdomain for the fullmax-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
Vercel — vercel.json at the project root:
{
"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:
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.