@djangocfg/nextjs 2.1.427 → 2.1.429
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/README.md +33 -0
- package/dist/config/index.d.mts +12 -3
- package/dist/config/index.mjs +39 -21
- package/dist/config/index.mjs.map +1 -1
- package/dist/i18n/request.d.mts +3 -3
- package/dist/i18n/request.mjs.map +1 -1
- package/dist/index.mjs +39 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/config/constants.ts +0 -6
- package/src/config/createNextConfig.ts +71 -19
- package/src/i18n/request.ts +3 -5
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
- **i18n** — full next-intl integration with routing, middleware, and components
|
|
28
28
|
- **PWA** — zero-config service worker and manifest
|
|
29
29
|
- **Base Next.js Config** — reusable `createBaseNextConfig()` factory for monorepos
|
|
30
|
+
- **Security headers + CSP** — baseline Content-Security-Policy and hardening headers on every route, dev-aware (see below)
|
|
31
|
+
- **DPoP auth** — one-flag opt-in to RFC 9449 sender-constrained tokens (`dpop: true`) — see [`@docs/DPOP.md`](./@docs/DPOP.md)
|
|
30
32
|
- **Sitemap** — paginated sitemap-index backed by `django_cfg.modules.django_sitemap` (handles 2M+ URLs)
|
|
31
33
|
- **Health Checks** — production-ready health monitoring endpoints
|
|
32
34
|
- **Navigation** — route definitions, menu generation, and active-state helpers
|
|
@@ -112,6 +114,37 @@ export default withBundleAnalyzer(createBaseNextConfig({
|
|
|
112
114
|
}));
|
|
113
115
|
```
|
|
114
116
|
|
|
117
|
+
### Security headers & CSP
|
|
118
|
+
|
|
119
|
+
`createBaseNextConfig()` adds baseline security headers to **every route** out of
|
|
120
|
+
the box — no setup required:
|
|
121
|
+
|
|
122
|
+
- `Content-Security-Policy` — a pragmatic baseline that blocks the high-value
|
|
123
|
+
injection vectors (`object-src 'none'`, `base-uri 'self'`, `form-action 'self'`,
|
|
124
|
+
`frame-ancestors`) while keeping the inline/eval scripts a Next.js app needs.
|
|
125
|
+
**Dev-aware:** `connect-src`/`img-src` allow `http:`/`ws:` in development (local
|
|
126
|
+
Django/Centrifugo) and tighten to `https:`/`wss:` only in production.
|
|
127
|
+
- `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`
|
|
128
|
+
- `X-Frame-Options: DENY` (or `SAMEORIGIN` when `allowIframeFrom` is set)
|
|
129
|
+
|
|
130
|
+
A fully strict nonce-based `script-src` is a planned follow-up (needs middleware +
|
|
131
|
+
per-request nonce). To allow iframe embedding, pass `allowIframeFrom: ['https://example.com']`.
|
|
132
|
+
|
|
133
|
+
### DPoP — sender-constrained tokens (RFC 9449)
|
|
134
|
+
|
|
135
|
+
Make a stolen access token useless, **without a BFF/proxy** — opt in with one flag:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
export default createBaseNextConfig({
|
|
139
|
+
dpop: true, // inlines NEXT_PUBLIC_DPOP_ENABLED='true'
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The browser holds a non-extractable key and signs a per-request `DPoP` proof; the
|
|
144
|
+
backend binds the token to that key (`cnf.jkt`) and rejects replays. Requires
|
|
145
|
+
`JWTConfig(dpop_enabled=True)` on the Django side. Full guide:
|
|
146
|
+
[`@docs/DPOP.md`](./@docs/DPOP.md).
|
|
147
|
+
|
|
115
148
|
### PWA (Progressive Web App)
|
|
116
149
|
|
|
117
150
|
**Zero-config PWA** - Works out of the box! Service worker and offline support included automatically.
|
package/dist/config/index.d.mts
CHANGED
|
@@ -50,6 +50,15 @@ interface BaseNextConfigOptions {
|
|
|
50
50
|
* Set to ['*'] to allow all origins, or specify domains like ['https://djangocfg.com']
|
|
51
51
|
*/
|
|
52
52
|
allowIframeFrom?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Enable DPoP (RFC 9449) sender-constrained tokens on the generated API
|
|
55
|
+
* client. When true, the client holds a non-extractable key and signs a
|
|
56
|
+
* per-request `DPoP` proof so a stolen token is useless. Must be paired with
|
|
57
|
+
* `jwt = JWTConfig(dpop_enabled=True)` on the Django backend. Default: false.
|
|
58
|
+
*
|
|
59
|
+
* Inlines `NEXT_PUBLIC_DPOP_ENABLED='true'` so the generated auth.ts activates.
|
|
60
|
+
*/
|
|
61
|
+
dpop?: boolean;
|
|
53
62
|
/**
|
|
54
63
|
* PWA configuration options
|
|
55
64
|
* Set to false to disable PWA, or provide custom options
|
|
@@ -103,9 +112,9 @@ declare function createBaseNextConfig(options?: BaseNextConfigOptions): NextConf
|
|
|
103
112
|
*/
|
|
104
113
|
declare const PACKAGE_NAME = "@djangocfg/nextjs";
|
|
105
114
|
declare const DJANGO_CFG_BANNER = "\n\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\n\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\n\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2557\n\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\n\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\n\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\n";
|
|
106
|
-
declare const DJANGOCFG_PACKAGES: readonly ["@djangocfg/ui-core", "@djangocfg/
|
|
107
|
-
declare const DEFAULT_TRANSPILE_PACKAGES: readonly ["@djangocfg/i18n", "@djangocfg/ui-core", "@djangocfg/
|
|
108
|
-
declare const DEFAULT_OPTIMIZE_PACKAGES: readonly ["@djangocfg/ui-core", "@djangocfg/
|
|
115
|
+
declare const DJANGOCFG_PACKAGES: readonly ["@djangocfg/ui-core", "@djangocfg/layouts", "@djangocfg/nextjs", "@djangocfg/api", "@djangocfg/centrifugo", "@djangocfg/eslint-config", "@djangocfg/typescript-config"];
|
|
116
|
+
declare const DEFAULT_TRANSPILE_PACKAGES: readonly ["@djangocfg/i18n", "@djangocfg/ui-core", "@djangocfg/layouts", "@djangocfg/ui-tools", "@djangocfg/api", "@djangocfg/centrifugo", "@djangocfg/debuger", "@djangocfg/monitor"];
|
|
117
|
+
declare const DEFAULT_OPTIMIZE_PACKAGES: readonly ["@djangocfg/ui-core", "@djangocfg/layouts", "lucide-react", "recharts"];
|
|
109
118
|
|
|
110
119
|
/**
|
|
111
120
|
* Deep Merge Utility
|
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.429",
|
|
18
18
|
description: "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
19
19
|
keywords: [
|
|
20
20
|
"nextjs",
|
|
@@ -263,7 +263,6 @@ var DJANGO_CFG_BANNER = `
|
|
|
263
263
|
`;
|
|
264
264
|
var DJANGOCFG_PACKAGES = [
|
|
265
265
|
"@djangocfg/ui-core",
|
|
266
|
-
"@djangocfg/ui-nextjs",
|
|
267
266
|
"@djangocfg/layouts",
|
|
268
267
|
"@djangocfg/nextjs",
|
|
269
268
|
"@djangocfg/api",
|
|
@@ -274,20 +273,15 @@ var DJANGOCFG_PACKAGES = [
|
|
|
274
273
|
var DEFAULT_TRANSPILE_PACKAGES = [
|
|
275
274
|
"@djangocfg/i18n",
|
|
276
275
|
"@djangocfg/ui-core",
|
|
277
|
-
"@djangocfg/ui-nextjs",
|
|
278
276
|
"@djangocfg/layouts",
|
|
279
277
|
"@djangocfg/ui-tools",
|
|
280
278
|
"@djangocfg/api",
|
|
281
279
|
"@djangocfg/centrifugo",
|
|
282
280
|
"@djangocfg/debuger",
|
|
283
|
-
"@djangocfg/monitor"
|
|
284
|
-
// Extensions (for source imports without build)
|
|
285
|
-
"@djangocfg/ext-support",
|
|
286
|
-
"@djangocfg/ext-payments"
|
|
281
|
+
"@djangocfg/monitor"
|
|
287
282
|
];
|
|
288
283
|
var DEFAULT_OPTIMIZE_PACKAGES = [
|
|
289
284
|
"@djangocfg/ui-core",
|
|
290
|
-
"@djangocfg/ui-nextjs",
|
|
291
285
|
"@djangocfg/layouts",
|
|
292
286
|
"lucide-react",
|
|
293
287
|
"recharts"
|
|
@@ -1353,6 +1347,10 @@ function createBaseNextConfig(options = {}) {
|
|
|
1353
1347
|
const baseConfig = {
|
|
1354
1348
|
reactStrictMode: true,
|
|
1355
1349
|
trailingSlash: true,
|
|
1350
|
+
// Dev indicator position — set explicitly so it's managed from one place
|
|
1351
|
+
// across every @djangocfg app. Apps can override via `devIndicators` in
|
|
1352
|
+
// their options (deep-merged over this). Kept bottom-left for now.
|
|
1353
|
+
devIndicators: { position: "bottom-left" },
|
|
1356
1354
|
// Static export configuration
|
|
1357
1355
|
...isStaticBuild && {
|
|
1358
1356
|
output: "export",
|
|
@@ -1372,6 +1370,9 @@ function createBaseNextConfig(options = {}) {
|
|
|
1372
1370
|
NEXT_PUBLIC_BASE_PATH: basePath,
|
|
1373
1371
|
NEXT_PUBLIC_API_URL: apiUrl,
|
|
1374
1372
|
NEXT_PUBLIC_SITE_URL: siteUrl,
|
|
1373
|
+
// DPoP toggle for the generated auth client (RFC 9449). Only inlined when
|
|
1374
|
+
// explicitly enabled, so non-DPoP apps stay on the plain Bearer path.
|
|
1375
|
+
...options.dpop ? { NEXT_PUBLIC_DPOP_ENABLED: "true" } : {},
|
|
1375
1376
|
// Disable Next.js telemetry (Next.js 15+ uses env var instead of config option)
|
|
1376
1377
|
NEXT_TELEMETRY_DISABLED: "1",
|
|
1377
1378
|
...options.env
|
|
@@ -1392,19 +1393,35 @@ function createBaseNextConfig(options = {}) {
|
|
|
1392
1393
|
]
|
|
1393
1394
|
}
|
|
1394
1395
|
];
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1396
|
+
const hasIframeAllowlist = !!options.allowIframeFrom && options.allowIframeFrom.length > 0;
|
|
1397
|
+
const frameAncestors = hasIframeAllowlist ? options.allowIframeFrom.includes("*") ? "*" : `'self' ${options.allowIframeFrom.join(" ")}` : "'none'";
|
|
1398
|
+
const connectSrc = isDev ? "connect-src 'self' http: https: ws: wss:" : "connect-src 'self' https: wss:";
|
|
1399
|
+
const imgSrc = isDev ? "img-src 'self' data: blob: http: https:" : "img-src 'self' data: blob: https:";
|
|
1400
|
+
const cspDirectives = [
|
|
1401
|
+
"default-src 'self'",
|
|
1402
|
+
// Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.
|
|
1403
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
|
1404
|
+
"style-src 'self' 'unsafe-inline'",
|
|
1405
|
+
imgSrc,
|
|
1406
|
+
"font-src 'self' data:",
|
|
1407
|
+
// API/websocket calls go cross-origin to Django/Centrifugo.
|
|
1408
|
+
connectSrc,
|
|
1409
|
+
"object-src 'none'",
|
|
1410
|
+
"base-uri 'self'",
|
|
1411
|
+
"form-action 'self'",
|
|
1412
|
+
`frame-ancestors ${frameAncestors}`
|
|
1413
|
+
];
|
|
1414
|
+
headers.push({
|
|
1415
|
+
source: "/:path*",
|
|
1416
|
+
headers: [
|
|
1417
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
1418
|
+
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
1419
|
+
{ key: "Content-Security-Policy", value: cspDirectives.join("; ") },
|
|
1420
|
+
// X-Frame-Options for older browsers. Omitted when a wildcard iframe
|
|
1421
|
+
// allowlist is set (browsers then rely on CSP frame-ancestors).
|
|
1422
|
+
...hasIframeAllowlist && options.allowIframeFrom.includes("*") ? [] : [{ key: "X-Frame-Options", value: hasIframeAllowlist ? "SAMEORIGIN" : "DENY" }]
|
|
1423
|
+
]
|
|
1424
|
+
});
|
|
1408
1425
|
const userHeaders = options.headers ? await options.headers() : [];
|
|
1409
1426
|
return [...headers, ...userHeaders];
|
|
1410
1427
|
},
|
|
@@ -1506,6 +1523,7 @@ function createBaseNextConfig(options = {}) {
|
|
|
1506
1523
|
checkPackages: checkPackages2,
|
|
1507
1524
|
autoInstall,
|
|
1508
1525
|
allowIframeFrom,
|
|
1526
|
+
dpop,
|
|
1509
1527
|
...nextConfigOptions
|
|
1510
1528
|
} = options;
|
|
1511
1529
|
let finalConfig = deepMerge(baseConfig, nextConfigOptions);
|