@djangocfg/nextjs 1.0.4 → 1.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @djangocfg/nextjs
2
2
 
3
- > Comprehensive Next.js utilities and components: sitemap generation, health checks, OG images, legal pages, error pages, and more
3
+ > Server-side Next.js utilities: sitemap generation, health checks, OG images, contact forms, navigation, and config
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@djangocfg/nextjs.svg)](https://www.npmjs.com/package/@djangocfg/nextjs)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
@@ -11,7 +11,9 @@
11
11
 
12
12
  ## Overview
13
13
 
14
- `@djangocfg/nextjs` provides a comprehensive set of utilities and components for Next.js applications. From SEO optimization with sitemaps to health monitoring, from dynamic OG images to ready-to-use legal pages, this package covers common Next.js needs out of the box.
14
+ `@djangocfg/nextjs` provides server-side utilities for Next.js applications. This package focuses on server-only functionality: SEO optimization with sitemaps, health monitoring, dynamic OG images, contact form handling, navigation utilities, and configuration management.
15
+
16
+ > **Note:** Client components (legal pages, error pages, redirect components) have been moved to `@djangocfg/layouts` to keep this package server-only.
15
17
 
16
18
  ## Features
17
19
 
@@ -19,12 +21,10 @@
19
21
  - **Sitemap Generation** - Dynamic XML sitemap generation for SEO
20
22
  - **Health Checks** - Production-ready health monitoring endpoints
21
23
  - **OG Images** - Dynamic Open Graph image generation with templates
24
+ - **Contact Forms** - Server-side contact form submission handlers
22
25
  - **Navigation Utilities** - Route definitions, menu generation, and navigation helpers
23
- - **Legal Pages** - Pre-built privacy, terms, cookies, and security pages
24
- - **Error Pages** - Reusable 404 and 500 error layouts
25
- - **HomePage Component** - Smart authentication redirect component
26
26
  - **TypeScript** - Full type safety throughout
27
- - **Self-Contained** - No external dependencies beyond Next.js
27
+ - **Server-Only** - No client-side code, pure server utilities
28
28
 
29
29
  ## Installation
30
30
 
@@ -147,77 +147,55 @@ Automatically add OG images to page metadata:
147
147
 
148
148
  ```tsx
149
149
  // app/page.tsx
150
- import { generateMetadata } from '@core/metadata';
150
+ import { generateOgImageMetadata } from '@djangocfg/nextjs/og-image/utils';
151
151
 
152
- export const metadata = generateMetadata({
152
+ export const metadata = generateOgImageMetadata({
153
153
  title: 'My Page',
154
154
  description: 'Page description',
155
155
  });
156
156
  ```
157
157
 
158
- ### Legal Pages
158
+ ### Contact Form
159
159
 
160
- Use pre-built legal pages:
160
+ Create a contact form API route:
161
161
 
162
162
  ```tsx
163
- // app/legal/privacy/page.tsx
164
- import { PrivacyPage } from '@djangocfg/nextjs/legal';
163
+ // app/api/contact/route.ts
164
+ import { createContactRoute } from '@djangocfg/nextjs/contact';
165
165
 
166
- export default PrivacyPage;
166
+ export const POST = createContactRoute();
167
167
  ```
168
168
 
169
- Available pages:
170
- - `PrivacyPage` - Privacy policy
171
- - `TermsPage` - Terms of service
172
- - `CookiesPage` - Cookie policy
173
- - `SecurityPage` - Security policy
174
-
175
- ### Error Pages
176
-
177
- Use reusable error layouts:
169
+ The route handler accepts form data and submits it to your API endpoint. The `apiUrl` can be passed in the request body or configured via environment variables.
178
170
 
179
- ```tsx
180
- // app/not-found.tsx
181
- import { ErrorLayout } from '@djangocfg/nextjs/errors';
171
+ ### Navigation Utilities
182
172
 
183
- export default function NotFound() {
184
- return <ErrorLayout code={404} supportEmail="support@example.com" />;
185
- }
186
- ```
173
+ Define routes and generate navigation menus:
187
174
 
188
175
  ```tsx
189
- // app/error.tsx
190
- 'use client';
176
+ import { defineRoute, routesToMenuItems } from '@djangocfg/nextjs/navigation';
191
177
 
192
- import { useEffect } from 'react';
193
- import { ErrorLayout } from '@djangocfg/nextjs/errors';
178
+ const routes = [
179
+ defineRoute('/dashboard', { title: 'Dashboard' }),
180
+ defineRoute('/settings', { title: 'Settings' }),
181
+ ];
194
182
 
195
- export default function Error({ error, reset }: { error: Error; reset: () => void }) {
196
- useEffect(() => {
197
- console.error('Error:', error);
198
- }, [error]);
199
-
200
- return <ErrorLayout code={500} supportEmail="support@example.com" />;
201
- }
183
+ const menuItems = routesToMenuItems(routes);
202
184
  ```
203
185
 
204
- ### HomePage Component
186
+ ### Client Components
205
187
 
206
- Smart authentication redirect component:
188
+ Client-side components (legal pages, error pages, redirect components) are available in `@djangocfg/layouts`:
207
189
 
208
190
  ```tsx
209
- // app/page.tsx
210
- import { HomePage } from '@djangocfg/nextjs/components';
211
-
212
- export default function Page() {
213
- return (
214
- <HomePage
215
- authenticatedPath="/dashboard"
216
- unauthenticatedPath="/auth"
217
- loadingText="Loading..."
218
- />
219
- );
220
- }
191
+ // Legal pages
192
+ import { PrivacyPage, TermsPage } from '@djangocfg/layouts/pages/legal';
193
+
194
+ // Error pages
195
+ import { ErrorLayout } from '@djangocfg/layouts/components/errors';
196
+
197
+ // Redirect component
198
+ import { RedirectPage } from '@djangocfg/layouts/components/RedirectPage';
221
199
  ```
222
200
 
223
201
  ## Exports
@@ -230,10 +208,10 @@ export default function Page() {
230
208
  | `@djangocfg/nextjs/health` | Health check handlers |
231
209
  | `@djangocfg/nextjs/og-image` | OG image generation |
232
210
  | `@djangocfg/nextjs/og-image/utils` | OG image URL and metadata utilities |
211
+ | `@djangocfg/nextjs/og-image/components` | OG image template components |
212
+ | `@djangocfg/nextjs/contact` | Contact form route handlers |
233
213
  | `@djangocfg/nextjs/navigation` | Route definitions, menu generation, navigation helpers |
234
- | `@djangocfg/nextjs/legal` | Legal page components |
235
- | `@djangocfg/nextjs/errors` | Error page layouts |
236
- | `@djangocfg/nextjs/components` | Reusable components |
214
+ | `@djangocfg/nextjs/scripts` | Utility scripts (e.g., link checking) |
237
215
 
238
216
  ## API Reference
239
217
 
@@ -326,17 +304,35 @@ generateOgImageMetadata(
326
304
 
327
305
  #### `generateOgImageUrl(baseUrl, params, useBase64?)`
328
306
 
329
- Generates OG image URL with base64-encoded parameters.
307
+ > **Note:** This utility has been moved to `@djangocfg/layouts/utils/og-image` for client-side usage.
308
+
309
+ For server-side usage, you can still use it from `@djangocfg/nextjs/og-image/utils`, but for client components, import from layouts:
330
310
 
331
311
  ```tsx
332
- generateOgImageUrl('/api/og', {
312
+ // Client component
313
+ import { generateOgImageUrl } from '@djangocfg/layouts/utils/og-image';
314
+
315
+ const url = generateOgImageUrl('/api/og', {
333
316
  title: 'My Page',
334
317
  description: 'Page description',
335
318
  siteName: 'My Site',
336
- logo: '/logo.svg',
337
- }, true) // useBase64 = true (default)
319
+ });
320
+ ```
321
+
322
+ ### Contact Form
323
+
324
+ #### `createContactRoute(options?)`
325
+
326
+ Creates a Next.js API route handler for contact form submissions.
327
+
328
+ ```tsx
329
+ createContactRoute({
330
+ // Options are handled via request body or environment variables
331
+ })
338
332
  ```
339
333
 
334
+ The route accepts POST requests with form data and submits to the API endpoint specified in the request body (`_apiUrl` field) or environment variables.
335
+
340
336
  ### Navigation
341
337
 
342
338
  #### `defineRoute(path, metadata, config?)`
@@ -371,57 +367,6 @@ Converts route definitions to menu items.
371
367
  const menuItems = routesToMenuItems(allRoutes);
372
368
  ```
373
369
 
374
- ### Legal Pages
375
-
376
- #### `PrivacyPage`, `TermsPage`, `CookiesPage`, `SecurityPage`
377
-
378
- Pre-built legal page components with default content. Can be customized:
379
-
380
- ```tsx
381
- import { PrivacyPage, privacyConfig } from '@djangocfg/nextjs/legal';
382
-
383
- // Use default
384
- export default PrivacyPage;
385
-
386
- // Or customize
387
- export default function CustomPrivacy() {
388
- return <PrivacyPage config={{
389
- ...privacyConfig,
390
- lastUpdated: '2024-01-01',
391
- }} />;
392
- }
393
- ```
394
-
395
- ### Error Pages
396
-
397
- #### `ErrorLayout`
398
-
399
- Reusable error page layout component.
400
-
401
- ```tsx
402
- <ErrorLayout
403
- code={404 | 500 | string | number}
404
- title?: string
405
- description?: string
406
- supportEmail?: string
407
- actions?: React.ReactNode
408
- showDefaultActions?: boolean
409
- />
410
- ```
411
-
412
- ### Components
413
-
414
- #### `HomePage`
415
-
416
- Authentication redirect component.
417
-
418
- ```tsx
419
- <HomePage
420
- authenticatedPath?: string // Default: '/private'
421
- unauthenticatedPath?: string // Default: '/auth'
422
- loadingText?: string // Default: 'Loading...'
423
- />
424
- ```
425
370
 
426
371
  ## Requirements
427
372
 
@@ -431,9 +376,8 @@ Authentication redirect component.
431
376
 
432
377
  ## Peer Dependencies
433
378
 
434
- - `@djangocfg/layouts` - For auth context in HomePage
435
- - `@djangocfg/ui` - For UI components in legal/error pages
436
- - `lucide-react` - For icons
379
+ - `@djangocfg/api` - For API client functionality
380
+ - `next` - Next.js framework (^15.4.4)
437
381
 
438
382
  ## Documentation
439
383
 
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "1.0.4",
4
- "description": "Next.js utilities and components: sitemap, health, OG images, legal pages, error pages",
3
+ "version": "1.0.6",
4
+ "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
7
7
  "sitemap",
8
8
  "health",
9
9
  "og-image",
10
- "legal-pages",
11
- "error-pages",
10
+ "contact",
11
+ "navigation",
12
+ "config",
12
13
  "react",
13
14
  "typescript"
14
15
  ],
@@ -59,21 +60,6 @@
59
60
  "import": "./src/og-image/components/index.ts",
60
61
  "default": "./src/og-image/components/index.ts"
61
62
  },
62
- "./legal": {
63
- "types": "./src/legal/index.ts",
64
- "import": "./src/legal/index.ts",
65
- "default": "./src/legal/index.ts"
66
- },
67
- "./errors": {
68
- "types": "./src/errors/index.ts",
69
- "import": "./src/errors/index.ts",
70
- "default": "./src/errors/index.ts"
71
- },
72
- "./components": {
73
- "types": "./src/components/index.ts",
74
- "import": "./src/components/index.ts",
75
- "default": "./src/components/index.ts"
76
- },
77
63
  "./contact": {
78
64
  "types": "./src/contact/index.ts",
79
65
  "import": "./src/contact/index.ts",
@@ -88,6 +74,11 @@
88
74
  "types": "./src/config/index.ts",
89
75
  "import": "./src/config/index.ts",
90
76
  "default": "./src/config/index.ts"
77
+ },
78
+ "./scripts": {
79
+ "types": "./src/scripts/index.ts",
80
+ "import": "./src/scripts/index.ts",
81
+ "default": "./src/scripts/index.ts"
91
82
  }
92
83
  },
93
84
  "files": [
@@ -100,34 +91,35 @@
100
91
  "dev": "tsup --watch",
101
92
  "clean": "rm -rf dist",
102
93
  "lint": "eslint .",
103
- "check": "tsc --noEmit"
104
- },
105
- "dependencies": {
106
- "@vercel/og": "^0.8.5"
94
+ "check": "tsc --noEmit",
95
+ "check-links": "tsx src/scripts/check-links.ts"
107
96
  },
108
97
  "peerDependencies": {
109
- "next": "^15.4.4",
110
- "react": "^19.1.0",
111
- "react-dom": "^19.1.0",
112
- "@djangocfg/ui": "^1.4.34",
113
- "@djangocfg/layouts": "^2.0.4",
114
- "@djangocfg/api": "^1.4.34",
115
- "lucide-react": "^0.469.0"
98
+ "@djangocfg/api": "^1.4.36",
99
+ "next": "^15.4.4"
116
100
  },
117
101
  "devDependencies": {
118
- "@djangocfg/typescript-config": "^1.4.34",
119
- "@djangocfg/layouts": "^2.0.4",
102
+ "@djangocfg/imgai": "^1.0.22",
103
+ "@djangocfg/layouts": "^2.0.6",
104
+ "@djangocfg/typescript-config": "^1.4.36",
120
105
  "@types/node": "^24.7.2",
121
106
  "@types/react": "19.2.2",
122
107
  "@types/react-dom": "19.2.1",
123
108
  "@types/webpack": "^5.28.5",
109
+ "@vercel/og": "^0.8.5",
124
110
  "eslint": "^9.37.0",
111
+ "linkinator": "^7.5.0",
125
112
  "lucide-react": "^0.469.0",
113
+ "picocolors": "^1.1.1",
114
+ "prompts": "^2.4.2",
126
115
  "tsup": "^8.0.1",
116
+ "tsx": "^4.19.2",
127
117
  "typescript": "^5.9.3"
128
118
  },
129
119
  "publishConfig": {
130
120
  "access": "public"
121
+ },
122
+ "dependencies": {
123
+ "chalk": "^5.3.0"
131
124
  }
132
- }
133
-
125
+ }
@@ -23,6 +23,7 @@
23
23
 
24
24
  import type { NextConfig } from 'next';
25
25
  import type { Configuration as WebpackConfig } from 'webpack';
26
+ import chalk from 'chalk';
26
27
  import { deepMerge } from './deepMerge';
27
28
 
28
29
  // ─────────────────────────────────────────────────────────────────────────
@@ -32,6 +33,25 @@ import { deepMerge } from './deepMerge';
32
33
  const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
33
34
  const isDev = process.env.NODE_ENV === 'development';
34
35
 
36
+ // Global flag to track if browser was already opened (persists across hot reloads)
37
+ let browserOpened = false;
38
+ let bannerPrinted = false;
39
+
40
+ // ASCII Art Banner for Django CFG
41
+ const DJANGO_CFG_BANNER = `
42
+ 888 d8b .d888
43
+ 888 Y8P d88P"
44
+ 888 888
45
+ .d88888 8888 8888b. 88888b. .d88b. .d88b. .d8888b 888888 .d88b.
46
+ d88" 888 "888 "88b 888 "88b d88P"88b d88""88b d88P" 888 d88P"88b
47
+ 888 888 888 .d888888 888 888 888 888 888 888 888 888 888 888
48
+ Y88b 888 888 888 888 888 888 Y88b 888 Y88..88P Y88b. 888 Y88b 888
49
+ "Y88888 888 "Y888888 888 888 "Y88888 "Y88P" "Y8888P 888 "Y88888
50
+ 888 888 888
51
+ d88P Y8b d88P Y8b d88P
52
+ 888P" "Y88P" "Y88P"
53
+ `;
54
+
35
55
  // ─────────────────────────────────────────────────────────────────────────
36
56
  // Configuration Options
37
57
  // ─────────────────────────────────────────────────────────────────────────
@@ -43,6 +63,8 @@ export interface BaseNextConfigOptions {
43
63
  transpilePackages?: string[];
44
64
  /** Additional optimize package imports (merged with defaults) */
45
65
  optimizePackageImports?: string[];
66
+ /** Automatically open browser in dev mode (default: false) */
67
+ openBrowser?: boolean;
46
68
  /** Custom webpack configuration function (called after base webpack logic) */
47
69
  webpack?: (
48
70
  config: WebpackConfig,
@@ -153,6 +175,50 @@ export function createBaseNextConfig(
153
175
  webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean;[key: string]: any }) => {
154
176
  const { isServer, dev } = webpackOptions;
155
177
 
178
+ // Print banner and auto-open browser in dev mode (client-side only)
179
+ if (dev && !isServer) {
180
+ // Create a simple plugin to print banner and open browser after first compilation
181
+ const DevStartupPlugin = class {
182
+ apply(compiler: any) {
183
+ compiler.hooks.done.tap('DevStartupPlugin', () => {
184
+ // Print banner only once
185
+ if (!bannerPrinted) {
186
+ bannerPrinted = true;
187
+ console.log('\n' + chalk.yellowBright.bold(DJANGO_CFG_BANNER) + '\n');
188
+ }
189
+
190
+ // Auto-open browser if enabled
191
+ if (options.openBrowser && !browserOpened) {
192
+ browserOpened = true;
193
+ // Delay to ensure server is ready
194
+ setTimeout(() => {
195
+ const { exec } = require('child_process');
196
+ const port = process.env.PORT || '3000';
197
+ const url = `http://localhost:${port}`;
198
+
199
+ const command = process.platform === 'darwin'
200
+ ? 'open'
201
+ : process.platform === 'win32'
202
+ ? 'start'
203
+ : 'xdg-open';
204
+
205
+ exec(`${command} ${url}`, (error: Error | null) => {
206
+ if (error) {
207
+ console.warn(`Failed to open browser: ${error.message}`);
208
+ }
209
+ });
210
+ }, 2000); // Wait 2 seconds for server to be ready
211
+ }
212
+ });
213
+ }
214
+ };
215
+
216
+ if (!config.plugins) {
217
+ config.plugins = [];
218
+ }
219
+ config.plugins.push(new DevStartupPlugin());
220
+ }
221
+
156
222
  // Dev mode optimizations
157
223
  if (dev) {
158
224
  config.optimization = {
@@ -226,6 +292,8 @@ export function createBaseNextConfig(
226
292
  // Cleanup: Remove our custom options that are not part of NextConfig
227
293
  // These are internal to BaseNextConfigOptions and should not be in the final config
228
294
  delete (finalConfig as any).optimizePackageImports;
295
+ delete (finalConfig as any).isDefaultCfgAdmin;
296
+ delete (finalConfig as any).openBrowser;
229
297
  // Note: We don't delete transpilePackages, experimental, env, webpack
230
298
  // as they are valid NextConfig keys and may have been overridden by user
231
299
 
@@ -4,6 +4,8 @@
4
4
  * Server-side function to submit contact form data to backend API.
5
5
  * Can be used in Next.js API routes to avoid CORS issues.
6
6
  *
7
+ * Uses Fetchers with server-side API instance for type safety and Zod validation.
8
+ *
7
9
  * @example
8
10
  * ```ts
9
11
  * import { submitContactForm } from '@djangocfg/nextjs/contact';
@@ -17,8 +19,9 @@
17
19
  * ```
18
20
  */
19
21
 
20
- import { API, MemoryStorageAdapter, Fetchers } from '@djangocfg/api';
21
- import type { Schemas } from '@djangocfg/api';
22
+ // Use server-only exports to avoid loading React hooks (useSWRConfig)
23
+ import { API, MemoryStorageAdapter, Fetchers } from '@djangocfg/api/server';
24
+ import type { Schemas } from '@djangocfg/api/server';
22
25
 
23
26
  export interface SubmitContactFormOptions {
24
27
  /** Lead submission data */
@@ -38,8 +41,8 @@ export interface SubmitContactFormResult {
38
41
  /**
39
42
  * Submit contact form data to backend API
40
43
  *
41
- * Server-side function that uses the typed fetcher for type safety
42
- * and runtime validation via Zod schemas.
44
+ * Server-side function that uses Fetchers with server-side API instance.
45
+ * This provides type safety, Zod validation, and proper error handling.
43
46
  */
44
47
  export async function submitContactForm({
45
48
  data,
@@ -56,7 +59,7 @@ export async function submitContactForm({
56
59
  }
57
60
 
58
61
  // Create server-side API instance with MemoryStorageAdapter
59
- // This works on the server because MemoryStorageAdapter is not client-only
62
+ // This works on server-side and doesn't require browser APIs
60
63
  const serverApi = new API(apiUrl, {
61
64
  storage: new MemoryStorageAdapter(),
62
65
  });
@@ -67,14 +70,24 @@ export async function submitContactForm({
67
70
  site_url: data.site_url || siteUrl,
68
71
  };
69
72
 
70
- // Use typed fetcher with server API instance
71
- // This provides type safety and runtime validation via Zod
72
- const result = await Fetchers.createLeadsSubmitCreate(submissionData, serverApi);
73
+ try {
74
+ // Use typed fetcher with server API instance
75
+ // This provides type safety and runtime validation via Zod
76
+ // Using Fetchers namespace to avoid loading hooks
77
+ const result = await Fetchers.createLeadsSubmitCreate(submissionData, serverApi);
73
78
 
74
- return {
75
- success: result.success,
76
- message: result.message,
77
- lead_id: result.lead_id,
78
- };
79
+ // Return formatted result
80
+ return {
81
+ success: result.success ?? true,
82
+ message: result.message || 'Contact form submitted successfully',
83
+ lead_id: result.lead_id,
84
+ };
85
+ } catch (error) {
86
+ // Handle API errors (including validation errors from Zod)
87
+ if (error instanceof Error) {
88
+ throw new Error(`Failed to submit contact form: ${error.message}`);
89
+ }
90
+ throw new Error('An unexpected error occurred while submitting the contact form');
91
+ }
79
92
  }
80
93
 
package/src/index.ts CHANGED
@@ -13,15 +13,6 @@ export * from './health';
13
13
  // OG Image
14
14
  export * from './og-image';
15
15
 
16
- // Legal pages
17
- export * from './legal';
18
-
19
- // Error pages
20
- export * from './errors';
21
-
22
- // Components
23
- export * from './components';
24
-
25
16
  // Contact form
26
17
  export * from './contact';
27
18