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 +357 -0
- package/index.d.ts +42 -0
- package/index.js +6 -0
- package/lib/advanced-next-build-filter-plugin.js +204 -0
- package/lib/empty-module.js +29 -0
- package/lib/next-build-filter-plugin.js +272 -0
- package/lib/with-page-filter.js +169 -0
- package/package.json +51 -0
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
|
+
}
|