Skip to main content
Skip to content

Last updated on 4 June 2026

Installation Guide — UI20 Rift

Prerequisites

  • Node.js 20+ (LTS recommended)

  • 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` 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 env vars — it runs fully on mock data.

Create .env.local from .env.example only when you wire optional integrations:

env
# 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.

Build for Production

bash
pnpm build

This generates a static export in the out/ directory (configured via next.config.ts).

Phase Gate Commands

Run these before every release:

bash
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.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.


Template Empire — Halbon Labs