Installation Guide — UI03 ShieldOps Cybersecurity
Prerequisites
- Node.js 20 or higher (nodejs.org)
- pnpm 10 or higher:
npm install -g pnpm
Setup
1. Extract and install
cd <extracted-folder>
cp .env.example .env.local
pnpm install
2. Licence key
Open licence.json and replace the placeholder key TE-XXXX-XXXX-XXXX with the key from your account at templateempire.io/account. Builds work without a real key but will print a yellow warning.
3. Run the development server
pnpm dev
Open http://localhost:3000 in your browser.
The dev server uses the default Next.js port (3000). Change it in
package.jsonif needed.
4. Build for production
pnpm build
The static output is written to the out/ directory.
Deployment
This template uses output: 'export' — fully static, no server-side runtime required.
Vercel
# Vercel CLI is typically installed globally via npm (standard for CLI tools)
npm install -g vercel
vercel --prod
Set output directory: out · Build command: pnpm build
Netlify
# netlify.toml
[build]
command = "pnpm build"
publish = "out"
Cloudflare Pages
Build command: pnpm build · Output directory: out
Network requirements
The kit uses Google Fonts (Geist, Geist Mono, Chakra Petch) loaded via next/font/google. The build process fetches font files from Google's CDN at build time. If you are building in a network-restricted or air-gapped environment, either:
- Replace the
next/font/googleimports insrc/app/layout.tsxwith locally hosted fonts usingnext/font/local, or - Ensure your build machine has outbound HTTPS access to
fonts.googleapis.comandfonts.gstatic.com.
Licence validation: When a real licence key is provided in licence.json, the prebuild script (scripts/validate-licence.mjs) makes an HTTPS request to templateempire.io to validate the key. In air-gapped or restricted-egress environments, either use the placeholder key (TE-XXXX-XXXX-XXXX) during local builds, or ensure outbound HTTPS access to templateempire.io is permitted before running pnpm build.
Common Issues
Port already in use
The dev server uses the default Next.js port (3000). Change it in package.json if needed:
"dev": "next dev --turbopack --port 3001"
tailwindcss not found
Run pnpm install from the template root.
Theme flash on load
The ThemeScript in providers.tsx prevents flash by reading localStorage before React hydrates. Ensure JavaScript is enabled.
GSAP SplitText
GSAP SplitText ships free with gsap 3.12+. If you see import errors, ensure you are on gsap ^3.15.0 as specified in package.json.
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.