next-build-filter 0.1.0

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 ADDED
@@ -0,0 +1,357 @@
1
+ # Next.js Build Filter Plugin
2
+
3
+ A powerful Next.js plugin that allows you to exclude specific pages/routes from the build process without removing the files from your project. **Supports both Next.js Pages Router and App Router (Next.js 13+)**. This is perfect for speeding up development builds, creating different build configurations, or excluding admin/debug pages from production builds.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Speed up builds** by excluding unnecessary pages/routes
8
+ - 📱 **App Router Support** - Full Next.js 13+ App Router compatibility
9
+ - 📄 **Pages Router Support** - Traditional Pages Router support
10
+ - 🎯 **Flexible filtering** with multiple configuration options
11
+ - 🔄 **Non-destructive** - files remain in your codebase
12
+ - 🌍 **Environment-aware** - different configurations for dev/prod
13
+ - 📝 **Verbose logging** to see what's being filtered
14
+ - 🎨 **Pattern matching** support with regex
15
+ - 🔧 **TypeScript support** with full type definitions
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install next-build-filter
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### For Pages Router (Traditional Next.js)
26
+
27
+ ```javascript
28
+ // next.config.js
29
+ const withPageFilter = require('next-build-filter');
30
+
31
+ const filterConfig = {
32
+ enabled: process.env.FILTER_PAGES === 'true',
33
+ verbose: true,
34
+ supportPagesRouter: true,
35
+ supportAppRouter: false,
36
+ excludedPages: [
37
+ 'admin',
38
+ 'dev/debug',
39
+ ],
40
+ };
41
+
42
+ module.exports = withPageFilter(filterConfig)({
43
+ reactStrictMode: true,
44
+ });
45
+ ```
46
+
47
+ ### For App Router (Next.js 13+)
48
+
49
+ ```javascript
50
+ // next.config.js
51
+ const withPageFilter = require('next-build-filter');
52
+
53
+ const filterConfig = {
54
+ enabled: process.env.FILTER_PAGES === 'true',
55
+ verbose: true,
56
+ supportAppRouter: true,
57
+ supportPagesRouter: false,
58
+ excludedPages: [
59
+ 'admin', // Excludes app/admin/page.tsx
60
+ 'dev/debug', // Excludes app/dev/debug/page.tsx
61
+ ],
62
+ };
63
+
64
+ module.exports = withPageFilter(filterConfig)({
65
+ reactStrictMode: true,
66
+ experimental: {
67
+ appDir: true,
68
+ },
69
+ });
70
+ ```
71
+
72
+ ### For Mixed Router Support (Both App Router and Pages Router)
73
+
74
+ ```javascript
75
+ // next.config.js
76
+ const withPageFilter = require('next-build-filter');
77
+
78
+ const filterConfig = {
79
+ enabled: process.env.FILTER_PAGES === 'true',
80
+ verbose: true,
81
+ supportAppRouter: true,
82
+ supportPagesRouter: true,
83
+ excludedPages: [
84
+ 'admin', // Excludes both pages/admin.js and app/admin/page.tsx
85
+ 'dev/debug', // Excludes both pages/dev/debug.js and app/dev/debug/page.tsx
86
+ ],
87
+ };
88
+
89
+ module.exports = withPageFilter(filterConfig)({
90
+ reactStrictMode: true,
91
+ experimental: {
92
+ appDir: true,
93
+ },
94
+ });
95
+ ```
96
+
97
+ 3. **Run filtered builds:**
98
+ ```bash
99
+ # Normal build (all pages included)
100
+ npm run build
101
+
102
+ # Filtered build (excludes configured pages)
103
+ FILTER_PAGES=true npm run build
104
+
105
+ # Or use the predefined script
106
+ npm run build:filtered
107
+ ```
108
+
109
+ ## Configuration Options
110
+
111
+ ### Basic Options
112
+
113
+ | Option | Type | Default | Description |
114
+ |--------|------|---------|-------------|
115
+ | `enabled` | boolean | `process.env.FILTER_PAGES === 'true'` | Enable/disable page filtering |
116
+ | `verbose` | boolean | `false` | Show detailed logging of filtered pages |
117
+ | `enableInDev` | boolean | `false` | Apply filtering in development mode |
118
+
119
+ ### Router Support Options
120
+
121
+ | Option | Type | Default | Description |
122
+ |--------|------|---------|-------------|
123
+ | `supportAppRouter` | boolean | `true` | Enable filtering for App Router (Next.js 13+) |
124
+ | `supportPagesRouter` | boolean | `true` | Enable filtering for Pages Router |
125
+ | `appDir` | string | `'app'` | App Router directory name |
126
+ | `pagesDir` | string | `'pages'` | Pages Router directory name |
127
+
128
+ ### Filtering Options
129
+
130
+ #### 1. Include Only Specific Pages
131
+ ```javascript
132
+ const filterConfig = {
133
+ enabled: true,
134
+ includedPages: [
135
+ 'index', // /
136
+ 'about', // /about
137
+ 'contact', // /contact
138
+ ],
139
+ };
140
+ ```
141
+
142
+ #### 2. Exclude Specific Pages
143
+ ```javascript
144
+ const filterConfig = {
145
+ enabled: true,
146
+ excludedPages: [
147
+ 'admin', // /admin
148
+ 'dev/debug', // /dev/debug
149
+ 'api/internal', // /api/internal
150
+ ],
151
+ };
152
+ ```
153
+
154
+ #### 3. Pattern-Based Exclusion
155
+ ```javascript
156
+ const filterConfig = {
157
+ enabled: true,
158
+ excludePatterns: [
159
+ 'dev/.*', // All pages in /dev/ directory
160
+ '.*admin.*', // Any page with 'admin' in the path
161
+ '.*test.*', // Any page with 'test' in the path
162
+ ],
163
+ };
164
+ ```
165
+
166
+ ## Usage Examples
167
+
168
+ ### Development Speed Build
169
+ Perfect for large projects where you only need a few pages during development:
170
+
171
+ ```javascript
172
+ // next.config.js
173
+ const withPageFilter = require('./plugins/withPageFilter');
174
+
175
+ const filterConfig = {
176
+ enabled: process.env.NODE_ENV === 'development',
177
+ verbose: true,
178
+ includedPages: [
179
+ 'index',
180
+ 'dashboard',
181
+ 'profile',
182
+ ],
183
+ };
184
+
185
+ module.exports = withPageFilter(filterConfig)({
186
+ reactStrictMode: true,
187
+ });
188
+ ```
189
+
190
+ ### Production Admin Exclusion
191
+ Exclude admin and debug pages from production builds:
192
+
193
+ ```javascript
194
+ const filterConfig = {
195
+ enabled: process.env.NODE_ENV === 'production',
196
+ excludedPages: [
197
+ 'admin',
198
+ 'debug',
199
+ 'dev/tools',
200
+ ],
201
+ excludePatterns: [
202
+ 'admin/.*',
203
+ 'dev/.*',
204
+ ],
205
+ };
206
+ ```
207
+
208
+ ### Environment Variable Control
209
+ Use environment variables for flexible configuration:
210
+
211
+ ```javascript
212
+ const filterConfig = {
213
+ enabled: process.env.FILTER_PAGES === 'true',
214
+ verbose: process.env.NODE_ENV === 'development',
215
+ excludedPages: process.env.EXCLUDED_PAGES ?
216
+ process.env.EXCLUDED_PAGES.split(',') : [],
217
+ includedPages: process.env.INCLUDED_PAGES ?
218
+ process.env.INCLUDED_PAGES.split(',') : [],
219
+ };
220
+ ```
221
+
222
+ Then use it:
223
+ ```bash
224
+ # Include only specific pages
225
+ FILTER_PAGES=true INCLUDED_PAGES=index,about,contact npm run build
226
+
227
+ # Exclude specific pages
228
+ FILTER_PAGES=true EXCLUDED_PAGES=admin,debug npm run build
229
+ ```
230
+
231
+ ## Available Scripts
232
+
233
+ The project includes several npm scripts for different build scenarios:
234
+
235
+ ```bash
236
+ # Development server
237
+ npm run dev
238
+
239
+ # Normal build (all pages)
240
+ npm run build
241
+
242
+ # Filtered build (respects FILTER_PAGES environment variable)
243
+ npm run build:filtered
244
+
245
+ # Start production server
246
+ npm start
247
+ ```
248
+
249
+ ## How It Works
250
+
251
+ The plugin works by integrating with Next.js's webpack configuration and build process:
252
+
253
+ 1. **Webpack Plugin Integration**: The plugin hooks into webpack's module resolution process
254
+ 2. **Page Detection**: It identifies page files in the `/pages` directory
255
+ 3. **Filtering Logic**: Based on your configuration, it determines which pages to exclude
256
+ 4. **Module Resolution**: Excluded pages are replaced with empty modules during the build process
257
+ 5. **Build Optimization**: The final bundle only includes the pages you want
258
+
259
+ ## Project Structure
260
+
261
+ ```
262
+ next-build-filter/ # Main plugin package
263
+ ├── lib/ # Plugin source code
264
+ │ ├── with-page-filter.js # Main plugin wrapper
265
+ │ ├── next-build-filter-plugin.js # Webpack plugin
266
+ │ ├── advanced-next-build-filter-plugin.js # Advanced filtering
267
+ │ └── empty-module.js # Replacement for filtered pages
268
+ ├── demo/ # Demo projects
269
+ │ ├── pages-router-demo/ # Pages Router demo
270
+ │ │ ├── pages/ # Traditional Next.js pages
271
+ │ │ │ ├── index.js # Home page
272
+ │ │ │ ├── about.js # About page
273
+ │ │ │ ├── admin.js # Admin page (filtered)
274
+ │ │ │ └── dev/debug.js # Debug page (filtered)
275
+ │ │ ├── next.config.js # Pages Router configuration
276
+ │ │ └── package.json # Demo dependencies
277
+ │ └── app-router-demo/ # App Router demo (Next.js 13+)
278
+ │ ├── app/ # App Router structure
279
+ │ │ ├── page.tsx # Home route
280
+ │ │ ├── layout.tsx # Root layout
281
+ │ │ ├── about/page.tsx # About route
282
+ │ │ ├── admin/page.tsx # Admin route (filtered)
283
+ │ │ └── dev/debug/page.tsx # Debug route (filtered)
284
+ │ ├── next.config.js # App Router configuration
285
+ │ └── package.json # Demo dependencies
286
+ ├── index.js # Main entry point
287
+ ├── index.d.ts # TypeScript definitions
288
+ ├── package.json # Plugin package configuration
289
+ └── README.md # This file
290
+ ```
291
+
292
+ ## Demo Projects
293
+
294
+ This package includes two complete demo projects to showcase the filtering capabilities:
295
+
296
+ ### Pages Router Demo
297
+ ```bash
298
+ cd demo/pages-router-demo
299
+ npm install
300
+ npm run build:filtered
301
+ ```
302
+
303
+ ### App Router Demo
304
+ ```bash
305
+ cd demo/app-router-demo
306
+ npm install
307
+ npm run build:filtered
308
+ ```
309
+
310
+ ## Real-World Use Cases
311
+
312
+ ### 1. Large E-commerce Sites
313
+ - Include only product pages during catalog development
314
+ - Exclude admin pages from customer-facing builds
315
+
316
+ ### 2. Multi-tenant Applications
317
+ - Build tenant-specific versions with only relevant pages
318
+ - Exclude unused features per tenant
319
+
320
+ ### 3. Development Teams
321
+ - Speed up local development by including only pages you're working on
322
+ - Create lightweight builds for testing specific features
323
+
324
+ ### 4. Staging Environments
325
+ - Create builds with debug pages for staging
326
+ - Exclude debug pages from production
327
+
328
+ ## Tips and Best Practices
329
+
330
+ 1. **Start Small**: Begin by excluding just a few pages and gradually expand
331
+ 2. **Use Verbose Mode**: Enable verbose logging during development to see what's being filtered
332
+ 3. **Environment-Specific**: Use different configurations for different environments
333
+ 4. **Test Thoroughly**: Always test your filtered builds to ensure functionality
334
+ 5. **Document Configuration**: Keep your filtering logic well-documented for your team
335
+
336
+ ## Troubleshooting
337
+
338
+ ### Pages Still Appearing in Build
339
+ - Check that `enabled: true` is set in your configuration
340
+ - Verify the page paths match exactly (case-sensitive)
341
+ - Enable verbose logging to see what's being processed
342
+
343
+ ### Build Errors
344
+ - Ensure all required pages (like `_app.js`, `_document.js`) are not being filtered
345
+ - Check that your regex patterns are valid if using `excludePatterns`
346
+
347
+ ### Development vs Production Differences
348
+ - Set `enableInDev: true` if you want consistent behavior across environments
349
+ - Use environment variables to control filtering per environment
350
+
351
+ ## Contributing
352
+
353
+ Feel free to submit issues, feature requests, or pull requests to improve this plugin.
354
+
355
+ ## License
356
+
357
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ declare module 'next-build-filter' {
2
+ import { NextConfig } from 'next';
3
+
4
+ interface FilterOptions {
5
+ /** Enable/disable page filtering */
6
+ enabled?: boolean;
7
+
8
+ /** Show detailed logging of filtered pages */
9
+ verbose?: boolean;
10
+
11
+ /** Apply filtering in development mode */
12
+ enableInDev?: boolean;
13
+
14
+ /** Pages to include (if specified, only these will be built) */
15
+ includedPages?: string[];
16
+
17
+ /** Pages to exclude from build */
18
+ excludedPages?: string[];
19
+
20
+ /** Regex patterns for page exclusion */
21
+ excludePatterns?: string[];
22
+
23
+ /** Pages directory name (default: 'pages') */
24
+ pagesDir?: string;
25
+
26
+ /** App directory name (default: 'app') */
27
+ appDir?: string;
28
+
29
+ /** Support App Router (Next.js 13+) */
30
+ supportAppRouter?: boolean;
31
+
32
+ /** Support Pages Router */
33
+ supportPagesRouter?: boolean;
34
+ }
35
+
36
+ type NextConfigFunction = (nextConfig?: NextConfig) => NextConfig;
37
+
38
+ function withPageFilter(options?: FilterOptions): NextConfigFunction;
39
+
40
+ export = withPageFilter;
41
+ export { FilterOptions, NextConfigFunction };
42
+ }
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ const NextBuildFilterPlugin = require('./lib/next-build-filter-plugin');
2
+ const withPageFilter = require('./lib/with-page-filter');
3
+
4
+ module.exports = withPageFilter;
5
+ module.exports.NextBuildFilterPlugin = NextBuildFilterPlugin;
6
+ module.exports.withPageFilter = withPageFilter;
@@ -0,0 +1,204 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * Advanced Next.js Build Filter Plugin
6
+ *
7
+ * This version integrates more deeply with Next.js's build process
8
+ * to provide better filtering capabilities.
9
+ */
10
+ class AdvancedNextBuildFilterPlugin {
11
+ constructor(options = {}) {
12
+ this.options = {
13
+ includedPages: options.includedPages || [],
14
+ excludedPages: options.excludedPages || [],
15
+ enabled: options.enabled !== undefined ? options.enabled : process.env.FILTER_PAGES === 'true',
16
+ excludePatterns: options.excludePatterns || [],
17
+ verbose: options.verbose || false,
18
+ pagesDir: options.pagesDir || 'pages',
19
+ ...options
20
+ };
21
+
22
+ this.filteredPages = new Set();
23
+ }
24
+
25
+ apply(compiler) {
26
+ if (!this.options.enabled) {
27
+ if (this.options.verbose) {
28
+ console.log('📄 AdvancedNextBuildFilterPlugin: Filtering disabled');
29
+ }
30
+ return;
31
+ }
32
+
33
+ const pluginName = 'AdvancedNextBuildFilterPlugin';
34
+
35
+ // Hook into the entry option to filter pages at the entry level
36
+ compiler.hooks.entryOption.tap(pluginName, (context, entry) => {
37
+ if (typeof entry === 'object' && !Array.isArray(entry)) {
38
+ this.filterEntries(entry);
39
+ }
40
+ });
41
+
42
+ // Hook into the compilation to filter modules
43
+ compiler.hooks.compilation.tap(pluginName, (compilation) => {
44
+ compilation.hooks.buildModule.tap(pluginName, (module) => {
45
+ if (module.resource && this.shouldFilterModule(module.resource)) {
46
+ if (this.options.verbose) {
47
+ console.log(`📄 Filtering module: ${module.resource}`);
48
+ }
49
+ // Mark the module as filtered
50
+ module._filtered = true;
51
+ }
52
+ });
53
+ });
54
+
55
+ // Hook into normal module factory to intercept page imports
56
+ compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
57
+ nmf.hooks.beforeResolve.tap(pluginName, (resolveData) => {
58
+ if (!resolveData) return;
59
+
60
+ const request = resolveData.request;
61
+ const context = resolveData.context;
62
+
63
+ if (this.isPageModule(request, context) && this.shouldFilterPage(request)) {
64
+ if (this.options.verbose) {
65
+ console.log(`📄 Intercepting page request: ${request}`);
66
+ }
67
+
68
+ // Replace with empty module
69
+ resolveData.request = path.resolve(__dirname, 'empty-module.js');
70
+ this.filteredPages.add(request);
71
+ }
72
+
73
+ return resolveData;
74
+ });
75
+ });
76
+
77
+ // Log summary after compilation
78
+ compiler.hooks.done.tap(pluginName, () => {
79
+ if (this.options.verbose && this.filteredPages.size > 0) {
80
+ console.log(`📄 Filtered ${this.filteredPages.size} pages:`);
81
+ this.filteredPages.forEach(page => {
82
+ console.log(` - ${page}`);
83
+ });
84
+ }
85
+ });
86
+ }
87
+
88
+ filterEntries(entry) {
89
+ const keysToRemove = [];
90
+
91
+ for (const [key, value] of Object.entries(entry)) {
92
+ if (this.isPageEntry(key) && this.shouldFilterPageEntry(key)) {
93
+ keysToRemove.push(key);
94
+ if (this.options.verbose) {
95
+ console.log(`📄 Filtering entry: ${key}`);
96
+ }
97
+ }
98
+ }
99
+
100
+ // Remove filtered entries
101
+ keysToRemove.forEach(key => {
102
+ delete entry[key];
103
+ this.filteredPages.add(key);
104
+ });
105
+ }
106
+
107
+ isPageEntry(entryKey) {
108
+ // Next.js page entries typically start with 'pages/'
109
+ return entryKey.startsWith('pages/') || entryKey.startsWith('static/chunks/pages/');
110
+ }
111
+
112
+ isPageModule(request, context) {
113
+ if (!request || !context) return false;
114
+
115
+ // Check if it's in the pages directory
116
+ const isInPages = request.includes(`/${this.options.pagesDir}/`) ||
117
+ context.includes(`/${this.options.pagesDir}/`);
118
+
119
+ // Check for page-like file extensions
120
+ const pageExtensions = ['.js', '.jsx', '.ts', '.tsx'];
121
+ const hasPageExtension = pageExtensions.some(ext => request.endsWith(ext));
122
+
123
+ // Exclude API routes and special Next.js files
124
+ const isApiRoute = request.includes('/api/');
125
+ const isSpecialFile = ['_app', '_document', '_error', '404', '500'].some(special =>
126
+ request.includes(special)
127
+ );
128
+
129
+ return isInPages && hasPageExtension && !isApiRoute && !isSpecialFile;
130
+ }
131
+
132
+ shouldFilterModule(resourcePath) {
133
+ if (!resourcePath) return false;
134
+
135
+ const normalizedPath = this.normalizePath(resourcePath);
136
+
137
+ // Extract page path from full resource path
138
+ const pagesIndex = normalizedPath.indexOf(`/${this.options.pagesDir}/`);
139
+ if (pagesIndex === -1) return false;
140
+
141
+ const pagePath = normalizedPath.substring(pagesIndex + `/${this.options.pagesDir}/`.length);
142
+ return this.shouldFilterPage(pagePath);
143
+ }
144
+
145
+ shouldFilterPage(pagePath) {
146
+ const normalizedPath = this.normalizePath(pagePath);
147
+
148
+ // Remove file extension for comparison
149
+ const pathWithoutExt = normalizedPath.replace(/\.(js|jsx|ts|tsx)$/, '');
150
+
151
+ // If includedPages is specified, only include those
152
+ if (this.options.includedPages.length > 0) {
153
+ const isIncluded = this.options.includedPages.some(page => {
154
+ const normalizedPage = this.normalizePath(page);
155
+ return pathWithoutExt === normalizedPage || pathWithoutExt.endsWith(`/${normalizedPage}`);
156
+ });
157
+ return !isIncluded;
158
+ }
159
+
160
+ // Check excluded pages
161
+ if (this.options.excludedPages.length > 0) {
162
+ const isExcluded = this.options.excludedPages.some(page => {
163
+ const normalizedPage = this.normalizePath(page);
164
+ return pathWithoutExt === normalizedPage || pathWithoutExt.endsWith(`/${normalizedPage}`);
165
+ });
166
+ if (isExcluded) return true;
167
+ }
168
+
169
+ // Check exclude patterns
170
+ if (this.options.excludePatterns.length > 0) {
171
+ const isPatternExcluded = this.options.excludePatterns.some(pattern => {
172
+ try {
173
+ const regex = new RegExp(pattern);
174
+ return regex.test(pathWithoutExt);
175
+ } catch (e) {
176
+ console.warn(`📄 Invalid regex pattern: ${pattern}`);
177
+ return false;
178
+ }
179
+ });
180
+ if (isPatternExcluded) return true;
181
+ }
182
+
183
+ return false;
184
+ }
185
+
186
+ shouldFilterPageEntry(entryKey) {
187
+ // Extract page path from entry key
188
+ let pagePath = entryKey;
189
+
190
+ if (entryKey.startsWith('pages/')) {
191
+ pagePath = entryKey.substring(6); // Remove 'pages/' prefix
192
+ } else if (entryKey.startsWith('static/chunks/pages/')) {
193
+ pagePath = entryKey.substring(20); // Remove 'static/chunks/pages/' prefix
194
+ }
195
+
196
+ return this.shouldFilterPage(pagePath);
197
+ }
198
+
199
+ normalizePath(filePath) {
200
+ return filePath.replace(/\\/g, '/').toLowerCase();
201
+ }
202
+ }
203
+
204
+ module.exports = AdvancedNextBuildFilterPlugin;
@@ -0,0 +1,29 @@
1
+ // Empty module to replace filtered pages
2
+ // This module provides a default export that's compatible with both Pages Router and App Router
3
+
4
+ function EmptyPage() {
5
+ return null;
6
+ }
7
+
8
+ // For React components (Pages Router)
9
+ module.exports = EmptyPage;
10
+ module.exports.default = EmptyPage;
11
+
12
+ // For App Router (ES6 export)
13
+ if (typeof exports !== 'undefined') {
14
+ exports.default = EmptyPage;
15
+ }
16
+
17
+ // Generate metadata for App Router if needed
18
+ module.exports.generateMetadata = () => ({
19
+ title: 'Page Not Available',
20
+ description: 'This page has been filtered out during build'
21
+ });
22
+
23
+ // Generate static params for dynamic routes if needed
24
+ module.exports.generateStaticParams = () => [];
25
+
26
+ // Export all common App Router functions as empty
27
+ module.exports.generateViewport = () => ({});
28
+ module.exports.generateStaticParams = () => [];
29
+ module.exports.revalidate = false;
@@ -0,0 +1,272 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * Next.js Build Filter Plugin
6
+ *
7
+ * This plugin allows you to exclude specific pages from the build process
8
+ * without actually removing the files from your project.
9
+ */
10
+ class NextBuildFilterPlugin {
11
+ constructor(options = {}) {
12
+ this.options = {
13
+ // Pages to include (if specified, only these pages will be built)
14
+ includedPages: options.includedPages || [],
15
+
16
+ // Pages to exclude (these pages will be skipped during build)
17
+ excludedPages: options.excludedPages || [],
18
+
19
+ // Whether to enable filtering (can be controlled by environment variable)
20
+ enabled: options.enabled !== undefined ? options.enabled : process.env.FILTER_PAGES === 'true',
21
+
22
+ // Pattern matching for page exclusion
23
+ excludePatterns: options.excludePatterns || [],
24
+
25
+ // Verbose logging
26
+ verbose: options.verbose || false,
27
+
28
+ // Directory configurations
29
+ pagesDir: options.pagesDir || 'pages',
30
+ appDir: options.appDir || 'app',
31
+
32
+ // Router support
33
+ supportAppRouter: options.supportAppRouter !== false,
34
+ supportPagesRouter: options.supportPagesRouter !== false
35
+ };
36
+ }
37
+
38
+ apply(compiler) {
39
+ if (!this.options.enabled) {
40
+ if (this.options.verbose) {
41
+ console.log('📄 NextBuildFilterPlugin: Filtering disabled');
42
+ }
43
+ return;
44
+ }
45
+
46
+ const pluginName = 'NextBuildFilterPlugin';
47
+
48
+ // Hook into the compilation process
49
+ compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
50
+ if (this.options.verbose) {
51
+ console.log('📄 NextBuildFilterPlugin: Starting page filtering...');
52
+ }
53
+ callback();
54
+ });
55
+
56
+ // Filter out pages during the resolve phase
57
+ compiler.hooks.normalModuleFactory.tap(pluginName, (normalModuleFactory) => {
58
+ normalModuleFactory.hooks.beforeResolve.tap(pluginName, (resolveData) => {
59
+ if (!resolveData || !resolveData.request) {
60
+ return;
61
+ }
62
+
63
+ const request = resolveData.request;
64
+
65
+ // Check if this is a page file
66
+ if (this.isPageFile(request, resolveData.context)) {
67
+ const shouldExclude = this.shouldExcludePage(request);
68
+
69
+ if (shouldExclude) {
70
+ if (this.options.verbose) {
71
+ console.log(`📄 NextBuildFilterPlugin: Replacing page with empty module: ${request}`);
72
+ }
73
+
74
+ // Replace with empty module instead of blocking the request
75
+ resolveData.request = require.resolve('./empty-module.js');
76
+ return;
77
+ }
78
+ }
79
+
80
+ // Don't return resolveData, just let it continue processing
81
+ });
82
+ });
83
+
84
+ // Additional hook to filter entries
85
+ compiler.hooks.entryOption.tap(pluginName, (context, entry) => {
86
+ if (typeof entry === 'object' && !Array.isArray(entry)) {
87
+ const filteredEntry = {};
88
+
89
+ for (const [key, value] of Object.entries(entry)) {
90
+ if (!this.shouldExcludeEntryPoint(key)) {
91
+ filteredEntry[key] = value;
92
+ } else if (this.options.verbose) {
93
+ console.log(`📄 NextBuildFilterPlugin: Excluding entry point: ${key}`);
94
+ }
95
+ }
96
+
97
+ // Replace the entry object with filtered version
98
+ Object.keys(entry).forEach(key => delete entry[key]);
99
+ Object.assign(entry, filteredEntry);
100
+ }
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Check if a request is for a page/route file
106
+ */
107
+ isPageFile(request, context) {
108
+ if (!request) return false;
109
+
110
+ // Check for common page file extensions
111
+ const pageExtensions = ['.js', '.jsx', '.ts', '.tsx'];
112
+ const hasPageExtension = pageExtensions.some(ext => request.endsWith(ext));
113
+
114
+ if (!hasPageExtension) return false;
115
+
116
+ // Check if the request is in the pages directory (Pages Router)
117
+ // Next.js uses 'private-next-pages/' prefix for page imports
118
+ const isInPages = this.options.supportPagesRouter && (
119
+ request.includes(`/${this.options.pagesDir}/`) ||
120
+ request.includes(`private-next-pages/`) ||
121
+ (context && context.includes(`/${this.options.pagesDir}/`))
122
+ );
123
+
124
+ // Check if the request is in the app directory (App Router)
125
+ const isInApp = this.options.supportAppRouter && (
126
+ request.includes(`/${this.options.appDir}/`) ||
127
+ (context && context.includes(`/${this.options.appDir}/`))
128
+ );
129
+
130
+ // For App Router, only filter page.tsx files, not layout.tsx or other special files
131
+ if (isInApp) {
132
+ // Don't filter layout.tsx, loading.tsx, error.tsx, etc.
133
+ const appRouterSpecialFiles = ['layout.tsx', 'layout.ts', 'layout.jsx', 'layout.js',
134
+ 'loading.tsx', 'loading.ts', 'loading.jsx', 'loading.js',
135
+ 'error.tsx', 'error.ts', 'error.jsx', 'error.js',
136
+ 'not-found.tsx', 'not-found.ts', 'not-found.jsx', 'not-found.js',
137
+ 'template.tsx', 'template.ts', 'template.jsx', 'template.js'];
138
+
139
+ const isSpecialFile = appRouterSpecialFiles.some(file => request.endsWith(file));
140
+ if (isSpecialFile) {
141
+ return false; // Don't filter special App Router files
142
+ }
143
+
144
+ // Only filter page.tsx files in App Router
145
+ const isPageFile = request.endsWith('page.tsx') || request.endsWith('page.ts') ||
146
+ request.endsWith('page.jsx') || request.endsWith('page.js');
147
+ return isPageFile;
148
+ }
149
+
150
+ return isInPages;
151
+ }
152
+
153
+ /**
154
+ * Determine if a page should be excluded from the build
155
+ */
156
+ shouldExcludePage(request) {
157
+ const normalizedRequest = this.normalizePath(request);
158
+
159
+ // Extract the route path from the request
160
+ const routePath = this.extractRoutePath(normalizedRequest);
161
+ if (!routePath) return false;
162
+
163
+ // If includedPages is specified, only include those pages
164
+ if (this.options.includedPages.length > 0) {
165
+ const isIncluded = this.options.includedPages.some(page => {
166
+ const normalizedPage = this.normalizePath(page);
167
+ return routePath === normalizedPage || routePath.startsWith(normalizedPage + '/');
168
+ });
169
+ return !isIncluded;
170
+ }
171
+
172
+ // Check excluded pages
173
+ if (this.options.excludedPages.length > 0) {
174
+ const isExcluded = this.options.excludedPages.some(page => {
175
+ const normalizedPage = this.normalizePath(page);
176
+ return routePath === normalizedPage || routePath.startsWith(normalizedPage + '/');
177
+ });
178
+ if (isExcluded) return true;
179
+ }
180
+
181
+ // Check exclude patterns
182
+ if (this.options.excludePatterns.length > 0) {
183
+ const isPatternExcluded = this.options.excludePatterns.some(pattern => {
184
+ try {
185
+ const regex = new RegExp(pattern);
186
+ return regex.test(routePath);
187
+ } catch (e) {
188
+ console.warn(`📄 Invalid regex pattern: ${pattern}`);
189
+ return false;
190
+ }
191
+ });
192
+ if (isPatternExcluded) return true;
193
+ }
194
+
195
+ return false;
196
+ }
197
+
198
+ /**
199
+ * Extract route path from a request
200
+ */
201
+ extractRoutePath(normalizedRequest) {
202
+ let routePath = null;
203
+
204
+ // Check for App Router path
205
+ if (this.options.supportAppRouter) {
206
+ const appIndex = normalizedRequest.indexOf(`/${this.options.appDir}/`);
207
+ if (appIndex !== -1) {
208
+ routePath = normalizedRequest.substring(appIndex + `/${this.options.appDir}/`.length);
209
+ }
210
+ }
211
+
212
+ // Check for Pages Router path
213
+ if (!routePath && this.options.supportPagesRouter) {
214
+ // Check for Next.js internal private-next-pages/ prefix
215
+ const privateIndex = normalizedRequest.indexOf('private-next-pages/');
216
+ if (privateIndex !== -1) {
217
+ routePath = normalizedRequest.substring(privateIndex + 'private-next-pages/'.length);
218
+ } else {
219
+ const pagesIndex = normalizedRequest.indexOf(`/${this.options.pagesDir}/`);
220
+ if (pagesIndex !== -1) {
221
+ routePath = normalizedRequest.substring(pagesIndex + `/${this.options.pagesDir}/`.length);
222
+ }
223
+ }
224
+ }
225
+
226
+ if (!routePath) return null;
227
+
228
+ // Remove file name and extension for App Router
229
+ if (this.options.supportAppRouter && normalizedRequest.includes(`/${this.options.appDir}/`)) {
230
+ // Remove /page.tsx, /page.js, etc.
231
+ routePath = routePath.replace(/\/page\.(tsx|ts|jsx|js)$/, '');
232
+ // Remove leading slash if present
233
+ routePath = routePath.replace(/^\//, '');
234
+ } else {
235
+ // For Pages Router, remove extension
236
+ routePath = routePath.replace(/\.(tsx|ts|jsx|js)$/, '');
237
+ }
238
+
239
+ // Handle root route
240
+ if (!routePath || routePath === '') {
241
+ return 'index';
242
+ }
243
+
244
+ return routePath;
245
+ }
246
+
247
+ /**
248
+ * Determine if an entry point should be excluded
249
+ */
250
+ shouldExcludeEntryPoint(entryKey) {
251
+ // Entry keys in Next.js typically follow patterns like:
252
+ // - pages/index
253
+ // - pages/about
254
+ // - pages/api/users
255
+
256
+ if (entryKey.startsWith('pages/')) {
257
+ const pagePath = entryKey.replace('pages/', '');
258
+ return this.shouldExcludePage(pagePath);
259
+ }
260
+
261
+ return false;
262
+ }
263
+
264
+ /**
265
+ * Normalize file paths for consistent comparison
266
+ */
267
+ normalizePath(filePath) {
268
+ return filePath.replace(/\\/g, '/').toLowerCase();
269
+ }
270
+ }
271
+
272
+ module.exports = NextBuildFilterPlugin;
@@ -0,0 +1,169 @@
1
+ const path = require('path');
2
+
3
+ /**
4
+ * Next.js Page Filter Plugin
5
+ *
6
+ * Enhanced version that supports both Pages Router and App Router (Next.js 13+)
7
+ */
8
+ function withPageFilter(options = {}) {
9
+ return (nextConfig = {}) => {
10
+ const filterOptions = {
11
+ includedPages: options.includedPages || [],
12
+ excludedPages: options.excludedPages || [],
13
+ enabled: options.enabled !== undefined ? options.enabled : process.env.FILTER_PAGES === 'true',
14
+ excludePatterns: options.excludePatterns || [],
15
+ verbose: options.verbose || false,
16
+ pagesDir: options.pagesDir || 'pages',
17
+ appDir: options.appDir || 'app',
18
+ supportAppRouter: options.supportAppRouter !== false, // Default to true
19
+ supportPagesRouter: options.supportPagesRouter !== false, // Default to true
20
+ ...options
21
+ };
22
+
23
+ if (!filterOptions.enabled) {
24
+ if (filterOptions.verbose) {
25
+ console.log('📄 Page filtering disabled');
26
+ }
27
+ return nextConfig;
28
+ }
29
+
30
+ return {
31
+ ...nextConfig,
32
+
33
+ webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
34
+ // Apply the existing webpack config if it exists
35
+ if (nextConfig.webpack) {
36
+ config = nextConfig.webpack(config, { buildId, dev, isServer, defaultLoaders, webpack });
37
+ }
38
+
39
+ // Don't apply filtering in development mode unless explicitly enabled
40
+ if (dev && !filterOptions.enableInDev) {
41
+ return config;
42
+ }
43
+
44
+ // Add our custom plugin
45
+ const NextBuildFilterPlugin = require('./next-build-filter-plugin');
46
+ config.plugins.push(new NextBuildFilterPlugin(filterOptions));
47
+
48
+ return config;
49
+ },
50
+
51
+ // Preserve existing experimental settings
52
+ experimental: {
53
+ ...nextConfig.experimental
54
+ }
55
+ };
56
+ };
57
+ }
58
+
59
+ function isPageFile(request, context, options) {
60
+ if (!request) return false;
61
+
62
+ const pageExtensions = ['.js', '.jsx', '.ts', '.tsx'];
63
+ const hasPageExtension = pageExtensions.some(ext => request.endsWith(ext));
64
+
65
+ if (!hasPageExtension) return false;
66
+
67
+ // Check for Pages Router
68
+ const isInPagesDir = options.supportPagesRouter && (
69
+ request.includes(`/${options.pagesDir}/`) ||
70
+ (context && context.includes(`/${options.pagesDir}/`))
71
+ );
72
+
73
+ // Check for App Router
74
+ const isInAppDir = options.supportAppRouter && (
75
+ request.includes(`/${options.appDir}/`) ||
76
+ (context && context.includes(`/${options.appDir}/`))
77
+ );
78
+
79
+ return isInPagesDir || isInAppDir;
80
+ }
81
+
82
+ function shouldExcludePage(request, options) {
83
+ const normalizedRequest = normalizePath(request);
84
+
85
+ // Extract route path from request
86
+ const routePath = extractRoutePath(normalizedRequest, options);
87
+
88
+ if (!routePath) return false;
89
+
90
+ // If includedPages is specified, only include those pages
91
+ if (options.includedPages && options.includedPages.length > 0) {
92
+ const isIncluded = options.includedPages.some(page => {
93
+ const normalizedPage = normalizePath(page);
94
+ return routePath === normalizedPage || routePath.includes(normalizedPage);
95
+ });
96
+ return !isIncluded;
97
+ }
98
+
99
+ // Check excluded pages
100
+ if (options.excludedPages && options.excludedPages.length > 0) {
101
+ const isExcluded = options.excludedPages.some(page => {
102
+ const normalizedPage = normalizePath(page);
103
+ return routePath === normalizedPage || routePath.includes(normalizedPage);
104
+ });
105
+ if (isExcluded) return true;
106
+ }
107
+
108
+ // Check exclude patterns
109
+ if (options.excludePatterns && options.excludePatterns.length > 0) {
110
+ const isPatternExcluded = options.excludePatterns.some(pattern => {
111
+ try {
112
+ const regex = new RegExp(pattern);
113
+ return regex.test(routePath);
114
+ } catch (e) {
115
+ console.warn(`📄 Invalid regex pattern: ${pattern}`);
116
+ return false;
117
+ }
118
+ });
119
+ if (isPatternExcluded) return true;
120
+ }
121
+
122
+ return false;
123
+ }
124
+
125
+ function extractRoutePath(normalizedRequest, options) {
126
+ // Extract route path from either /pages/ or /app/ directory
127
+ let routePath = null;
128
+
129
+ // Check for Pages Router path
130
+ if (options.supportPagesRouter) {
131
+ const pagesIndex = normalizedRequest.indexOf(`/${options.pagesDir}/`);
132
+ if (pagesIndex !== -1) {
133
+ routePath = normalizedRequest.substring(pagesIndex + `/${options.pagesDir}/`.length);
134
+ }
135
+ }
136
+
137
+ // Check for App Router path
138
+ if (!routePath && options.supportAppRouter) {
139
+ const appIndex = normalizedRequest.indexOf(`/${options.appDir}/`);
140
+ if (appIndex !== -1) {
141
+ routePath = normalizedRequest.substring(appIndex + `/${options.appDir}/`.length);
142
+ }
143
+ }
144
+
145
+ if (!routePath) return null;
146
+
147
+ // Remove file extension
148
+ routePath = routePath.replace(/\.(js|jsx|ts|tsx)$/, '');
149
+
150
+ // Handle special App Router files
151
+ if (options.supportAppRouter) {
152
+ // Remove App Router special files (page.tsx, layout.tsx, etc.)
153
+ routePath = routePath.replace(/\/(page|layout|loading|error|not-found|template|default)$/, '');
154
+
155
+ // Handle route groups - remove (group) patterns
156
+ routePath = routePath.replace(/\/\([^)]+\)/g, '');
157
+ }
158
+
159
+ // Clean up empty segments and normalize
160
+ routePath = routePath.replace(/\/+/g, '/').replace(/^\/|\/$/g, '');
161
+
162
+ return routePath || 'index';
163
+ }
164
+
165
+ function normalizePath(filePath) {
166
+ return filePath.replace(/\\/g, '/').toLowerCase();
167
+ }
168
+
169
+ module.exports = withPageFilter;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "next-build-filter",
3
+ "version": "0.1.0",
4
+ "description": "A Next.js plugin to exclude pages/routes during build without removing files. Supports both Pages Router and App Router (Next.js 13+).",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts",
10
+ "lib/",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "prepublishOnly": "npm run build",
16
+ "build": "echo \"No build process needed for now\""
17
+ },
18
+ "keywords": [
19
+ "nextjs",
20
+ "next.js",
21
+ "webpack",
22
+ "plugin",
23
+ "build",
24
+ "filter",
25
+ "pages",
26
+ "app-router",
27
+ "pages-router",
28
+ "optimization",
29
+ "bundle"
30
+ ],
31
+ "author": "",
32
+ "license": "MIT",
33
+ "peerDependencies": {
34
+ "next": ">=12.0.0",
35
+ "react": ">=17.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.0.0"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/your-username/next-build-filter.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/your-username/next-build-filter/issues"
46
+ },
47
+ "homepage": "https://github.com/your-username/next-build-filter#readme",
48
+ "engines": {
49
+ "node": ">=16.0.0"
50
+ }
51
+ }