Skip to main content
Skip to content

Last updated on 4 June 2026

Installation Guide — UI07 Ember

Prerequisites

  • Node.js 22+ (LTS recommended)

  • pnpm 10+ — the kit pins packageManager (pnpm@10.33.4) in package.json, so the recommended path is Corepack (ships with Node, no global install needed):

    bash
    corepack enable
    # `pnpm` is provisioned automatically on first invocation
    

    If you prefer a manual global install:

    bash
    npm install -g pnpm
    
  • A code editor with TypeScript support (VS Code recommended)

Setup

bash
# 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 environment variables — it runs fully on mock data.

Create .env.local from .env.example only when you wire optional integrations. The shipped .env.example documents three variables, all optional:

env
# Optional: Google Analytics 4 measurement ID. Loaded by AnalyticsScripts
# only after the user grants analytics consent.
NEXT_PUBLIC_GA_ID=

# Optional: demo API key used in code snippets and dashboard examples only.
EMBER_API_KEY=

# Template Empire live-demo flag — DO NOT SET in your own deployment.
# Used only by the templateempire.io live preview to render a "Demo" banner
# and apply noindex/nofollow. Leaving it blank dead-code-eliminates the
# banner from the buyer bundle.
NEXT_PUBLIC_TE_DEMO=

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.

Build for Production

Network dependency at build time: The kit loads its Geist body font via next/font/google. During pnpm build, Next.js downloads it from Google Fonts (fonts.googleapis.com, fonts.gstatic.com). The Cabinet Grotesk display font is self-hosted (next/font/local, src/app/fonts/CabinetGrotesk-Variable.woff2) and needs no network access. In CI environments with restricted egress or air-gapped builds, the Geist download will fail. Options:

  • Allow outbound HTTPS to fonts.googleapis.com and fonts.gstatic.com
  • Switch Geist to a self-hosted file: replace its next/font/google import with next/font/local and add the font file to src/app/fonts/. See CUSTOMIZATION.md for guidance.
bash
pnpm build

This generates a static export in the out/ directory (output: 'export', trailingSlash: true, unoptimised images — configured in next.config.ts).

Preview Static Build

pnpm start runs next start, which expects a server build and will not serve the static out/ directory. To preview the production export locally, serve out/ with a static file server:

bash
pnpm build
pnpm dlx serve out
# opens a local static server for the generated out/ directory

Use this to verify the production export before deploying.

Phase Gate Commands

Run these before every release:

bash
pnpm typecheck       # TypeScript strict check
pnpm lint            # ESLint
pnpm build           # Production static export
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.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.

Troubleshooting

Port already in use

bash
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

bash
pnpm typecheck

If errors persist after a dependency bump, delete tsconfig.tsbuildinfo and re-run. The project uses strict: true in tsconfig.json.

Licence Key

The kit validates your licence key on pnpm dev and pnpm build via a predev / prebuild hook (scripts/validate-licence.mjs). To activate:

  1. Open licence.json in the project root.
  2. Replace the TE-XXXX-XXXX-XXXX placeholder with your key from store.templateempire.io/account (the same URL the validation script prints).
  3. Run pnpm dev — the validation script will confirm the key is set.

The check is informational during development; the build will still complete with a warning if the key is absent, letting you evaluate the kit before purchasing.


Template Empire