Skip to content

Commit c3e6680

Browse files
authored
perf(build): optimize buildAppStaticPaths performance and add helper function (#81386)
### What? Optimizes the `buildAppStaticPaths` function performance and extracts a helper function `calculateFallbackMode` to reduce code duplication. ### Why? The `buildAppStaticPaths` function had several performance bottlenecks: 1. **Repeated regex pattern compilation**: Route parameter patterns were being compiled inside the loop for every route, causing unnecessary overhead 2. **Inefficient root parameter lookups**: Using `Array.includes()` for root parameter checks resulted in O(n) lookups for each parameter 3. **Duplicated fallback mode logic**: The same fallback mode calculation was repeated in multiple places, making the code harder to maintain and prone to inconsistencies These inefficiencies became more apparent when processing large numbers of routes with complex parameter structures. ### How? **Performance optimizations:** - Pre-compile regex patterns for route parameters outside the loop and store them in a Map for O(1) lookups - Convert `rootParamKeys` to a Set for O(1) lookup time instead of O(n) array searches - Optimize the fallback parameter collection loop to break early and avoid redundant iterations **Code organization:** - Extract `calculateFallbackMode` helper function to eliminate code duplication - Add comprehensive tests for the new helper function to ensure correctness - Simplify the main loop logic by using the pre-compiled patterns and optimized data structures
1 parent a3f031d commit c3e6680

File tree

2 files changed

+207
-110
lines changed

2 files changed

+207
-110
lines changed

packages/next/src/build/static-paths/app.test.ts

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { FallbackMode } from '../../lib/fallback'
22
import type { Params } from '../../server/request/params'
33
import {
44
assignErrorIfEmpty,
5-
generateParamPrefixCombinations,
5+
generateAllParamCombinations,
6+
calculateFallbackMode,
67
filterUniqueParams,
78
generateRouteStaticParams,
89
} from './app'
@@ -356,7 +357,7 @@ describe('generateParamPrefixCombinations', () => {
356357
{ id: '2', name: 'test' },
357358
]
358359

359-
const unique = generateParamPrefixCombinations(['id'], params, [])
360+
const unique = generateAllParamCombinations(['id'], params, [])
360361

361362
expect(unique).toEqual([{ id: '1' }, { id: '2' }])
362363
})
@@ -369,11 +370,7 @@ describe('generateParamPrefixCombinations', () => {
369370
{ lang: 'fr', region: 'CA', page: 'about' },
370371
]
371372

372-
const unique = generateParamPrefixCombinations(
373-
['lang', 'region'],
374-
params,
375-
[]
376-
)
373+
const unique = generateAllParamCombinations(['lang', 'region'], params, [])
377374

378375
expect(unique).toEqual([
379376
{ lang: 'en' },
@@ -386,20 +383,20 @@ describe('generateParamPrefixCombinations', () => {
386383
it('should handle parameter value collisions', () => {
387384
const params = [{ slug: ['foo', 'bar'] }, { slug: 'foo,bar' }]
388385

389-
const unique = generateParamPrefixCombinations(['slug'], params, [])
386+
const unique = generateAllParamCombinations(['slug'], params, [])
390387

391388
expect(unique).toEqual([{ slug: ['foo', 'bar'] }, { slug: 'foo,bar' }])
392389
})
393390

394391
it('should handle empty inputs', () => {
395392
// Empty routeParamKeys
396-
expect(generateParamPrefixCombinations([], [{ id: '1' }], [])).toEqual([])
393+
expect(generateAllParamCombinations([], [{ id: '1' }], [])).toEqual([])
397394

398395
// Empty routeParams
399-
expect(generateParamPrefixCombinations(['id'], [], [])).toEqual([])
396+
expect(generateAllParamCombinations(['id'], [], [])).toEqual([])
400397

401398
// Both empty
402-
expect(generateParamPrefixCombinations([], [], [])).toEqual([])
399+
expect(generateAllParamCombinations([], [], [])).toEqual([])
403400
})
404401

405402
it('should handle undefined parameters', () => {
@@ -409,7 +406,7 @@ describe('generateParamPrefixCombinations', () => {
409406
{ id: '3' }, // missing name key
410407
]
411408

412-
const unique = generateParamPrefixCombinations(['id', 'name'], params, [])
409+
const unique = generateAllParamCombinations(['id', 'name'], params, [])
413410

414411
expect(unique).toEqual([
415412
{ id: '1' },
@@ -426,7 +423,7 @@ describe('generateParamPrefixCombinations', () => {
426423
{ lang: 'fr' }, // missing region and category
427424
]
428425

429-
const unique = generateParamPrefixCombinations(
426+
const unique = generateAllParamCombinations(
430427
['lang', 'region', 'category'],
431428
params,
432429
[]
@@ -450,7 +447,7 @@ describe('generateParamPrefixCombinations', () => {
450447
{ slug: 'U:undefined' }, // String that looks like undefined prefix
451448
]
452449

453-
const unique = generateParamPrefixCombinations(['slug'], params, [])
450+
const unique = generateAllParamCombinations(['slug'], params, [])
454451

455452
expect(unique).toEqual([
456453
{ slug: ['foo', 'bar'] },
@@ -468,7 +465,7 @@ describe('generateParamPrefixCombinations', () => {
468465
{ slug: ['foo', 'bar|baz'] }, // Array with pipe in element
469466
]
470467

471-
const unique = generateParamPrefixCombinations(['slug'], params, [])
468+
const unique = generateAllParamCombinations(['slug'], params, [])
472469

473470
expect(unique).toEqual([{ slug: 'foo|bar' }, { slug: ['foo', 'bar|baz'] }])
474471
})
@@ -480,7 +477,7 @@ describe('generateParamPrefixCombinations', () => {
480477
{ a: '1', b: '2', c: '3', d: '7' },
481478
]
482479

483-
const unique = generateParamPrefixCombinations(
480+
const unique = generateAllParamCombinations(
484481
['a', 'b', 'c', 'd', 'e'],
485482
params,
486483
[]
@@ -505,7 +502,7 @@ describe('generateParamPrefixCombinations', () => {
505502
{ lang: 'fr', region: 'CA', slug: 'about' },
506503
]
507504

508-
const unique = generateParamPrefixCombinations(
505+
const unique = generateAllParamCombinations(
509506
['lang', 'region', 'slug'],
510507
params,
511508
['lang', 'region'] // Root params
@@ -529,7 +526,7 @@ describe('generateParamPrefixCombinations', () => {
529526
{ category: 'sports', slug: 'news' },
530527
]
531528

532-
const unique = generateParamPrefixCombinations(
529+
const unique = generateAllParamCombinations(
533530
['category', 'slug'],
534531
params,
535532
[] // No root params
@@ -552,7 +549,7 @@ describe('generateParamPrefixCombinations', () => {
552549
{ lang: 'fr', page: 'home' },
553550
]
554551

555-
const unique = generateParamPrefixCombinations(
552+
const unique = generateAllParamCombinations(
556553
['lang', 'page'],
557554
params,
558555
['lang'] // Single root param
@@ -575,7 +572,7 @@ describe('generateParamPrefixCombinations', () => {
575572
{ page: 'contact' }, // Missing lang root param
576573
]
577574

578-
const unique = generateParamPrefixCombinations(
575+
const unique = generateAllParamCombinations(
579576
['lang', 'page'],
580577
params,
581578
['lang'] // Root param
@@ -596,7 +593,7 @@ describe('generateParamPrefixCombinations', () => {
596593
{ category: 'sports', slug: 'news' },
597594
]
598595

599-
const unique = generateParamPrefixCombinations(
596+
const unique = generateAllParamCombinations(
600597
['category', 'slug'],
601598
params,
602599
['lang', 'region'] // Root params not in route params
@@ -620,7 +617,7 @@ describe('generateParamPrefixCombinations', () => {
620617
{ lang: 'en', locale: 'us' }, // Missing slug parameter
621618
]
622619

623-
const unique = generateParamPrefixCombinations(
620+
const unique = generateAllParamCombinations(
624621
['lang', 'locale', 'slug'], // All route params
625622
params,
626623
['lang', 'locale'] // Root params
@@ -637,7 +634,7 @@ describe('generateParamPrefixCombinations', () => {
637634
// This might be what's happening for the [slug] route
638635
const params: Params[] = [] // No generateStaticParams results
639636

640-
const unique = generateParamPrefixCombinations(
637+
const unique = generateAllParamCombinations(
641638
['lang', 'locale', 'slug'], // All route params
642639
params,
643640
['lang', 'locale'] // Root params
@@ -1027,3 +1024,73 @@ describe('generateRouteStaticParams', () => {
10271024
})
10281025
})
10291026
})
1027+
1028+
describe('calculateFallbackMode', () => {
1029+
it('should return NOT_FOUND when dynamic params are disabled', () => {
1030+
const result = calculateFallbackMode(false, [], FallbackMode.PRERENDER)
1031+
1032+
expect(result).toBe(FallbackMode.NOT_FOUND)
1033+
})
1034+
1035+
it('should return NOT_FOUND when dynamic params are disabled regardless of root params', () => {
1036+
const result = calculateFallbackMode(
1037+
false,
1038+
['rootParam'],
1039+
FallbackMode.BLOCKING_STATIC_RENDER
1040+
)
1041+
1042+
expect(result).toBe(FallbackMode.NOT_FOUND)
1043+
})
1044+
1045+
it('should return BLOCKING_STATIC_RENDER when dynamic params are enabled and root params exist', () => {
1046+
const result = calculateFallbackMode(
1047+
true,
1048+
['rootParam1', 'rootParam2'],
1049+
FallbackMode.PRERENDER
1050+
)
1051+
1052+
expect(result).toBe(FallbackMode.BLOCKING_STATIC_RENDER)
1053+
})
1054+
1055+
it('should return base fallback mode when dynamic params are enabled and no root params', () => {
1056+
const result = calculateFallbackMode(true, [], FallbackMode.PRERENDER)
1057+
1058+
expect(result).toBe(FallbackMode.PRERENDER)
1059+
})
1060+
1061+
it('should return base fallback mode when dynamic params are enabled and empty root params', () => {
1062+
const result = calculateFallbackMode(
1063+
true,
1064+
[],
1065+
FallbackMode.BLOCKING_STATIC_RENDER
1066+
)
1067+
1068+
expect(result).toBe(FallbackMode.BLOCKING_STATIC_RENDER)
1069+
})
1070+
1071+
it('should return NOT_FOUND when dynamic params are enabled but no base fallback mode provided', () => {
1072+
const result = calculateFallbackMode(true, [], undefined)
1073+
1074+
expect(result).toBe(FallbackMode.NOT_FOUND)
1075+
})
1076+
1077+
it('should prioritize root params over base fallback mode', () => {
1078+
const result = calculateFallbackMode(
1079+
true,
1080+
['rootParam'],
1081+
FallbackMode.PRERENDER
1082+
)
1083+
1084+
expect(result).toBe(FallbackMode.BLOCKING_STATIC_RENDER)
1085+
})
1086+
1087+
it('should handle single root param correctly', () => {
1088+
const result = calculateFallbackMode(
1089+
true,
1090+
['singleParam'],
1091+
FallbackMode.PRERENDER
1092+
)
1093+
1094+
expect(result).toBe(FallbackMode.BLOCKING_STATIC_RENDER)
1095+
})
1096+
})

0 commit comments

Comments
 (0)