@djangocfg/nextjs 2.1.427 → 2.1.428

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.427",
3
+ "version": "2.1.428",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -143,9 +143,9 @@
143
143
  "ai-docs": "tsx src/ai/cli.ts"
144
144
  },
145
145
  "peerDependencies": {
146
- "@djangocfg/i18n": "^2.1.427",
147
- "@djangocfg/monitor": "^2.1.427",
148
- "@djangocfg/ui-core": "^2.1.427",
146
+ "@djangocfg/i18n": "^2.1.428",
147
+ "@djangocfg/monitor": "^2.1.428",
148
+ "@djangocfg/ui-core": "^2.1.428",
149
149
  "next": "^16.2.2"
150
150
  },
151
151
  "peerDependenciesMeta": {
@@ -167,11 +167,11 @@
167
167
  "serwist": "^9.2.3"
168
168
  },
169
169
  "devDependencies": {
170
- "@djangocfg/i18n": "^2.1.427",
171
- "@djangocfg/monitor": "^2.1.427",
172
- "@djangocfg/ui-core": "^2.1.427",
173
- "@djangocfg/layouts": "^2.1.427",
174
- "@djangocfg/typescript-config": "^2.1.427",
170
+ "@djangocfg/i18n": "^2.1.428",
171
+ "@djangocfg/monitor": "^2.1.428",
172
+ "@djangocfg/ui-core": "^2.1.428",
173
+ "@djangocfg/layouts": "^2.1.428",
174
+ "@djangocfg/typescript-config": "^2.1.428",
175
175
  "@types/node": "^25.2.3",
176
176
  "@types/react": "^19.2.15",
177
177
  "@types/react-dom": "^19.2.3",
@@ -21,7 +21,6 @@ export const DJANGO_CFG_BANNER = `
21
21
  // All @djangocfg packages that can be updated together
22
22
  export const DJANGOCFG_PACKAGES = [
23
23
  '@djangocfg/ui-core',
24
- '@djangocfg/ui-nextjs',
25
24
  '@djangocfg/layouts',
26
25
  '@djangocfg/nextjs',
27
26
  '@djangocfg/api',
@@ -35,22 +34,17 @@ export const DJANGOCFG_PACKAGES = [
35
34
  export const DEFAULT_TRANSPILE_PACKAGES = [
36
35
  '@djangocfg/i18n',
37
36
  '@djangocfg/ui-core',
38
- '@djangocfg/ui-nextjs',
39
37
  '@djangocfg/layouts',
40
38
  '@djangocfg/ui-tools',
41
39
  '@djangocfg/api',
42
40
  '@djangocfg/centrifugo',
43
41
  '@djangocfg/debuger',
44
42
  '@djangocfg/monitor',
45
- // Extensions (for source imports without build)
46
- '@djangocfg/ext-support',
47
- '@djangocfg/ext-payments',
48
43
  ] as const;
49
44
 
50
45
  // Default packages to optimize imports
51
46
  export const DEFAULT_OPTIMIZE_PACKAGES = [
52
47
  '@djangocfg/ui-core',
53
- '@djangocfg/ui-nextjs',
54
48
  '@djangocfg/layouts',
55
49
  'lucide-react',
56
50
  'recharts',
@@ -58,6 +58,15 @@ export interface BaseNextConfigOptions {
58
58
  * Set to ['*'] to allow all origins, or specify domains like ['https://djangocfg.com']
59
59
  */
60
60
  allowIframeFrom?: string[];
61
+ /**
62
+ * Enable DPoP (RFC 9449) sender-constrained tokens on the generated API
63
+ * client. When true, the client holds a non-extractable key and signs a
64
+ * per-request `DPoP` proof so a stolen token is useless. Must be paired with
65
+ * `jwt = JWTConfig(dpop_enabled=True)` on the Django backend. Default: false.
66
+ *
67
+ * Inlines `NEXT_PUBLIC_DPOP_ENABLED='true'` so the generated auth.ts activates.
68
+ */
69
+ dpop?: boolean;
61
70
  /**
62
71
  * PWA configuration options
63
72
  * Set to false to disable PWA, or provide custom options
@@ -121,6 +130,11 @@ export function createBaseNextConfig(
121
130
  reactStrictMode: true,
122
131
  trailingSlash: true,
123
132
 
133
+ // Dev indicator position — set explicitly so it's managed from one place
134
+ // across every @djangocfg app. Apps can override via `devIndicators` in
135
+ // their options (deep-merged over this). Kept bottom-left for now.
136
+ devIndicators: { position: 'bottom-left' },
137
+
124
138
  // Static export configuration
125
139
  ...(isStaticBuild && {
126
140
  output: 'export' as const,
@@ -142,6 +156,9 @@ export function createBaseNextConfig(
142
156
  NEXT_PUBLIC_BASE_PATH: basePath,
143
157
  NEXT_PUBLIC_API_URL: apiUrl,
144
158
  NEXT_PUBLIC_SITE_URL: siteUrl,
159
+ // DPoP toggle for the generated auth client (RFC 9449). Only inlined when
160
+ // explicitly enabled, so non-DPoP apps stay on the plain Bearer path.
161
+ ...(options.dpop ? { NEXT_PUBLIC_DPOP_ENABLED: 'true' } : {}),
145
162
  // Disable Next.js telemetry (Next.js 15+ uses env var instead of config option)
146
163
  NEXT_TELEMETRY_DISABLED: '1',
147
164
  ...options.env,
@@ -165,26 +182,60 @@ export function createBaseNextConfig(
165
182
  },
166
183
  ];
167
184
 
168
- // Add iframe embedding headers if allowIframeFrom is specified
169
- if (options.allowIframeFrom && options.allowIframeFrom.length > 0) {
170
- const frameAncestors = options.allowIframeFrom.includes('*')
171
- ? '*'
172
- : `'self' ${options.allowIframeFrom.join(' ')}`;
185
+ // ── Baseline security headers on every route ──────────────────────────
186
+ // First line of defence against XSS/clickjacking/sniffing. The CSP here is
187
+ // a PRAGMATIC baseline: it kills the high-value injection vectors
188
+ // (plugins/objects, <base> hijacking, form-action exfiltration, iframe
189
+ // embedding) while still allowing the inline/eval scripts a Next.js app
190
+ // needs in dev. A fully strict nonce-based `script-src` is a follow-up
191
+ // (needs middleware + per-request nonce wiring) — tracked in the
192
+ // auth-hardening plan. `allowIframeFrom` (below) overrides frame-ancestors.
193
+ const hasIframeAllowlist =
194
+ !!options.allowIframeFrom && options.allowIframeFrom.length > 0;
195
+ const frameAncestors = hasIframeAllowlist
196
+ ? (options.allowIframeFrom!.includes('*')
197
+ ? '*'
198
+ : `'self' ${options.allowIframeFrom!.join(' ')}`)
199
+ : "'none'";
200
+
201
+ // In dev the backend (Django, Centrifugo) is plain http/ws on localhost,
202
+ // so connect/img must allow http:/ws:. Production stays strict (https/wss
203
+ // only) to forbid downgrade/cleartext exfiltration.
204
+ const connectSrc = isDev
205
+ ? "connect-src 'self' http: https: ws: wss:"
206
+ : "connect-src 'self' https: wss:";
207
+ const imgSrc = isDev
208
+ ? "img-src 'self' data: blob: http: https:"
209
+ : "img-src 'self' data: blob: https:";
210
+
211
+ const cspDirectives = [
212
+ "default-src 'self'",
213
+ // Next.js requires inline + eval for its runtime/HMR. Tighten to nonces later.
214
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
215
+ "style-src 'self' 'unsafe-inline'",
216
+ imgSrc,
217
+ "font-src 'self' data:",
218
+ // API/websocket calls go cross-origin to Django/Centrifugo.
219
+ connectSrc,
220
+ "object-src 'none'",
221
+ "base-uri 'self'",
222
+ "form-action 'self'",
223
+ `frame-ancestors ${frameAncestors}`,
224
+ ];
173
225
 
174
- headers.push({
175
- source: '/:path*',
176
- headers: [
177
- // Content-Security-Policy frame-ancestors directive
178
- { key: 'Content-Security-Policy', value: `frame-ancestors ${frameAncestors}` },
179
- // X-Frame-Options for older browsers (ALLOW-FROM is deprecated, use CSP instead)
180
- // Only set SAMEORIGIN if allowing all, otherwise browsers will use CSP
181
- ...(options.allowIframeFrom.includes('*')
182
- ? []
183
- : [{ key: 'X-Frame-Options', value: 'SAMEORIGIN' }]
184
- ),
185
- ],
186
- });
187
- }
226
+ headers.push({
227
+ source: '/:path*',
228
+ headers: [
229
+ { key: 'X-Content-Type-Options', value: 'nosniff' },
230
+ { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
231
+ { key: 'Content-Security-Policy', value: cspDirectives.join('; ') },
232
+ // X-Frame-Options for older browsers. Omitted when a wildcard iframe
233
+ // allowlist is set (browsers then rely on CSP frame-ancestors).
234
+ ...(hasIframeAllowlist && options.allowIframeFrom!.includes('*')
235
+ ? []
236
+ : [{ key: 'X-Frame-Options', value: hasIframeAllowlist ? 'SAMEORIGIN' : 'DENY' }]),
237
+ ],
238
+ });
188
239
 
189
240
  // Merge with user-provided headers
190
241
  const userHeaders = options.headers ? await options.headers() : [];
@@ -308,6 +359,7 @@ export function createBaseNextConfig(
308
359
  checkPackages,
309
360
  autoInstall,
310
361
  allowIframeFrom,
362
+ dpop,
311
363
  ...nextConfigOptions
312
364
  } = options;
313
365
 
@@ -8,11 +8,9 @@
8
8
  * // i18n/request.ts in your app
9
9
  * import { createRequestConfig } from '@djangocfg/nextjs/i18n';
10
10
  * import { en, ru, ko } from '@djangocfg/i18n';
11
- * import { leadsI18n } from '@djangocfg/ext-leads';
12
11
  *
13
12
  * export default createRequestConfig({
14
13
  * locales: { en, ru, ko },
15
- * extensions: [leadsI18n],
16
14
  * });
17
15
  * ```
18
16
  */
@@ -101,11 +99,11 @@ function loadMessages(
101
99
  * ```ts
102
100
  * // i18n/request.ts
103
101
  * import { createRequestConfig } from '@djangocfg/nextjs/i18n';
104
- * import { leadsI18n } from '@djangocfg/ext-leads';
105
- * import { paymentsI18n } from '@djangocfg/ext-payments';
106
102
  *
103
+ * // `extensions` merges extra namespaced translation bundles, each shaped
104
+ * // as `{ namespace: string, locales: Record<LocaleCode, Messages> }`.
107
105
  * export default createRequestConfig({
108
- * extensions: [leadsI18n, paymentsI18n],
106
+ * extensions: [myFeatureI18n],
109
107
  * });
110
108
  * ```
111
109
  */