@djangocfg/nextjs 2.1.483 → 2.1.484

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.483",
3
+ "version": "2.1.484",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -144,9 +144,9 @@
144
144
  "ai-docs": "tsx src/ai/cli.ts"
145
145
  },
146
146
  "peerDependencies": {
147
- "@djangocfg/devtools": "^2.1.483",
148
- "@djangocfg/i18n": "^2.1.483",
149
- "@djangocfg/ui-core": "^2.1.483",
147
+ "@djangocfg/devtools": "^2.1.484",
148
+ "@djangocfg/i18n": "^2.1.484",
149
+ "@djangocfg/ui-core": "^2.1.484",
150
150
  "next": "^16.2.2"
151
151
  },
152
152
  "peerDependenciesMeta": {
@@ -168,11 +168,11 @@
168
168
  "serwist": "^9.5.11"
169
169
  },
170
170
  "devDependencies": {
171
- "@djangocfg/devtools": "^2.1.483",
172
- "@djangocfg/i18n": "^2.1.483",
173
- "@djangocfg/layouts": "^2.1.483",
174
- "@djangocfg/typescript-config": "^2.1.483",
175
- "@djangocfg/ui-core": "^2.1.483",
171
+ "@djangocfg/devtools": "^2.1.484",
172
+ "@djangocfg/i18n": "^2.1.484",
173
+ "@djangocfg/layouts": "^2.1.484",
174
+ "@djangocfg/typescript-config": "^2.1.484",
175
+ "@djangocfg/ui-core": "^2.1.484",
176
176
  "@types/node": "^25.9.5",
177
177
  "@types/react": "19.2.15",
178
178
  "@types/react-dom": "19.2.3",
@@ -31,7 +31,14 @@ import { CSP_BASELINE_VENDORS, type CspVendor, resolveCspVendors } from './csp';
31
31
  import { addCompressionPlugins } from './plugins/compression';
32
32
  import { DevStartupPlugin } from './plugins/devStartup';
33
33
  import { deepMerge } from './utils/deepMerge';
34
- import { getApiUrl, getBasePath, getSiteUrl, isDev, isStaticBuild } from './utils/env';
34
+ import {
35
+ getApiUrl,
36
+ getBasePath,
37
+ getSiteUrl,
38
+ isDev,
39
+ isStaticBuild,
40
+ warnIfApiUrlMissing,
41
+ } from './utils/env';
35
42
 
36
43
  // ─────────────────────────────────────────────────────────────────────────
37
44
  // Configuration Options
@@ -204,6 +211,7 @@ export function createBaseNextConfig(
204
211
  ): NextConfig {
205
212
 
206
213
  const basePath = getBasePath(options.isDefaultCfgAdmin);
214
+ warnIfApiUrlMissing();
207
215
  const apiUrl = getApiUrl();
208
216
  const siteUrl = getSiteUrl();
209
217
 
@@ -2,6 +2,8 @@
2
2
  * Environment Variable Utilities
3
3
  */
4
4
 
5
+ import consola from 'consola';
6
+
5
7
  export const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
6
8
  export const isDev = process.env.NODE_ENV === 'development';
7
9
  export const isProduction = !isDev;
@@ -12,10 +14,67 @@ export function getBasePath(isDefaultCfgAdmin?: boolean): string {
12
14
  return isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';
13
15
  }
14
16
 
17
+ /**
18
+ * Resolve the API origin that gets inlined into the client bundle.
19
+ *
20
+ * `NEXT_PUBLIC_*` is substituted at BUILD time, so a value supplied only at
21
+ * container runtime (a compose `environment:` entry, a `docker run -e`) arrives
22
+ * far too late — the bundle already shipped with whatever was present during
23
+ * `next build`.
24
+ *
25
+ * An empty result means "no API origin is configured". Callers MUST treat that
26
+ * as *do not send the request* rather than falling back to a relative path: the
27
+ * browser resolves a relative URL against the CURRENT page, so on an
28
+ * i18n-prefixed route `/cfg/accounts/profile` silently becomes
29
+ * `https://site.com/<locale>/cfg/accounts/profile` — a 404 that reproduces only
30
+ * in production. Use {@link hasApiUrl} to gate those call sites.
31
+ *
32
+ * The one case where empty legitimately means "relative": a static build served
33
+ * from the same origin as Django.
34
+ */
15
35
  export function getApiUrl(): string {
16
36
  return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_API_URL || '');
17
37
  }
18
38
 
39
+ /**
40
+ * Whether a usable API origin is configured for this build.
41
+ *
42
+ * Gate every network call that targets Django on this. An app with no backend
43
+ * (a pure marketing site) then needs no configuration at all — its optional
44
+ * transports simply stay dormant instead of firing requests at the page's own
45
+ * origin.
46
+ *
47
+ * A static build is same-origin by design, so relative paths ARE the intent
48
+ * there and this reports true despite the empty string.
49
+ */
50
+ export function hasApiUrl(): boolean {
51
+ return isStaticBuild || Boolean(process.env.NEXT_PUBLIC_API_URL);
52
+ }
53
+
54
+ /**
55
+ * Warn once, at build time, when no API origin is configured.
56
+ *
57
+ * Not an error: an app with no backend is a legitimate configuration. But the
58
+ * same empty value also appears when a build *should* have had an API URL and
59
+ * lost it — a `.dockerignore` rule stripping `.env.production`, or a value
60
+ * supplied only through a runtime `environment:` entry. Both produce a bundle
61
+ * whose API-backed features are silently inert, so say so while the build log
62
+ * is still being read.
63
+ */
64
+ export function warnIfApiUrlMissing(): void {
65
+ if (hasApiUrl() || isDev) return;
66
+
67
+ consola.warn(
68
+ 'NEXT_PUBLIC_API_URL is not set — API-backed features are disabled in this build.\n' +
69
+ 'Requests that need it are skipped rather than sent to a relative path.\n' +
70
+ 'If this app DOES have a backend, the value must be present at BUILD time:\n' +
71
+ ' • commit it to the app\'s `.env.production`, and check no `.dockerignore`\n' +
72
+ ' rule (e.g. `**/.env*`) strips that file from the build context;\n' +
73
+ ' • or pass it as a Dockerfile ARG/ENV before the build step.\n' +
74
+ 'A compose `environment:` entry is too late — it only exists at runtime.',
75
+ );
76
+ }
77
+
19
78
  export function getSiteUrl(): string {
20
79
  return isStaticBuild ? '' : (process.env.NEXT_PUBLIC_SITE_URL || '');
21
80
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Usage:
5
5
  * ```tsx
6
- * // app/api/health/route.ts
6
+ * // app/healthz/route.ts
7
7
  * import { createHealthHandler } from '@djangocfg/nextjs/health';
8
8
  *
9
9
  * export const GET = createHealthHandler({
@@ -63,4 +63,3 @@ export function createHealthHandler(config: HealthConfig = {}) {
63
63
  return NextResponse.json(response, { status: statusCode });
64
64
  };
65
65
  }
66
-