Installation Guide — UI18 Locale
Prerequisites
-
Node.js 20+ (LTS recommended)
-
pnpm 10+ — the kit pins
packageManagerinpackage.json, so the recommended path is Corepack (ships with Node, no global install needed):bashcorepack enable # `pnpm` is provisioned automatically on first invocationIf you prefer a manual global install:
bashnpm install -g pnpm -
A code editor with TypeScript support (VS Code recommended)
Setup
# 1. Copy environment variables (kit builds without them — all optional)
cp .env.example .env.local
# 2. Install dependencies
pnpm install
# 3. Start development server
pnpm dev
Open http://localhost:3000 in your browser.
Environment Variables
The kit ships with no required env vars — it runs fully on mock data.
Create .env.local from .env.example only when you wire optional integrations:
# Optional: Google Analytics 4 measurement ID. Loaded by AnalyticsScripts
# only after the user grants analytics consent (or, when SITE_CONFIG.cookieConsent.enabled
# is false, immediately if set).
# NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
When you add new env vars, document them in .env.example first so a buyer
doing cp .env.example .env.local can see what to fill in.
Licence key
The kit ships with a placeholder licence at licence.json:
{ "key": "TE-XXXX-XXXX-XXXX" }
Replace it with the key you received from
templateempire.io/account. The predev
and prebuild hooks shell out to scripts/validate-licence.mjs which prints
a yellow "Licence key not set" warning until you do — the warning is
non-blocking, so pnpm dev and pnpm build continue to work without a
key. The validator has a 3-second timeout and falls through gracefully if
the validation endpoint is unreachable, so it never wedges your local loop.
Build for Production
pnpm build
This generates a static export in the out/ directory (configured via next.config.ts).
Phase Gate Commands
Run these before every release:
pnpm typecheck # TypeScript strict check
pnpm lint # ESLint
pnpm build # Production build
pnpm audit --prod --audit-level=high # Dependency audit (production deps only)
pnpm dev # Manual smoke test
The kit ships no test suite by default — wire your own (Vitest, Playwright, etc.) if your release process requires one.
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.
Troubleshooting
Port already in use
pnpm dlx kill-port 3000
pnpm dev
Tailwind styles not applying
Make sure you have postcss.config.mjs with the @tailwindcss/postcss plugin configured.
TypeScript errors after install
pnpm typecheck
If errors persist after a dependency bump, delete tsconfig.tsbuildinfo and re-run. The project uses strict: true in tsconfig.
Template Empire — Halbon Labs