-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnext.config.mjs
More file actions
88 lines (83 loc) · 4.5 KB
/
Copy pathnext.config.mjs
File metadata and controls
88 lines (83 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/** @type {import('next').NextConfig} */
// Only harden the anti-framing headers in production. In development (`next dev`)
// we drop them so the app can be embedded in an iframe — which is exactly what
// happens when someone runs THIS project (or any app) inside Nova's Run tab via
// WebContainers: a dev server whose X-Frame-Options / frame-ancestors would
// otherwise make the preview "refuse to connect". Production stays locked down.
const isProd = process.env.NODE_ENV === "production";
// Content-Security-Policy. Note: Nova's in-browser toolchain (Babel-standalone,
// esbuild-wasm, Monaco workers, the live canvas) genuinely requires
// 'unsafe-eval'/'wasm-unsafe-eval'/blob:, so script-src can't be locked down
// without breaking the product. The value here is in the OTHER directives —
// object-src 'none', base-uri 'self', frame-ancestors (prod), form-action
// 'self' — plus documenting intent. It is a baseline, not a guarantee.
const csp = [
"default-src 'self'",
"base-uri 'self'",
"object-src 'none'",
// lock framing in prod; allow it in dev so a dev server is previewable in Run
isProd ? "frame-ancestors 'self'" : "frame-ancestors *",
"form-action 'self'",
// toolchain needs eval/wasm-eval + blob workers; allow the CDNs the app and canvas load from
"script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' blob: https://cdn.jsdelivr.net https://cdn.tailwindcss.com https://unpkg.com https://esm.sh https://plausible.io",
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com",
"img-src 'self' data: blob: https:",
"font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net",
"worker-src 'self' blob:",
"child-src 'self' blob:",
// http://127.0.0.1:* / localhost lets the Run preview iframe load the local
// runner agent's bridge proxy (127.0.0.1:4320).
"frame-src 'self' blob: data: https: http://127.0.0.1:* http://localhost:*",
// BYO-key calls go direct to the user's chosen provider, and custom endpoints
// are allowed, so connect-src stays broad (this is the trade-off of "any model").
// http://127.0.0.1:* / localhost is the local runner agent (detection + SSE on
// :4319) — it's the user's own machine, and the agent is origin-locked + token-gated.
"connect-src 'self' blob: data: https: wss: ws: http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:*",
].join("; ");
const securityHeaders = [
{ key: "Content-Security-Policy", value: csp },
{ key: "X-Content-Type-Options", value: "nosniff" },
// X-Frame-Options only in prod (see note above) — omitting it in dev lets the
// Run tab embed a dev server of this app.
...(isProd ? [{ key: "X-Frame-Options", value: "SAMEORIGIN" }] : []),
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "X-DNS-Prefetch-Control", value: "off" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), browsing-topics=()" },
];
const nextConfig = {
reactStrictMode: true,
async headers() {
// WebContainers need cross-origin isolation (SharedArrayBuffer). Scope the
// isolation headers to the /run surface ONLY, so the editor's in-browser
// bundler + external CDNs (esm.sh, Tailwind, jsDelivr, avatars) keep working.
const isolation = [
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Embedder-Policy", value: "credentialless" },
];
return [
{ source: "/run", headers: isolation },
{ source: "/run/:path*", headers: isolation },
// /editor is isolated too so it can boot a WebContainer in-place for the
// live preview pane (play-as-toggle). Validated that the canvas still
// renders CDN-heavy imported sites under COEP credentialless. Both the bare
// path and the per-project route (/editor/{projectId}) need it.
{ source: "/editor", headers: isolation },
{ source: "/editor/:path*", headers: isolation },
// baseline security headers on every route
{ source: "/:path*", headers: securityHeaders },
];
},
async rewrites() {
// The homepage and docs are static, canvas-editable HTML files
// (public/landing.html, public/docs.html) — so they can be edited visually
// in Nova itself. beforeFiles runs before the filesystem, so these own their
// paths instead of any app route.
return {
beforeFiles: [
{ source: "/", destination: "/landing.html" },
{ source: "/docs", destination: "/docs.html" },
],
};
},
};
export default nextConfig;