A Next.js plugin to exclude pages/routes during build without removing files. Supports both Pages Router and App Router (Next.js 13+).
npm install next-build-filterA 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.
- š Speed up builds by excluding unnecessary pages/routes
- š± App Router Support - Full Next.js 13+ App Router compatibility
- š Pages Router Support - Traditional Pages Router support
- šÆ Flexible filtering with multiple configuration options
- š Glob Pattern Matching - Use powerful wildcards like admin/, /test, blog/, /internal/**
- š Non-destructive - files remain in your codebase
- š Environment-aware - different configurations for dev/prod
- š Verbose logging to see what's being filtered
- šØ Advanced pattern matching support with regex (for complex cases)
- š§ TypeScript support with full type definitions
``bash`
npm install next-build-filter
`javascript
// next.config.js
const withPageFilter = require('next-build-filter');
const filterConfig = {
enabled: process.env.FILTER_PAGES === 'true',
verbose: true,
supportPagesRouter: true,
supportAppRouter: false,
excludedPages: [
'admin/**', // Exclude all admin pages (supports glob patterns)
'dev/**', // Exclude all dev pages
],
};
module.exports = withPageFilter(filterConfig)({
reactStrictMode: true,
});
`
`javascript
// next.config.js
const withPageFilter = require('next-build-filter');
const filterConfig = {
enabled: process.env.FILTER_PAGES === 'true',
verbose: true,
supportAppRouter: true,
supportPagesRouter: false,
excludedPages: [
'admin/**', // Excludes all routes under app/admin/ (glob pattern)
'dev/**', // Excludes all routes under app/dev/
],
};
module.exports = withPageFilter(filterConfig)({
reactStrictMode: true,
experimental: {
appDir: true,
},
});
`
`javascript
// next.config.js
const withPageFilter = require('next-build-filter');
const filterConfig = {
enabled: process.env.FILTER_PAGES === 'true',
verbose: true,
supportAppRouter: true,
supportPagesRouter: true,
excludedPages: [
'admin/', // Excludes both pages/admin/ and app/admin/** (glob pattern)
'dev/', // Excludes both pages/dev/ and app/dev/**
'**/internal', // Excludes any route ending with /internal
],
};
module.exports = withPageFilter(filterConfig)({
reactStrictMode: true,
experimental: {
appDir: true,
},
});
`
3. Run filtered builds:
`bashNormal build (all pages included)
npm run build
Glob Pattern Quick Reference
The plugin supports powerful glob patterns for flexible page/route matching:
| Pattern | What it matches | Example |
|---------|----------------|---------|
|
admin | Exact match | /admin only |
| admin/* | One level deep | /admin/users, /admin/settings |
| admin/** | Any depth | /admin/users, /admin/users/edit, /admin/settings/advanced |
| **/test | Ending with | /api/test, /components/test, /admin/tools/test |
| */debug | One wildcard | /api/debug, /dev/debug |
| /internal/ | Containing | Any route with /internal/ anywhere in path |
| {admin,dev}/** | Multiple patterns | All routes under /admin or /dev |Example Usage:
`javascript
excludedPages: [
'admin/**', // ā
Exclude all admin routes (recommended)
'dev/**/test', // ā
Exclude test pages in dev directory
'*-draft', // ā
Exclude pages ending with -draft
'api/*/internal', // ā
Exclude internal API routes
]
`Configuration Options
$3
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
enabled | boolean | process.env.FILTER_PAGES === 'true' | Enable/disable page filtering |
| verbose | boolean | false | Show detailed logging of filtered pages |
| enableInDev | boolean | false | Apply filtering in development mode |$3
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
supportAppRouter | boolean | true | Enable filtering for App Router (Next.js 13+) |
| supportPagesRouter | boolean | true | Enable filtering for Pages Router |
| appDir | string | 'app' | App Router directory name |
| pagesDir | string | 'pages' | Pages Router directory name |$3
The plugin supports three types of matching:
1. Exact Match - Direct string matching
2. Glob Patterns - Flexible wildcard matching (powered by minimatch)
3. Regex Patterns - Advanced pattern matching via
excludePatterns#### Glob Pattern Syntax
| Pattern | Description | Example Matches |
|---------|-------------|-----------------|
|
| Matches any characters except / | blog/ matches blog/post1, blog/post2 but not blog/category/post1 |
| | Matches any characters including / | blog/ matches blog/post1, blog/category/post1, blog/a/b/c |
| ? | Matches exactly one character | user/?/profile matches user/a/profile, user/1/profile |
| [abc] | Matches any character in the brackets | user/[0-9]/profile matches user/0/profile, user/5/profile |
| {a,b} | Matches any of the patterns | {admin,dev}/** matches admin/users, dev/debug |
| ! at start | Negation (not commonly used in this plugin) | !admin/** would match everything except admin routes |Common Glob Patterns Cheat Sheet:
| Use Case | Pattern | Matches |
|----------|---------|---------|
| Exact page |
admin | /admin only |
| Direct children | admin/* | /admin/users, /admin/settings (not nested) |
| All nested routes | admin/** | /admin/users, /admin/users/edit, /admin/settings/advanced |
| Ends with | /test or /*-test | /api/test, /users/profile-test |
| Starts with | admin/* or admin | /admin, /admin-panel, /admin/users |
| Contains | /internal/ | Any path with /internal/ segment |
| Multiple patterns | {admin,dev,test}/** | All routes under /admin, /dev, or /test |
| Specific file patterns | */-draft | /blog/post-draft, /products/item-draft |
| API versioning | api/v{1,2}/* | /api/v1/, /api/v2/* |
| Wildcard in middle | api/*/internal | /api/users/internal, /api/products/internal |#### 1. Include Only Specific Pages
Exact Match:
`javascript
const filterConfig = {
enabled: true,
includedPages: [
'index', // /
'about', // /about
'contact', // /contact
],
};
`Glob Pattern Matching:
`javascript
const filterConfig = {
enabled: true,
includedPages: [
'index', // Exact match: /
'blog/*', // All direct children: /blog/post1, /blog/post2
'products/*', // All nested routes: /products/, /products/category/*, etc.
'user/*/profile', // Wildcard in middle: /user/123/profile, /user/456/profile
],
};
`#### 2. Exclude Specific Pages
Exact Match:
`javascript
const filterConfig = {
enabled: true,
excludedPages: [
'admin', // /admin
'dev/debug', // /dev/debug
'api/internal', // /api/internal
],
};
`Glob Pattern Matching:
`javascript
const filterConfig = {
enabled: true,
excludedPages: [
'admin', // Exact match: /admin
'admin/*', // All admin sub-pages: /admin/users, /admin/settings
'admin/*', // All nested admin routes: /admin/, /admin/users/*, etc.
'dev/*', // All dev routes: /dev/, /dev/debug/*, etc.
'*/test', // Any route ending with /test: /api/test, /dev/test
'/internal/', // Any route containing /internal/
],
};
`#### 3. Regex Pattern-Based Exclusion (Advanced)
For complex patterns that can't be expressed with glob syntax, use
excludePatterns:
`javascript
const filterConfig = {
enabled: true,
excludePatterns: [
'dev/.*', // Regex: All pages in /dev/ directory
'.admin.', // Regex: Any page with 'admin' in the path
'.test.', // Regex: Any page with 'test' in the path
'^api/v[0-9]+/', // Regex: API versioned routes like /api/v1/, /api/v2/
],
};
`$3
Choose the right pattern type for your use case:
| Use Case | Glob Pattern | Regex Pattern | Recommendation |
|----------|--------------|---------------|----------------|
| Exclude all admin routes |
admin/* | admin/. | ā
Use Glob (simpler) |
| Exclude routes ending with -test | */-test | .*-test$ | ā
Use Glob (simpler) |
| Exclude versioned API routes (v1, v2) | N/A | ^api/v[0-9]+/ | ā
Use Regex (complex pattern) |
| Match any route containing 'internal' | /internal/ | .internal. | ā
Use Glob (simpler) |
| Match routes with date pattern (2024-01-01) | N/A | ^\d{4}-\d{2}-\d{2}$ | ā
Use Regex (complex pattern) |Guidelines:
- Use Glob Patterns (
includedPages/excludedPages) for most cases - they're simpler and more readable
- Use Regex Patterns (excludePatterns) only when you need advanced matching like character classes, lookaheads, or complex alternationsUsage Examples
$3
Perfect for large projects where you only need a few pages during development:`javascript
// next.config.js
const withPageFilter = require('./plugins/withPageFilter');const filterConfig = {
enabled: process.env.NODE_ENV === 'development',
verbose: true,
includedPages: [
'index',
'dashboard',
'profile',
],
};
module.exports = withPageFilter(filterConfig)({
reactStrictMode: true,
});
`$3
Exclude admin and debug pages from production builds using glob patterns:`javascript
const filterConfig = {
enabled: process.env.NODE_ENV === 'production',
excludedPages: [
'admin/**', // Exclude all admin routes
'debug', // Exclude debug page
'dev/**', // Exclude all dev tools and utilities
'**/test', // Exclude all test pages
'internal/**', // Exclude internal pages
],
};
`$3
Build only specific feature sets for different teams:`javascript
// Team A: Only marketing pages
const marketingConfig = {
enabled: true,
includedPages: [
'index',
'about',
'contact',
'blog/**', // All blog routes
'marketing/**', // All marketing pages
],
};// Team B: Only product pages
const productConfig = {
enabled: true,
includedPages: [
'products/**', // All product routes
'checkout/**', // All checkout routes
'cart', // Shopping cart
],
};
`$3
Use glob patterns to exclude testing and debugging routes:`javascript
const filterConfig = {
enabled: process.env.NODE_ENV === 'production',
excludedPages: [
'*/-test', // Exclude all routes ending with -test
'*/-debug', // Exclude all routes ending with -debug
'test/**', // Exclude all test directory routes
'debug/**', // Exclude all debug directory routes
'dev/**', // Exclude all development routes
'playground/**', // Exclude playground routes
],
};
`$3
Filter specific API routes using glob patterns:`javascript
const filterConfig = {
enabled: true,
excludedPages: [
'api/internal/**', // Exclude internal API routes
'api/*/admin', // Exclude admin endpoints in any API version
'api/webhooks/test-*', // Exclude test webhooks
'api/v/deprecated/*', // Exclude deprecated endpoints in all versions
],
};
`$3
Create builds with specific features using glob patterns:`javascript
const filterConfig = {
enabled: true,
includedPages: [
'index', // Home page
'about', // About page
// Conditionally include features based on environment
...(process.env.ENABLE_BLOG ? ['blog/**'] : []),
...(process.env.ENABLE_SHOP ? ['shop/', 'cart', 'checkout/'] : []),
...(process.env.ENABLE_FORUM ? ['forum/', 'community/'] : []),
],
};
`$3
Build specific language versions:`javascript
const filterConfig = {
enabled: true,
// Only build English version
includedPages: [
'en/**', // All English pages
'index', // Root page
],
// Or exclude other languages
excludedPages: [
'fr/**', // Exclude French
'de/**', // Exclude German
'es/**', // Exclude Spanish
'ja/**', // Exclude Japanese
],
};
`$3
Use environment variables for flexible configuration:`javascript
const filterConfig = {
enabled: process.env.FILTER_PAGES === 'true',
verbose: process.env.NODE_ENV === 'development',
excludedPages: process.env.EXCLUDED_PAGES ?
process.env.EXCLUDED_PAGES.split(',') : [],
includedPages: process.env.INCLUDED_PAGES ?
process.env.INCLUDED_PAGES.split(',') : [],
};
`Then use it:
`bash
Include only specific pages
FILTER_PAGES=true INCLUDED_PAGES=index,about,contact npm run buildExclude specific pages
FILTER_PAGES=true EXCLUDED_PAGES=admin,debug npm run build
`Available Scripts
The project includes several npm scripts for different build scenarios:
`bash
Development server
npm run devNormal build (all pages)
npm run buildFiltered build (respects FILTER_PAGES environment variable)
npm run build:filteredStart production server
npm start
`How It Works
The plugin works by integrating with Next.js's webpack configuration and build process:
1. Webpack Plugin Integration: The plugin hooks into webpack's module resolution process
2. Page Detection: It identifies page files in the
/pages and /app directories (based on router support configuration)
3. Path Normalization: Routes are normalized (lowercase, forward slashes) for consistent matching
4. Pattern Matching: For each page, the plugin checks if it should be filtered using:
- Glob patterns (via minimatch) - checked first
- Exact string matching - fallback for backward compatibility
- Regex patterns (via excludePatterns) - for advanced cases
5. Filtering Logic: Based on your configuration, it determines which pages to include/exclude
6. Module Replacement: Filtered pages are replaced with empty modules during the build process
7. Build Optimization: The final bundle only includes meaningful content for pages you want$3
The plugin replaces filtered pages with custom 404 pages rather than removing them entirely from the build. This approach:
- ā
Clear user feedback: Users see a "Page Not Available" message if they access a filtered page
- ā
Preserves routing structure: Pages still exist in the manifest
- ā
Prevents build errors: No missing module errors from dependencies
- ā
Testable: Contains a unique marker (
NEXT_BUILD_FILTER_EXCLUDED_PAGE) for verification
- ā
Proper HTTP status: Returns 404 status codeFor App Router: Uses Next.js's built-in
notFound() function for proper 404 handling
For Pages Router: Returns a custom 404 component with proper status codeWhat this means:
- Filtered pages will still appear in your
.next/server directory
- They will show a "Page Not Available" message if accessed
- The pages contain minimal code (just the 404 component)
- Build verification can detect filtered pages via the unique marker$3
When matching routes, the plugin uses this order:
1. Glob Pattern Match (via minimatch):
admin/** matches admin/users/list
2. Exact Match: admin matches only admin
3. Substring Match: admin also matches routes containing admin (backward compatibility)
4. Regex Match (if using excludePatterns): admin/.* matches admin/anythingThis multi-tiered approach ensures backward compatibility while providing powerful glob pattern support.
Project Structure
`
next-build-filter/ # Main plugin package
āāā lib/ # Plugin source code
ā āāā with-page-filter.js # Main plugin wrapper
ā āāā next-build-filter-plugin.js # Webpack plugin
ā āāā advanced-next-build-filter-plugin.js # Advanced filtering
ā āāā empty-module.js # Replacement for filtered pages
āāā demo/ # Demo projects
ā āāā pages-router-demo/ # Pages Router demo
ā ā āāā pages/ # Traditional Next.js pages
ā ā ā āāā index.js # Home page
ā ā ā āāā about.js # About page
ā ā ā āāā admin.js # Admin page (filtered)
ā ā ā āāā dev/debug.js # Debug page (filtered)
ā ā āāā next.config.js # Pages Router configuration
ā ā āāā package.json # Demo dependencies
ā āāā app-router-demo/ # App Router demo (Next.js 13+)
ā āāā app/ # App Router structure
ā ā āāā page.tsx # Home route
ā ā āāā layout.tsx # Root layout
ā ā āāā about/page.tsx # About route
ā ā āāā admin/page.tsx # Admin route (filtered)
ā ā āāā dev/debug/page.tsx # Debug route (filtered)
ā āāā next.config.js # App Router configuration
ā āāā package.json # Demo dependencies
āāā index.js # Main entry point
āāā index.d.ts # TypeScript definitions
āāā package.json # Plugin package configuration
āāā README.md # This file
`Demo Projects
This package includes two complete demo projects to showcase the filtering capabilities:
$3
`bash
cd demo/pages-router-demo
npm install
npm run build:filtered
`$3
`bash
cd demo/app-router-demo
npm install
npm run build:filtered
`Real-World Use Cases
$3
- Include only product pages during catalog development
- Exclude admin pages from customer-facing builds$3
- Build tenant-specific versions with only relevant pages
- Exclude unused features per tenant$3
- Speed up local development by including only pages you're working on
- Create lightweight builds for testing specific features$3
- Create builds with debug pages for staging
- Exclude debug pages from productionTips and Best Practices
1. Start Small: Begin by excluding just a few pages and gradually expand
2. Use Verbose Mode: Enable verbose logging during development to see what's being filtered
3. Environment-Specific: Use different configurations for different environments
4. Test Thoroughly: Always test your filtered builds to ensure functionality
5. Document Configuration: Keep your filtering logic well-documented for your team
Migration Guide
$3
If you're currently using
excludePatterns with regex, consider migrating to glob patterns in excludedPages for better readability:Before (Regex):
`javascript
const filterConfig = {
excludePatterns: [
'admin/.*', // Regex
'dev/.*', // Regex
'.test.', // Regex
],
};
`After (Glob):
`javascript
const filterConfig = {
excludedPages: [
'admin/**', // Glob - clearer intent
'dev/**', // Glob - easier to read
'/test/', // Glob - more intuitive
],
};
`Note: Both approaches work! Use glob patterns for simplicity and regex for complex patterns.
Troubleshooting
$3
- ā
Check that enabled: true is set in your configuration
- ā
Verify the page paths match (glob patterns are case-sensitive by default)
- ā
Enable verbose: true to see what's being processed
- ā
Test your glob pattern: admin/* matches all nested routes, while admin/ only matches direct children
- ā
Ensure you're running the build with the correct environment variable: FILTER_PAGES=true npm run build$3
Common Issues:
1. Wrong wildcard usage
- ā
admin/* only matches direct children like /admin/users
- ā
admin/** matches all nested routes like /admin/users/edit2. Case sensitivity
- Route paths are normalized to lowercase before matching
- Pattern:
Admin/ will be normalized to admin/3. Missing or extra slashes
- ā
Correct:
admin/users/, /test, api/*
- ā Avoid: /admin/users/** (leading slash not needed)4. Not matching what you expect
- Enable
verbose: true to see the actual route paths
- Example verbose output: š Filtering out: admin/users/edit
- Compare the logged path with your patternTesting Your Patterns:
Use
verbose: true and check the console output during build:
`javascript
const filterConfig = {
enabled: true,
verbose: true, // Shows which routes are being filtered
excludedPages: ['admin/**'],
};
`Console output will show:
`
š Filtering out: admin/dashboard
š Filtering out: admin/users/list
š Filtering out: admin/settings/profile
`Pattern Matching Examples:
| Route Path | Pattern | Matches? | Why |
|------------|---------|----------|-----|
|
admin/users | admin/* | ā
Yes | Direct child |
| admin/users/edit | admin/* | ā No | Too deeply nested |
| admin/users/edit | admin/ | ā
Yes | matches any depth |
| blog/post-123 | blog/- | ā
Yes | * matches post and 123 |
| api/v1/users | api//users | ā
Yes | matches v1 |
| api/v1/internal/users | api/*/users | ā No | Too many segments |
| anything/internal/data | /internal/ | ā
Yes | ** matches any segments |$3
- Ensure all required pages (like _app.js, _document.js, _app.tsx) are not being filtered
- Check that your regex patterns are valid if using excludePatterns
- Avoid overly broad patterns that might exclude critical Next.js files
- Test patterns incrementally: start with one pattern and add more once working$3
- Set enableInDev: true if you want consistent behavior across environments
- Use environment variables to control filtering per environment
- Note: By default, filtering is disabled in development mode unless enableInDev is set
- Remember: npm run dev vs npm run build behave differently by default$3
If you're having trouble with patterns, try these steps:
1. Start simple: Test with an exact match first
`javascript
excludedPages: ['admin'] // Start with exact match
`2. Add verbosity: See what's being matched
`javascript
verbose: true
`3. Test one pattern at a time: Isolate the problematic pattern
`javascript
excludedPages: ['admin/**'] // Test one at a time
`4. Check the actual route paths: Look at your project structure
`
pages/
admin/
users.js ā Route path: admin/users
settings/
profile.js ā Route path: admin/settings/profile
`5. Use the demos: Test your patterns in the included demo projects
`bash
cd demo/pages-router-demo
FILTER_PAGES=true npm run build
`Technical Details
$3
This plugin uses minimatch for glob pattern matching, the same library used by many popular tools like:
- npm
- webpack
- babel
- eslint
Minimatch provides powerful and reliable glob pattern matching with full support for:
- Brace expansion:
{a,b,c}
- Extended glob patterns: @(pattern|list)
- Multiple wildcards: //
- Character classes: [abc], [0-9]$3
- Glob pattern matching is performed during the webpack build phase
- Pattern matching is highly optimized by minimatch
- Routes are normalized once and cached for efficient matching
- Only page/route files are checked (not all webpack modules)
$3
- ā
Next.js 12+ (Pages Router)
- ā
Next.js 13+ (App Router)
- ā
Node.js 16+
- ā
Works with TypeScript
- ā
Compatible with all Next.js deployment targets (standalone, static export, etc.)
Testing
This project includes a comprehensive test suite with unit tests and end-to-end tests.
$3
`bash
Run all unit tests
npm testRun unit tests in watch mode (for development)
npm run test:watchRun unit tests with coverage report
npm run test:coverageRun end-to-end tests (actual builds)
npm run test:e2eRun all tests (unit + e2e)
npm run test:all
`$3
- Unit Tests (
tests/unit/): Test core functionality using Vitest
- glob-patterns.test.js: Tests for glob pattern matching
- plugin.test.js: Tests for plugin core functionality
- with-page-filter.test.js: Tests for configuration wrapper- E2E Tests (
tests/e2e/): Test actual Next.js builds
- Verifies filtering works correctly in real builds
- Tests both Pages Router and App Router demos$3
The test suite covers:
- ā
Glob pattern matching with various patterns
- ā
Path normalization and route extraction
- ā
Page file identification (Pages Router & App Router)
- ā
Filtering logic (includedPages, excludedPages, patterns)
- ā
Configuration options and defaults
- ā
Webpack integration
- ā
Actual build output verification
See
tests/README.md for detailed testing documentation.Contributing
Contributions are welcome! When contributing:
1. Write tests for new features
2. Ensure all tests pass:
npm run test:all`Feel free to submit issues, feature requests, or pull requests to improve this plugin.
MIT