diff --git a/.changeset/add-plugin-content-schema-extension.md b/.changeset/add-plugin-content-schema-extension.md
new file mode 100644
index 00000000..36573fd7
--- /dev/null
+++ b/.changeset/add-plugin-content-schema-extension.md
@@ -0,0 +1,7 @@
+---
+"@stackwright/types": minor
+---
+
+Add `contentItemSchemas` and `knownContentTypeKeys` to `PrebuildPlugin` interface.
+Add `buildExtendedPageContentSchema()` function for merging OSS and plugin content schemas.
+Add `ValidatePageContentOptions` to `validatePageContent()` for plugin-aware validation.
diff --git a/.changeset/add-pro-content-normalization.md b/.changeset/add-pro-content-normalization.md
new file mode 100644
index 00000000..8e63d51e
--- /dev/null
+++ b/.changeset/add-pro-content-normalization.md
@@ -0,0 +1,6 @@
+---
+"@stackwright/build-scripts": minor
+---
+
+Add content format normalization (mapping-key YAML format → type-field format) to prebuild pipeline.
+Plugin `contentItemSchemas` and `knownContentTypeKeys` are now applied during page validation.
diff --git a/.changeset/dependabot-batch-updates.md b/.changeset/dependabot-batch-updates.md
new file mode 100644
index 00000000..d5b22d58
--- /dev/null
+++ b/.changeset/dependabot-batch-updates.md
@@ -0,0 +1,15 @@
+---
+"@stackwright/core": patch
+"@stackwright/icons": patch
+"@stackwright/maplibre": patch
+"@stackwright/nextjs": patch
+"@stackwright/ui-shadcn": patch
+---
+
+chore: consolidate dependabot dependency updates
+
+- `lucide-react`: `^0.525.0` → `^1.8.0` (icons, ui-shadcn) — includes icon rename fixes for v1 API (`CheckCircle` → `CircleCheck`, `Code2`/`Layout` backward-compat aliases)
+- `@swc/core`: `^1.15.18` → `^1.15.26` (core, nextjs)
+- `jsdom`: `^28.1.0` → `^29.0.2` (maplibre)
+- `react-dom`: `19.2.4` → `19.2.5` (pnpm.overrides)
+- `prettier`: `^3.8.1` → `^3.8.3` (devDependencies)
diff --git a/.changeset/feat-188-page-add-content-flag.md b/.changeset/feat-188-page-add-content-flag.md
new file mode 100644
index 00000000..eab52a5e
--- /dev/null
+++ b/.changeset/feat-188-page-add-content-flag.md
@@ -0,0 +1,7 @@
+---
+"@stackwright/cli": patch
+---
+
+feat(cli): add --content flag to `page add` for inline YAML (#188)
+
+Agents can now create a page with full content in a single command instead of a two-step add + write sequence. Content is validated before writing; invalid YAML is rejected with field-level errors.
diff --git a/.changeset/feat-243-security-headers.md b/.changeset/feat-243-security-headers.md
new file mode 100644
index 00000000..76a47d9a
--- /dev/null
+++ b/.changeset/feat-243-security-headers.md
@@ -0,0 +1,5 @@
+---
+"@stackwright/nextjs": minor
+---
+
+Add security headers (CSP, HSTS, COOP/CORP/COEP) to Next.js integration with customizable configuration
diff --git a/.changeset/fix-352-install-flag-actually-installs.md b/.changeset/fix-352-install-flag-actually-installs.md
new file mode 100644
index 00000000..2cf34eda
--- /dev/null
+++ b/.changeset/fix-352-install-flag-actually-installs.md
@@ -0,0 +1,5 @@
+---
+"@stackwright/cli": patch
+---
+
+fix(cli): --install flag now runs pnpm install before postInstall hooks
diff --git a/.changeset/fix-plugin-config-schema-242.md b/.changeset/fix-plugin-config-schema-242.md
new file mode 100644
index 00000000..88db2c67
--- /dev/null
+++ b/.changeset/fix-plugin-config-schema-242.md
@@ -0,0 +1,6 @@
+---
+"@stackwright/types": patch
+"@stackwright/build-scripts": patch
+---
+
+Add configSchema field to PrebuildPlugin for plugin config validation
diff --git a/.changeset/fix-preinstall-double-run.md b/.changeset/fix-preinstall-double-run.md
new file mode 100644
index 00000000..df264ead
--- /dev/null
+++ b/.changeset/fix-preinstall-double-run.md
@@ -0,0 +1,18 @@
+---
+"@stackwright/cli": patch
+---
+
+fix(cli): remove duplicate preInstall hook call from processTemplate
+
+`processTemplate()` was calling `runScaffoldHooks('preInstall', ...)` internally,
+then `scaffold.ts` called it again after `processTemplate` returned — running every
+preInstall handler twice. Worse, the second call passed the original empty `{}` object
+(not the built package.json), so hooks registered via `scaffold.ts` could never affect
+the written file.
+
+Fix: lifecycle orchestration now lives entirely in `scaffold.ts`. `buildPackageJson` is
+exported so `scaffold.ts` can build the default package.json before running preInstall
+hooks, then passes the already-hooks-modified object into `processTemplate` for writing.
+`processTemplate` no longer calls hooks.
+
+Fixes #351.
diff --git a/.changeset/grumpy-paws-create.md b/.changeset/grumpy-paws-create.md
new file mode 100644
index 00000000..3394347b
--- /dev/null
+++ b/.changeset/grumpy-paws-create.md
@@ -0,0 +1,5 @@
+---
+"@stackwright/core": patch
+---
+
+fix(core): prevent duplicate TopAppBar rendering that caused a double dark-mode toggle icon
diff --git a/.changeset/happy-books-accept.md b/.changeset/happy-books-accept.md
deleted file mode 100644
index 8f86a9d9..00000000
--- a/.changeset/happy-books-accept.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-"stackwright": patch
----
-
-fix(ci): update GitHub Actions to latest versions
-
-- actions/checkout@v5 (was v4)
-- actions/setup-node@v5 (was v4)
-- pnpm/action-setup@v4 (was v3)
-- Node 22 (was 20 in deploy-docs and prerelease)
-- Add deploy-docs.yml to its own path triggers
-
-Updated composite action and all workflows to use latest action versions.
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 38bb5c43..361d1a8c 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -18,21 +18,31 @@
"@stackwright/scaffold-core": "0.1.0-alpha.1",
"@stackwright/themes": "0.5.1-alpha.0",
"@stackwright/types": "1.1.0-alpha.6",
- "@stackwright/ui-shadcn": "0.1.0"
+ "@stackwright/ui-shadcn": "0.1.0",
+ "@stackwright/hooks-registry": "0.1.0-alpha.0"
},
"changesets": [
"add-code2-layout-icons",
+ "add-image-dimension-validation",
"bright-otters-glow",
"built-in-search-feature",
"compose-site-atomic",
"declarative-entry-pages",
+ "dependabot-batch-updates",
"docs-architecture-principles",
+ "feat-243-security-headers",
+ "feat-env-var-secrets-245",
+ "fix-352-install-flag-actually-installs",
"fix-cli-scaffold-smoke-test",
"fix-dark-mode-bugs",
"fix-dark-mode",
"fix-icons-architecture-codeblock",
+ "fix-issue-339-icon-theme-tokens",
"fix-maplibre-lockfile",
+ "fix-plugin-config-schema-242",
+ "fix-preinstall-double-run",
"fix-unpin-otter-models",
+ "grumpy-paws-create",
"integrations-config",
"launch-stackwright-package",
"map-adapter-phases-1-2",
diff --git a/.github/workflows/accessibility.yml b/.github/workflows/accessibility.yml
index 9731a4bd..e85b421a 100644
--- a/.github/workflows/accessibility.yml
+++ b/.github/workflows/accessibility.yml
@@ -55,7 +55,7 @@ jobs:
- name: Parse test results and comment on PR
if: github.event_name == 'pull_request' && always()
- uses: actions/github-script@v7
+ uses: actions/github-script@v9
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index e0ec3002..026dc378 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -47,7 +47,7 @@ jobs:
- name: Comment PR with coverage
if: github.event_name == 'pull_request'
- uses: actions/github-script@v7
+ uses: actions/github-script@v9
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index e5b55f0b..6b07d7e5 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -43,7 +43,7 @@ jobs:
fetch-depth: 0
- name: Install pnpm
- run: npm install -g pnpm@10.30.3
+ run: npm install -g pnpm@10.33.0
- name: Setup Node.js
uses: actions/setup-node@v5
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
index 3590338e..c7a571ea 100644
--- a/.github/workflows/performance.yml
+++ b/.github/workflows/performance.yml
@@ -2,7 +2,7 @@ name: Performance Benchmarks
on:
pull_request:
- branches: [main, develop]
+ branches: [main, dev]
workflow_dispatch:
inputs:
run-all:
@@ -27,6 +27,9 @@ jobs:
build: true
relink-bins: true
+ - name: Install Playwright browsers
+ run: pnpm --filter @stackwright/e2e exec playwright install --with-deps chromium
+
- name: ⚡ Run build time benchmarks
id: build-time
env:
@@ -91,7 +94,7 @@ jobs:
- name: 💬 Comment PR with results
if: github.event_name == 'pull_request'
- uses: actions/github-script@v7
+ uses: actions/github-script@v9
continue-on-error: true
with:
script: |
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index c1e582d6..be351fa1 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -27,7 +27,7 @@ jobs:
with:
token: ${{ steps.app-token.outputs.token }}
- - uses: pnpm/action-setup@v4
+ - uses: pnpm/action-setup@v6
with:
version: "10.30.3"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8d5234e8..ce0bb9f6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,7 +35,7 @@ jobs:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0
- - uses: pnpm/action-setup@v4
+ - uses: pnpm/action-setup@v6
with:
version: "10.30.3"
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index a1a35bba..4379cf6d 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -29,9 +29,9 @@ jobs:
fetch-depth: 0 # Full history for gitleaks
- name: Setup Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@v6
with:
- go-version: '1.21'
+ go-version: '1.24'
- name: Install Gitleaks
run: go install github.com/gitleaks/gitleaks/v9@latest
@@ -47,9 +47,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup pnpm
- uses: pnpm/action-setup@v2
- with:
- version: 8
+ uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -71,9 +69,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup pnpm
- uses: pnpm/action-setup@v2
- with:
- version: 8
+ uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -95,7 +91,7 @@ jobs:
- name: Upload Semgrep SARIF
if: always() && -f semgrep.sarif
- uses: github/codeql-action/upload-sarif@v3
+ uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: semgrep.sarif
category: semgrep
diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
index 489627d2..e2ca5a5a 100644
--- a/.github/workflows/visual-regression.yml
+++ b/.github/workflows/visual-regression.yml
@@ -63,7 +63,7 @@ jobs:
- name: Comment PR with results
if: always() && github.event_name == 'pull_request'
- uses: actions/github-script@v7
+ uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
diff --git a/CLAUDE.md b/CLAUDE.md
index edfdd03e..df12d0f4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -325,6 +325,17 @@ Core components (`packages/core/src/components/`) use **inline `style={{}}` prop
- For flex layouts that must stack on mobile, use `flexWrap: 'wrap'` with a `minWidth` on children to control the wrap breakpoint. Use `minWidth: 'min(Xpx, 100%)'` to prevent overflow on very narrow viewports.
- For text that may overflow on narrow viewports (emails, URLs, long strings), add `wordBreak: 'break-word'` or `wordBreak: 'break-all'` as appropriate.
+### Security Headers
+
+Stackwright projects should implement security headers for defense in depth. See [docs/CSP-BEST-PRACTICES.md](./docs/CSP-BEST-PRACTICES.md) for:
+- Complete `next.config.js` CSP configuration
+- Next.js App Router (middleware.ts) patterns
+- Google Fonts-specific directives
+- Permissions-Policy recommendations
+- Common gotchas and testing strategies
+
+Quick reference snippet: [docs/snippets/CSP-QUICK-REF.js](./docs/snippets/CSP-QUICK-REF.js)
+
### Image Co-location Pipeline
Images can be co-located with their page YAML files in `pages/`. Using a relative path starting with `./` in YAML (e.g., `src: ./hero-image.png`) triggers automatic processing during the prebuild step:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 43b5ce39..19963d13 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -146,7 +146,7 @@ Good commit points:
- After adding a new module or file that compiles/passes lint
- After wiring up a new feature end-to-end (even before tests)
- After adding or updating tests for the feature
-- After updating docs, ROADMAP.md, or changesets
+- After updating docs or changesets
- Before and after a refactor that touches many files
Commit messages should be concise and use conventional commit prefixes (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`). Include the issue number when relevant (e.g., `feat(build-scripts): add --watch mode (#122)`).
@@ -266,7 +266,7 @@ The AGENTS.md tables are auto-generated from the live Zod schemas. Do NOT edit t
## Priority Labels & Product Board
-Work is tracked via GitHub Issues with priority labels. `ROADMAP.md` is a narrative document describing architectural direction — not a task tracker.
+Work is tracked via GitHub Issues with priority labels. GitHub Issues are the single source of truth for planned work — run `pnpm stackwright -- board` to see the prioritized board.
| Label | Meaning |
|-------|--------|
@@ -286,7 +286,7 @@ pnpm stackwright -- board --json
Agents can call `stackwright_get_board` via MCP for the same data.
-The architect sets priority tiers. Contributors and agents should pick work from `priority:now` first, then `priority:next`. When a PR closes an issue, GitHub handles it automatically — no manual ROADMAP.md updates needed.
+The architect sets priority tiers. Contributors and agents should pick work from `priority:now` first, then `priority:next`. When a PR closes an issue, GitHub handles it automatically.
## Package Structure
diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md
index 5e5b8e3f..2c9a9b00 100644
--- a/PHILOSOPHY.md
+++ b/PHILOSOPHY.md
@@ -4,7 +4,7 @@ This document captures the product intent and architectural principles behind St
Stackwright's one-sentence thesis: **Visual rendering + constrained DSL + AI iteration = non-technical people building enterprise apps that are safe by construction.**
-[CONTRIBUTING.md](./CONTRIBUTING.md) tells you how to work in this repo. [ROADMAP.md](./ROADMAP.md) tells you what to build next. This document tells you what Stackwright is and why it is built the way it is.
+[CONTRIBUTING.md](./CONTRIBUTING.md) tells you how to work in this repo. For the live list of what's being worked on, run `pnpm stackwright -- board` or see the [GitHub Issues](https://github.com/Per-Aspera-LLC/stackwright/issues). This document tells you what Stackwright is and why it is built the way it is.
---
@@ -269,4 +269,3 @@ For contributors and agents making implementation decisions:
5. **Agent-facing docs are part of the build.** The content type reference tables in AGENTS.md must be kept in sync with the TypeScript types. This is as important as keeping the JSON schemas in sync. Stale agent docs produce exactly the same class of bugs as stale type definitions.
6. **Constrain first, extend later — in the free tier.** When in doubt about whether to add a new content type or field to `@stackwright/core`, wait. The cost of adding something is low; the cost of maintaining it, keeping it in the schema reference, making it agent-writable, and eventually removing it is high. The right answer to "I need something the core schema doesn't support" is either a developer-written React component or a pro component package — not a core schema extension. This principle does not apply to pro packages, which exist specifically to serve specialized use cases.
-t support" is either a developer-written React component or a pro component package — not a core schema extension. This principle does not apply to pro packages, which exist specifically to serve specialized use cases.
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index 43c03c27..00000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Stackwright Roadmap
-
-This document describes the *direction* of the project — the architectural rationale, the product trajectory, and the vision that shapes prioritization. It is narrative, not a checklist.
-
-**For the live, prioritized list of what's being worked on:**
-```bash
-# Terminal (humans)
-pnpm stackwright -- board
-
-# MCP tool (agents)
-stackwright_get_board
-```
-
-These query open GitHub Issues sorted by [priority labels](#priority-labels). Issues are the single source of truth for planned work.
-
----
-
-## Grammar Hardening
-
-The type system that defines what YAML can express — the Stackwright grammar — is the product's core moat. The grammar must be rigorous, introspectable, and extensible.
-
-**Completed foundation:** Zod schemas are the single source of truth for the grammar. TypeScript types are inferred via `z.infer<>`. JSON schemas are generated for IDE YAML validation. Runtime validation runs in the prebuild pipeline. Content types are extensible via `registerContentType()`. MCP tools introspect the schema at runtime.
-
-**Next architectural step: explicit `type` field (#131).** The content renderer currently uses `Object.entries(item)[0]` to discriminate content types — a pattern that relies on JS object insertion order, prevents TypeScript discriminated unions, and produces poor error messages. Migrating to an explicit `type` field on every content item is a breaking change that enables proper discriminated union narrowing and clearer validation errors. This is sequenced after the grammar foundation is solid because it touches every YAML file, test, and content type.
-
----
-
-## Visual Rendering & AI Design Loop
-
-**Shipped.** The visual rendering infrastructure is now in place — AI agents and the CLI can screenshot pages, preview raw YAML, and capture before/after diffs.
-
-**What's live:**
-- MCP tools: `stackwright_render_page`, `stackwright_render_diff`, `stackwright_render_yaml`, `stackwright_check_dev_server`
-- CLI: `stackwright preview` command
-- E2E tests: full render pipeline verified against the example app
-- **Brand Otter** — part of the Otter Raft, discovers brand through conversation and produces BRAND_BRIEF.md (see `./packages/otters/README.md`)
-
-**Next step: branding expert iteration loop.** With Brand Otter shipped, the next evolution is enabling AI agents to visually iterate on themes — generate variations, render each, evaluate against brand criteria, and converge on the right feel. This builds on the visual rendering infrastructure now in place.
-
-This iteration loop is the proof point for the platform's thesis: that non-technical people can build professional, brand-appropriate applications through conversation — with the constrained DSL guaranteeing safety and the visual feedback loop guaranteeing quality.
-
----
-
-## Framework Direction
-
-Stackwright ships 18 content types (carousel, main, tabbed_content, media, video, timeline, icon_grid, code_block, feature_list, testimonial_grid, faq, pricing_table, alert, contact_form_stub, grid, collection_list, text_block, map). Dark mode, SEO metadata, cookie persistence, and responsive design are first-class.
-
-**Next framework priorities** are tracked as GitHub Issues. Themes from PHILOSOPHY.md that shape prioritization:
-
-- **Constrain first, extend later.** New content types should represent genuinely distinct layout patterns, not one-off customizations. The bar for adding to `@stackwright/core` is deliberately high.
-- **The escape hatch is a feature.** Every architectural decision must preserve the ability for a React developer to open the project and extend it without knowing Stackwright exists.
-- **AI writes the YAML; the framework enforces correctness.** Schema reliability matters more than schema expressiveness.
-
----
-
-## Monetization Path
-
-See `.claude/stackwright-pro-vision.md` for the full product vision. Summary of the trajectory:
-
-**Near-term: CMS collection providers.** Third-party CMS SDKs (Contentful, Sanity, Shopify, Airtable, Notion) break frequently. Maintaining these integrations as `@stackwright-pro/collections-*` packages is a real, ongoing service — not a one-time implementation. Each implements the `CollectionProvider` interface; switching backends is a one-line change.
-
-**Medium-term: OpenAPI integration.** `@stackwright-pro/openapi` takes an OpenAPI spec and emits a typed `CollectionProvider`, Zod schemas, TypeScript types, and a client module. Turns "here is an API spec" into "here is a working, validated UI" in hours.
-
-**Long-term: safe enterprise application platform.** YAML-defined backend components — data tables, forms, approval flows, API integrations — all constrained by Zod schemas, all verifiably safe by construction. Subject matter experts define workflows; the platform enforces safety. The same compositional approach — declarative, schema-constrained, AI-writable, visually verifiable — extended from marketing sites to enterprise applications. The one-sentence pitch: visual rendering + constrained DSL + AI iteration = non-technical people building enterprise apps that are safe by construction.
-
-**Adjacent opportunities:** AI-powered project scaffolding, visual editor backed by the MCP server, data-interactive component library (charts, tables, forms).
-
----
-
-## Infrastructure Direction
-
-The MCP server is the primary non-developer interface (see PHILOSOPHY.md: "The GUI Is AI"). It currently provides 20 tools spanning content authoring, site configuration, visual rendering, git workflow, and project management.
-
-**Recent milestones:**
-- Visual rendering tools shipped — AI agents can now see their output
-- `stackwright preview` CLI command — screenshot pages from the terminal
-- E2E render pipeline tests — visual tooling verified end-to-end
-- Whole-site composition (`stackwright_compose_site`) with cross-page validation
-
-**Next infra priorities:** Branding expert agent, E2E screenshot comparison on merge (#141), AI-driven visual QA. All build on the visual rendering infrastructure now in place.
-
----
-
-## Priority Labels
-
-Issues use four priority labels. The architect sets tiers; agents and contributors respect them.
-
-| Label | Meaning |
-|-------|---------|
-| `priority:now` 🔴 | Actively in progress or next up |
-| `priority:next` 🟡 | Committed — starting soon |
-| `priority:later` 🟢 | Planned but not yet committed |
-| `priority:vision` 🟣 | Aspirational — shapes direction, no timeline |
-
-Run `pnpm stackwright -- board` to see the current board, or call `stackwright_get_board` via MCP.
diff --git a/docs/CSP-BEST-PRACTICES.md b/docs/CSP-BEST-PRACTICES.md
new file mode 100644
index 00000000..4d336481
--- /dev/null
+++ b/docs/CSP-BEST-PRACTICES.md
@@ -0,0 +1,555 @@
+# Content Security Policy (CSP) Best Practices for Next.js
+
+*This guide provides practical, implementable patterns for securing Next.js applications with CSP headers, security headers, and Permissions-Policy directives.*
+
+---
+
+## Table of Contents
+
+1. [Quick Start: Complete next.config.js Example](#quick-start-complete-nextconfigjs-example)
+2. [Recommended CSP Directives](#recommended-csp-directives)
+3. [Next.js 14/15 App Router Patterns](#nextjs-1415-app-router-patterns)
+4. [Google Fonts Configuration](#google-fonts-configuration)
+5. [Permissions-Policy Directives](#permissions-policy-directives)
+6. [Common Gotchas & Mistakes](#common-gotchas--mistakes)
+7. [Testing Your CSP](#testing-your-csp)
+8. [Report-URI & CSP Violation Monitoring](#report-uri--csp-violation-monitoring)
+
+---
+
+## Quick Start: Complete next.config.js Example
+
+```javascript
+// next.config.js
+const securityHeaders = [
+ {
+ // Content Security Policy
+ key: 'Content-Security-Policy',
+ value: `
+ default-src 'self';
+ script-src 'self' 'unsafe-inline' 'unsafe-eval';
+ style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
+ font-src 'self' https://fonts.gstatic.com;
+ img-src 'self' data: https://images.unsplash.com https://*.unsplash.com;
+ connect-src 'self' https://api.example.com wss://*.example.com;
+ frame-src 'none';
+ object-src 'none';
+ base-uri 'self';
+ form-action 'self';
+ frame-ancestors 'none';
+ upgrade-insecure-requests;
+ `.replace(/\s{2,}/g, ' ').trim(),
+ },
+ {
+ // X-Content-Type-Options prevents MIME type sniffing
+ key: 'X-Content-Type-Options',
+ value: 'nosniff',
+ },
+ {
+ // X-Frame-Options prevents clickjacking
+ key: 'X-Frame-Options',
+ value: 'DENY',
+ },
+ {
+ // X-XSS-Protection (legacy browsers)
+ key: 'X-XSS-Protection',
+ value: '1; mode=block',
+ },
+ {
+ // Referrer-Policy controls information in the Referer header
+ key: 'Referrer-Policy',
+ value: 'strict-origin-when-cross-origin',
+ },
+ {
+ // Permissions-Policy restricts browser features
+ key: 'Permissions-Policy',
+ value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
+ },
+ {
+ // Strict-Transport-Security enforces HTTPS
+ key: 'Strict-Transport-Security',
+ value: 'max-age=31536000; includeSubDomains; preload',
+ },
+];
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ async headers() {
+ return [
+ {
+ source: '/(.*)',
+ headers: securityHeaders,
+ },
+ ];
+ },
+};
+
+module.exports = nextConfig;
+```
+
+---
+
+## Recommended CSP Directives
+
+### Core Directives (Required)
+
+| Directive | Value | Purpose |
+|-----------|-------|---------|
+| `default-src` | `'self'` | Fallback for all resource types |
+| `script-src` | `'self'` | Restrict JavaScript sources |
+| `style-src` | `'self' 'unsafe-inline'` | Stylesheets (unsafe-inline often needed for Next.js) |
+| `img-src` | `'self' data: https:` | Images (data: for inline images, https: for external) |
+| `font-src` | `'self' https://fonts.gstatic.com` | Web fonts |
+| `connect-src` | `'self'` | AJAX, WebSocket, fetch sources |
+| `frame-src` | `'none'` | Prevent embedding in iframes |
+| `object-src` | `'none'` | Disable Flash and plugins |
+| `base-uri` | `'self'` | Restrict `