Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/nextjs/src/config/manifest/createRouteManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ function isRouteGroup(name: string): boolean {
return name.startsWith('(') && name.endsWith(')');
}

function normalizeRoutePath(routePath: string): string {
function normalizeRouteGroupPath(routePath: string): string {
// Remove route group segments from the path
return routePath.replace(/\/\([^)]+\)/g, '');
// Using positive lookahead with (?=[^)\/]*\)) to avoid polynomial matching
return routePath.replace(/\/\((?=[^)/]*\))[^)/]+\)/g, '');
}

function getDynamicRouteSegment(name: string): string {
Expand Down Expand Up @@ -140,7 +141,7 @@ function scanAppDirectory(dir: string, basePath: string = '', includeRouteGroups

if (pageFile) {
// Conditionally normalize the path based on includeRouteGroups option
const routePath = includeRouteGroups ? basePath || '/' : normalizeRoutePath(basePath || '/');
const routePath = includeRouteGroups ? basePath || '/' : normalizeRouteGroupPath(basePath || '/');
const isDynamic = routePath.includes(':');

// Check if this page has generateStaticParams (ISR/SSG indicator)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// API Internal Page
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Login V2 Page
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Features Beta Page
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ describe('route-groups', () => {
expect(manifest).toEqual({
staticRoutes: [
{ path: '/' },
{ path: '/api' },
{ path: '/login' },
{ path: '/signup' },
{ path: '/login' }, // from (auth-v2)
{ path: '/dashboard' },
{ path: '/settings/profile' },
{ path: '/public/about' },
{ path: '/features' },
],
dynamicRoutes: [
{
Expand All @@ -28,6 +31,8 @@ describe('route-groups', () => {
],
isrRoutes: [],
});
// Verify we have 9 static routes total (including duplicates from special chars)
expect(manifest.staticRoutes).toHaveLength(9);
});

test('should handle dynamic routes within route groups', () => {
Expand All @@ -37,6 +42,17 @@ describe('route-groups', () => {
expect(regex.test('/dashboard/abc')).toBe(true);
expect(regex.test('/dashboard/123/456')).toBe(false);
});

test.each([
{ routeGroup: '(auth-v2)', strippedPath: '/login', description: 'hyphens' },
{ routeGroup: '(api_internal)', strippedPath: '/api', description: 'underscores' },
{ routeGroup: '(v2.0.beta)', strippedPath: '/features', description: 'dots' },
])('should strip route groups with $description', ({ routeGroup, strippedPath }) => {
// Verify the stripped path exists
expect(manifest.staticRoutes.find(route => route.path === strippedPath)).toBeDefined();
// Verify the route group was stripped, not included
expect(manifest.staticRoutes.find(route => route.path.includes(routeGroup))).toBeUndefined();
});
});

describe('includeRouteGroups: true', () => {
Expand All @@ -46,11 +62,14 @@ describe('route-groups', () => {
expect(manifest).toEqual({
staticRoutes: [
{ path: '/' },
{ path: '/(api_internal)/api' },
{ path: '/(auth)/login' },
{ path: '/(auth)/signup' },
{ path: '/(auth-v2)/login' },
{ path: '/(dashboard)/dashboard' },
{ path: '/(dashboard)/settings/profile' },
{ path: '/(marketing)/public/about' },
{ path: '/(v2.0.beta)/features' },
],
dynamicRoutes: [
{
Expand All @@ -62,6 +81,7 @@ describe('route-groups', () => {
],
isrRoutes: [],
});
expect(manifest.staticRoutes).toHaveLength(9);
});

test('should handle dynamic routes within route groups with proper regex escaping', () => {
Expand Down Expand Up @@ -92,5 +112,13 @@ describe('route-groups', () => {
expect(authSignup).toBeDefined();
expect(marketingPublic).toBeDefined();
});

test.each([
{ fullPath: '/(auth-v2)/login', description: 'hyphens' },
{ fullPath: '/(api_internal)/api', description: 'underscores' },
{ fullPath: '/(v2.0.beta)/features', description: 'dots' },
])('should preserve route groups with $description when includeRouteGroups is true', ({ fullPath }) => {
expect(manifest.staticRoutes.find(route => route.path === fullPath)).toBeDefined();
});
});
});
Loading