@djangocfg/nextjs 2.1.471 → 2.1.474
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.d.mts +36 -0
- package/dist/config/index.mjs +71 -34
- package/dist/config/index.mjs.map +1 -1
- package/dist/index.mjs +71 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/config/createNextConfig.ts +129 -40
package/dist/config/index.d.mts
CHANGED
|
@@ -70,6 +70,42 @@ interface BaseNextConfigOptions {
|
|
|
70
70
|
* dashboard), so every other app keeps the tighter default. Default: false.
|
|
71
71
|
*/
|
|
72
72
|
stripe?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Content-Security-Policy control.
|
|
75
|
+
*
|
|
76
|
+
* - `false` — do not emit the `Content-Security-Policy` header at all.
|
|
77
|
+
* Use this on apps with no user auth/payment surface and unpredictable
|
|
78
|
+
* third-party embeds (a pitch deck, a marketing microsite) where
|
|
79
|
+
* naming every future embed's origin is not worth the maintenance —
|
|
80
|
+
* `X-Content-Type-Options`/`Referrer-Policy`/`X-Frame-Options` below
|
|
81
|
+
* still apply.
|
|
82
|
+
* - object — keep the baseline CSP but additively loosen specific
|
|
83
|
+
* directives for named trusted origins (the pattern `stripe` above is
|
|
84
|
+
* a hardcoded special case of). Every array is additive; omit a key to
|
|
85
|
+
* leave that directive untouched.
|
|
86
|
+
* - omitted — tightest default (`frame-src 'self'`, etc.), same as today.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // No CSP at all — simplest option for a low-risk app with ad hoc embeds
|
|
91
|
+
* createBaseNextConfig({ csp: false })
|
|
92
|
+
*
|
|
93
|
+
* // Keep CSP, just allow one trusted iframe origin
|
|
94
|
+
* createBaseNextConfig({ csp: { frameSrc: ['https://www.youtube-nocookie.com'] } })
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
csp?: false | {
|
|
98
|
+
/** Extra `frame-src` origins (iframes this app embeds). */
|
|
99
|
+
frameSrc?: string[];
|
|
100
|
+
/** Extra `script-src` origins (third-party SDK scripts). */
|
|
101
|
+
scriptSrc?: string[];
|
|
102
|
+
/** Extra `connect-src` origins (XHR/fetch/WebSocket to a third party). */
|
|
103
|
+
connectSrc?: string[];
|
|
104
|
+
/** Extra `img-src` origins (hotlinked third-party images). */
|
|
105
|
+
imgSrc?: string[];
|
|
106
|
+
/** Extra `font-src` origins (third-party web fonts). */
|
|
107
|
+
fontSrc?: string[];
|
|
108
|
+
};
|
|
73
109
|
/**
|
|
74
110
|
* PWA configuration options
|
|
75
111
|
* Set to false to disable PWA, or provide custom options
|
package/dist/config/index.mjs
CHANGED
|
@@ -14,7 +14,7 @@ var require_package = __commonJS({
|
|
|
14
14
|
"package.json"(exports, module) {
|
|
15
15
|
module.exports = {
|
|
16
16
|
name: "@djangocfg/nextjs",
|
|
17
|
-
version: "2.1.
|
|
17
|
+
version: "2.1.474",
|
|
18
18
|
description: "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
19
19
|
keywords: [
|
|
20
20
|
"nextjs",
|
|
@@ -1396,45 +1396,81 @@ function createBaseNextConfig(options = {}) {
|
|
|
1396
1396
|
];
|
|
1397
1397
|
const hasIframeAllowlist = !!options.allowIframeFrom && options.allowIframeFrom.length > 0;
|
|
1398
1398
|
const frameAncestors = hasIframeAllowlist ? options.allowIframeFrom.includes("*") ? "*" : `'self' ${options.allowIframeFrom.join(" ")}` : "'none'";
|
|
1399
|
+
const cspDisabled = options.csp === false;
|
|
1400
|
+
const cspExtra = typeof options.csp === "object" ? options.csp : void 0;
|
|
1399
1401
|
const stripeScript = options.stripe ? " https://js.stripe.com" : "";
|
|
1400
1402
|
const stripeConnect = options.stripe ? " https://api.stripe.com" : "";
|
|
1401
1403
|
const stripeFrame = options.stripe ? " https://js.stripe.com https://checkout.stripe.com" : "";
|
|
1402
|
-
const
|
|
1403
|
-
const
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1404
|
+
const extraScript = cspExtra?.scriptSrc?.length ? " " + cspExtra.scriptSrc.join(" ") : "";
|
|
1405
|
+
const extraConnect = cspExtra?.connectSrc?.length ? " " + cspExtra.connectSrc.join(" ") : "";
|
|
1406
|
+
const extraFrame = cspExtra?.frameSrc?.length ? " " + cspExtra.frameSrc.join(" ") : "";
|
|
1407
|
+
const extraImg = cspExtra?.imgSrc?.length ? " " + cspExtra.imgSrc.join(" ") : "";
|
|
1408
|
+
const extraFont = cspExtra?.fontSrc?.length ? " " + cspExtra.fontSrc.join(" ") : "";
|
|
1409
|
+
const connectSrc = isDev ? `connect-src 'self' http: https: ws: wss:${stripeConnect}${extraConnect}` : `connect-src 'self' https: wss:${stripeConnect}${extraConnect}`;
|
|
1410
|
+
const imgSrc = isDev ? `img-src 'self' data: blob: http: https:${extraImg}` : `img-src 'self' data: blob: https:${extraImg}`;
|
|
1411
|
+
if (!cspDisabled) {
|
|
1412
|
+
const cspDirectives = [
|
|
1413
|
+
"default-src 'self'",
|
|
1414
|
+
// Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.
|
|
1415
|
+
`script-src 'self' 'unsafe-inline' 'unsafe-eval'${stripeScript}${extraScript}`,
|
|
1416
|
+
"style-src 'self' 'unsafe-inline'",
|
|
1417
|
+
imgSrc,
|
|
1418
|
+
`font-src 'self' data:${extraFont}`,
|
|
1419
|
+
// API/websocket calls go cross-origin to Django/Centrifugo.
|
|
1420
|
+
connectSrc,
|
|
1421
|
+
// Stripe Elements/Checkout and any app-declared trusted origins
|
|
1422
|
+
// (options.csp.frameSrc) mount iframes; add their hosts when enabled.
|
|
1423
|
+
`frame-src 'self'${stripeFrame}${extraFrame}`,
|
|
1424
|
+
"object-src 'none'",
|
|
1425
|
+
"base-uri 'self'",
|
|
1426
|
+
"form-action 'self'",
|
|
1427
|
+
`frame-ancestors ${frameAncestors}`
|
|
1428
|
+
];
|
|
1429
|
+
headers.push({
|
|
1430
|
+
source: "/:path*",
|
|
1431
|
+
headers: [
|
|
1432
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
1433
|
+
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
1434
|
+
{ key: "Content-Security-Policy", value: cspDirectives.join("; ") },
|
|
1435
|
+
// X-Frame-Options for older browsers. Omitted when a wildcard iframe
|
|
1436
|
+
// allowlist is set (browsers then rely on CSP frame-ancestors).
|
|
1437
|
+
...hasIframeAllowlist && options.allowIframeFrom.includes("*") ? [] : [{ key: "X-Frame-Options", value: hasIframeAllowlist ? "SAMEORIGIN" : "DENY" }]
|
|
1438
|
+
]
|
|
1439
|
+
});
|
|
1440
|
+
} else {
|
|
1441
|
+
headers.push({
|
|
1442
|
+
source: "/:path*",
|
|
1443
|
+
headers: [
|
|
1444
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
1445
|
+
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
1446
|
+
...hasIframeAllowlist && options.allowIframeFrom.includes("*") ? [] : [{ key: "X-Frame-Options", value: hasIframeAllowlist ? "SAMEORIGIN" : "DENY" }]
|
|
1447
|
+
]
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1431
1450
|
const userHeaders = options.headers ? await options.headers() : [];
|
|
1432
1451
|
return [...headers, ...userHeaders];
|
|
1433
1452
|
},
|
|
1434
|
-
// Rewrites
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1453
|
+
// Rewrites — first-party analytics ingest proxy, then user rewrites.
|
|
1454
|
+
//
|
|
1455
|
+
// @djangocfg/analytics POSTs to a RELATIVE /cfg/analytics/collect/ so the beacon
|
|
1456
|
+
// stays same-origin (sendBeacon is unreliable cross-origin). On a split-domain
|
|
1457
|
+
// deploy (Next on one host, Django on `apiUrl`) that same-origin path has no
|
|
1458
|
+
// route here, so every beacon 404s and nothing is ingested — unless we proxy it.
|
|
1459
|
+
// Same-domain and static builds keep apiUrl empty and need no proxy.
|
|
1460
|
+
...apiUrl && !isStaticBuild || options.rewrites ? {
|
|
1461
|
+
async rewrites() {
|
|
1462
|
+
const analyticsProxy = apiUrl && !isStaticBuild ? [{ source: "/cfg/analytics/:path*", destination: `${apiUrl}/cfg/analytics/:path*` }] : [];
|
|
1463
|
+
const user = options.rewrites ? await options.rewrites() : [];
|
|
1464
|
+
if (Array.isArray(user)) {
|
|
1465
|
+
return { beforeFiles: analyticsProxy, afterFiles: user, fallback: [] };
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
beforeFiles: [...analyticsProxy, ...user?.beforeFiles ?? []],
|
|
1469
|
+
afterFiles: user?.afterFiles ?? [],
|
|
1470
|
+
fallback: user?.fallback ?? []
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
} : {},
|
|
1438
1474
|
// Redirects for URL normalization
|
|
1439
1475
|
// Fixes "Failed to construct 'URL': Invalid URL" error with double slashes
|
|
1440
1476
|
async redirects() {
|
|
@@ -1532,6 +1568,7 @@ function createBaseNextConfig(options = {}) {
|
|
|
1532
1568
|
allowIframeFrom,
|
|
1533
1569
|
dpop,
|
|
1534
1570
|
stripe,
|
|
1571
|
+
csp,
|
|
1535
1572
|
...nextConfigOptions
|
|
1536
1573
|
} = options;
|
|
1537
1574
|
let finalConfig = deepMerge(baseConfig, nextConfigOptions);
|