@djangocfg/nextjs 1.0.6 → 2.1.1

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.
@@ -1,303 +0,0 @@
1
- /**
2
- * Base Next.js Configuration Factory
3
- *
4
- * Universal, reusable Next.js config for all DjangoCFG projects
5
- * Provides standard settings that can be extended per project
6
- *
7
- * @example
8
- * ```typescript
9
- * // In your project's next.config.ts
10
- * import { createBaseNextConfig } from '@djangocfg/nextjs/config';
11
- * import bundleAnalyzer from '@next/bundle-analyzer';
12
- *
13
- * const withBundleAnalyzer = bundleAnalyzer({
14
- * enabled: process.env.ANALYZE === 'true',
15
- * });
16
- *
17
- * export default withBundleAnalyzer(createBaseNextConfig({
18
- * // Your project-specific overrides
19
- * transpilePackages: ['my-custom-package'],
20
- * }));
21
- * ```
22
- */
23
-
24
- import type { NextConfig } from 'next';
25
- import type { Configuration as WebpackConfig } from 'webpack';
26
- import chalk from 'chalk';
27
- import { deepMerge } from './deepMerge';
28
-
29
- // ─────────────────────────────────────────────────────────────────────────
30
- // Standard Environment Variables
31
- // ─────────────────────────────────────────────────────────────────────────
32
-
33
- const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
34
- const isDev = process.env.NODE_ENV === 'development';
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
-
55
- // ─────────────────────────────────────────────────────────────────────────
56
- // Configuration Options
57
- // ─────────────────────────────────────────────────────────────────────────
58
-
59
- export interface BaseNextConfigOptions {
60
- /** Static build path (used when NEXT_PUBLIC_STATIC_BUILD=true) */
61
- isDefaultCfgAdmin?: boolean;
62
- /** Additional transpile packages (merged with defaults) */
63
- transpilePackages?: string[];
64
- /** Additional optimize package imports (merged with defaults) */
65
- optimizePackageImports?: string[];
66
- /** Automatically open browser in dev mode (default: false) */
67
- openBrowser?: boolean;
68
- /** Custom webpack configuration function (called after base webpack logic) */
69
- webpack?: (
70
- config: WebpackConfig,
71
- options: { isServer: boolean; dev: boolean;[key: string]: any }
72
- ) => WebpackConfig | void;
73
- /** Custom experimental options (merged with defaults) */
74
- experimental?: NextConfig['experimental'];
75
- /** Custom env variables (merged with defaults) */
76
- env?: Record<string, string | undefined>;
77
- /** Override any Next.js config option */
78
- [key: string]: any;
79
- }
80
-
81
- // ─────────────────────────────────────────────────────────────────────────
82
- // Base Configuration Factory
83
- // ─────────────────────────────────────────────────────────────────────────
84
-
85
- /**
86
- * Create base Next.js configuration with standard DjangoCFG settings
87
- *
88
- * @param options - Custom configuration options to merge with base config
89
- * @returns Next.js configuration function
90
- */
91
- export function createBaseNextConfig(
92
- options: BaseNextConfigOptions = {}
93
- ): NextConfig {
94
-
95
- // If static build not provided, use default for nextjs-admin development
96
- const staticBuildPath = options.isDefaultCfgAdmin ? '/cfg/admin' : '/cfg/nextjs-admin';
97
- const basePath = isStaticBuild ? staticBuildPath : '';
98
- const apiUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_API_URL || '';
99
- const siteUrl = isStaticBuild ? '' : process.env.NEXT_PUBLIC_SITE_URL || '';
100
-
101
- // Base configuration
102
- const baseConfig: NextConfig = {
103
- reactStrictMode: true,
104
- trailingSlash: true,
105
-
106
- // Static export configuration
107
- ...(isStaticBuild && {
108
- output: 'export' as const,
109
- distDir: 'out',
110
- basePath: basePath,
111
- // assetPrefix: basePath,
112
- // Fix for Next.js 15.5.4: prevent 500.html generation issue
113
- //
114
- // PROBLEM: In App Router, error.tsx is a client component ('use client')
115
- // and cannot be statically exported as 500.html. Next.js tries to create
116
- // and move 500.html during static export, causing ENOENT error.
117
- //
118
- // SOLUTION: Use generateBuildId to work around the issue.
119
- // The error.tsx component will still work in development/runtime mode.
120
- generateBuildId: async () => {
121
- return 'static-build';
122
- },
123
- }),
124
-
125
- // Standalone output for Docker (only in production, not dev)
126
- ...(!isStaticBuild && !isDev && {
127
- output: 'standalone' as const,
128
- }),
129
-
130
- // Environment variables
131
- env: {
132
- NEXT_PUBLIC_BASE_PATH: basePath,
133
- NEXT_PUBLIC_API_URL: apiUrl,
134
- NEXT_PUBLIC_SITE_URL: siteUrl,
135
- ...options.env,
136
- },
137
-
138
- // Images configuration
139
- images: {
140
- unoptimized: true,
141
- },
142
-
143
- // Transpile packages (merge with user-provided)
144
- transpilePackages: [
145
- '@djangocfg/ui',
146
- '@djangocfg/layouts',
147
- '@djangocfg/nextjs',
148
- '@djangocfg/api',
149
- '@djangocfg/centrifugo',
150
- ...(options.transpilePackages || []),
151
- ],
152
-
153
- // Experimental features
154
- // Base optimizations first, then user options (user can override base settings)
155
- experimental: {
156
- // Optimize package imports (only in production)
157
- ...(!isDev && {
158
- optimizePackageImports: [
159
- '@djangocfg/ui',
160
- '@djangocfg/layouts',
161
- 'lucide-react',
162
- 'recharts',
163
- ...(options.optimizePackageImports || []),
164
- ],
165
- }),
166
- // Dev mode optimizations
167
- ...(isDev && {
168
- optimizeCss: false,
169
- }),
170
- // User experimental options applied last (can override base settings)
171
- ...options.experimental,
172
- },
173
-
174
- // Webpack configuration
175
- webpack: (config: WebpackConfig, webpackOptions: { isServer: boolean; dev: boolean;[key: string]: any }) => {
176
- const { isServer, dev } = webpackOptions;
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
-
222
- // Dev mode optimizations
223
- if (dev) {
224
- config.optimization = {
225
- ...config.optimization,
226
- removeAvailableModules: false,
227
- removeEmptyChunks: false,
228
- splitChunks: false, // Disable code splitting in dev for faster compilation
229
- };
230
- }
231
-
232
- // Filesystem cache (dev and production)
233
- config.cache = {
234
- type: 'filesystem',
235
- buildDependencies: {},
236
- };
237
-
238
- // Note: Dynamic API routes should use `export const dynamic = 'force-dynamic'`
239
- // in the route file itself to exclude them from static export.
240
- // This is simpler than using webpack plugins.
241
-
242
- // Compression plugins (only for static build in production)
243
- // Note: compression-webpack-plugin should be installed in the consuming project
244
- if (!isServer && isStaticBuild && !dev) {
245
- try {
246
- // Dynamic import to avoid bundling compression-webpack-plugin
247
- const CompressionPlugin = require('compression-webpack-plugin');
248
-
249
- // Gzip compression
250
- config.plugins.push(
251
- new CompressionPlugin({
252
- filename: '[path][base].gz',
253
- algorithm: 'gzip',
254
- test: /\.(js|css|html|svg|json)$/,
255
- threshold: 8192,
256
- minRatio: 0.8,
257
- })
258
- );
259
-
260
- // Brotli compression (balanced level for speed/size)
261
- config.plugins.push(
262
- new CompressionPlugin({
263
- filename: '[path][base].br',
264
- algorithm: 'brotliCompress',
265
- test: /\.(js|css|html|svg|json)$/,
266
- threshold: 8192,
267
- minRatio: 0.8,
268
- compressionOptions: {
269
- level: 8, // Balanced: good compression without excessive build time
270
- },
271
- })
272
- );
273
- } catch (error) {
274
- // compression-webpack-plugin not installed, skip compression
275
- console.warn('compression-webpack-plugin not found, skipping compression');
276
- }
277
- }
278
-
279
- // Call user's webpack function if provided
280
- if (options.webpack) {
281
- return options.webpack(config, webpackOptions);
282
- }
283
-
284
- return config;
285
- },
286
-
287
- };
288
-
289
- // Deep merge user options with base config
290
- const finalConfig = deepMerge(baseConfig, options);
291
-
292
- // Cleanup: Remove our custom options that are not part of NextConfig
293
- // These are internal to BaseNextConfigOptions and should not be in the final config
294
- delete (finalConfig as any).optimizePackageImports;
295
- delete (finalConfig as any).isDefaultCfgAdmin;
296
- delete (finalConfig as any).openBrowser;
297
- // Note: We don't delete transpilePackages, experimental, env, webpack
298
- // as they are valid NextConfig keys and may have been overridden by user
299
-
300
- // Return clean NextConfig object (user should wrap with withBundleAnalyzer in their next.config.ts)
301
- return finalConfig;
302
- }
303
-
@@ -1,33 +0,0 @@
1
-
2
- // ─────────────────────────────────────────────────────────────────────────
3
- // Deep Merge Helper (simple implementation)
4
- // ─────────────────────────────────────────────────────────────────────────
5
-
6
- export function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
7
- const output = { ...target };
8
-
9
- for (const key in source) {
10
- if (source[key] === undefined) continue;
11
-
12
- // Arrays: replace (don't merge arrays)
13
- if (Array.isArray(source[key])) {
14
- output[key] = source[key] as any;
15
- }
16
- // Objects: deep merge
17
- else if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
18
- const targetValue = output[key];
19
- if (targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
20
- output[key] = deepMerge(targetValue, source[key] as any);
21
- } else {
22
- output[key] = source[key] as any;
23
- }
24
- }
25
- // Primitives: replace
26
- else {
27
- output[key] = source[key] as any;
28
- }
29
- }
30
-
31
- return output;
32
- }
33
-