Skip to content

Commit e628cbd

Browse files
bgublubieowoce
authored andcommitted
feat: handle rootParams in routes (finish changes) (#81086)
This PR enables the use of root params in route handlers by: 1. Modifying the app route module to extract root params from context.params based on rootParamNames 2. Passing these root params to the request store in route handlers 3. Removing the error that previously prevented using root params in route handlers 4. Simplifying the rootParams handling by directly using string arrays instead of ParamInfo objects 5. Replacing the custom encodeToBase64 utility with standard Buffer.from().toString('base64')
1 parent 943a8bf commit e628cbd

File tree

10 files changed

+68
-31
lines changed

10 files changed

+68
-31
lines changed

crates/next-api/src/app.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use next_core::{
44
app_segment_config::NextSegmentConfig,
55
app_structure::{
66
AppPageLoaderTree, CollectedRootParams, Entrypoint as AppEntrypoint,
7-
Entrypoints as AppEntrypoints, FileSystemPathVec, MetadataItem, collect_root_params,
8-
get_entrypoints,
7+
Entrypoints as AppEntrypoints, FileSystemPathVec, MetadataItem, RootParamVecOption,
8+
collect_root_params, get_entrypoints,
99
},
1010
get_edge_resolve_options_context, get_next_package,
1111
next_app::{
@@ -990,7 +990,10 @@ pub fn app_entry_point_to_route(
990990
) -> Vc<Route> {
991991
match entrypoint {
992992
AppEntrypoint::AppPage {
993-
pages, loader_tree, ..
993+
pages,
994+
loader_tree,
995+
root_params,
996+
..
994997
} => Route::AppPage(
995998
pages
996999
.into_iter()
@@ -1004,6 +1007,7 @@ pub fn app_entry_point_to_route(
10041007
},
10051008
app_project,
10061009
page: page.clone(),
1010+
root_params,
10071011
}
10081012
.resolved_cell(),
10091013
),
@@ -1015,6 +1019,7 @@ pub fn app_entry_point_to_route(
10151019
},
10161020
app_project,
10171021
page,
1022+
root_params,
10181023
}
10191024
.resolved_cell(),
10201025
),
@@ -1025,6 +1030,7 @@ pub fn app_entry_point_to_route(
10251030
page,
10261031
path,
10271032
root_layouts,
1033+
root_params,
10281034
..
10291035
} => Route::AppRoute {
10301036
original_name: page.to_string().into(),
@@ -1033,17 +1039,24 @@ pub fn app_entry_point_to_route(
10331039
ty: AppEndpointType::Route { path, root_layouts },
10341040
app_project,
10351041
page,
1042+
root_params,
10361043
}
10371044
.resolved_cell(),
10381045
),
10391046
},
1040-
AppEntrypoint::AppMetadata { page, metadata, .. } => Route::AppRoute {
1047+
AppEntrypoint::AppMetadata {
1048+
page,
1049+
metadata,
1050+
root_params,
1051+
..
1052+
} => Route::AppRoute {
10411053
original_name: page.to_string().into(),
10421054
endpoint: ResolvedVc::upcast(
10431055
AppEndpoint {
10441056
ty: AppEndpointType::Metadata { metadata },
10451057
app_project,
10461058
page,
1059+
root_params,
10471060
}
10481061
.resolved_cell(),
10491062
),
@@ -1081,6 +1094,7 @@ struct AppEndpoint {
10811094
ty: AppEndpointType,
10821095
app_project: ResolvedVc<AppProject>,
10831096
page: AppPage,
1097+
root_params: ResolvedVc<RootParamVecOption>,
10841098
}
10851099

10861100
#[turbo_tasks::value_impl]
@@ -1127,6 +1141,7 @@ impl AppEndpoint {
11271141
self.app_project.project().project_path().owned().await?,
11281142
config,
11291143
next_config,
1144+
*self.root_params,
11301145
))
11311146
}
11321147

crates/next-core/src/next_app/app_route_entry.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub async fn get_app_route_entry(
3535
project_root: FileSystemPath,
3636
original_segment_config: Option<Vc<NextSegmentConfig>>,
3737
next_config: Vc<NextConfig>,
38+
root_params: Vc<crate::app_structure::RootParamVecOption>,
3839
) -> Result<Vc<AppEntry>> {
3940
let segment_from_source = parse_segment_config_from_source(source);
4041
let config = if let Some(original_segment_config) = original_segment_config {
@@ -70,6 +71,18 @@ pub async fn get_app_route_entry(
7071
.map(RcStr::from)
7172
.unwrap_or_else(|| "\"\"".into());
7273

74+
// Convert root params to JSON array of parameter names
75+
let root_param_names = if let Some(root_params_vec) = &*root_params.await? {
76+
serde_json::to_string(
77+
&root_params_vec
78+
.iter()
79+
.map(|param| param.param.as_str())
80+
.collect::<Vec<_>>(),
81+
)?
82+
} else {
83+
"[]".to_string()
84+
};
85+
7386
// Load the file from the next.js codebase.
7487
let virtual_source = load_next_js_template(
7588
"app-route.js",
@@ -85,7 +98,7 @@ pub async fn get_app_route_entry(
8598
},
8699
fxindexmap! {
87100
"nextConfigOutput" => output_type,
88-
// "rootParamNames" => ... // TODO(root-params)
101+
"rootParamNames" => root_param_names.into(),
89102
},
90103
fxindexmap! {},
91104
)

crates/next-core/src/next_app/metadata/route.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub async fn get_app_metadata_route_entry(
103103
project_root,
104104
Some(segment_config),
105105
next_config,
106+
Vc::cell(None), // Metadata routes don't have root params
106107
))
107108
}
108109

packages/next/src/build/entries.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,10 @@ export async function createEntrypoints(
718718
page,
719719
name: serverBundlePath,
720720
pagePath: absolutePagePath,
721-
rootParams: (staticInfo as AppPageStaticInfo).rootParams!,
721+
rootParams:
722+
(staticInfo as AppPageStaticInfo).rootParams?.map(
723+
(p) => p.param
724+
) || [],
722725
appDir,
723726
appPaths: matchedAppPaths,
724727
pageExtensions,
@@ -797,7 +800,10 @@ export async function createEntrypoints(
797800
name: serverBundlePath,
798801
page,
799802
pagePath: absolutePagePath,
800-
rootParams: (staticInfo as AppPageStaticInfo).rootParams!,
803+
rootParams:
804+
(staticInfo as AppPageStaticInfo).rootParams?.map(
805+
(p) => p.param
806+
) || [],
801807
appDir: appDir!,
802808
appPaths: matchedAppPaths,
803809
pageExtensions,

packages/next/src/build/webpack/loaders/next-app-loader/create-app-route-code.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { AppPathnameNormalizer } from '../../../../server/normalizers/built/app/
1111
import { loadEntrypoint } from '../../../load-entrypoint'
1212
import type { PageExtensions } from '../../../page-extensions-type'
1313
import { getFilenameAndExtension } from '../next-metadata-route-loader'
14-
import type { ParamInfo } from '../next-root-params-loader'
1514

1615
export async function createAppRouteCode({
1716
appDir,
@@ -27,7 +26,7 @@ export async function createAppRouteCode({
2726
name: string
2827
page: string
2928
pagePath: string
30-
rootParams: ParamInfo[]
29+
rootParams: string[]
3130
resolveAppRoute: (
3231
pathname: string
3332
) => Promise<string | undefined> | string | undefined
@@ -81,7 +80,7 @@ export async function createAppRouteCode({
8180
},
8281
{
8382
nextConfigOutput: JSON.stringify(nextConfigOutput),
84-
rootParamNames: JSON.stringify(rootParams.map((p) => p.param)),
83+
rootParamNames: JSON.stringify(rootParams),
8584
}
8685
)
8786
}

packages/next/src/build/webpack/loaders/next-app-loader/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@ import type { PageExtensions } from '../../../page-extensions-type'
3333
import { PARALLEL_ROUTE_DEFAULT_PATH } from '../../../../client/components/builtin/default'
3434
import type { Compilation } from 'webpack'
3535
import { createAppRouteCode } from './create-app-route-code'
36-
import type { ParamInfo } from '../next-root-params-loader'
3736

3837
export type AppLoaderOptions = {
3938
name: string
4039
page: string
4140
pagePath: string
42-
rootParams: ParamInfo[]
41+
rootParams: string[]
4342
appDir: string
4443
appPaths: readonly string[] | null
4544
preferredRegion: string | string[] | undefined

packages/next/src/server/dev/hot-reloader-webpack.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,10 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface {
981981
entryData.absolutePagePath
982982
).replace(/\\/g, '/')
983983
),
984-
rootParams: (staticInfo as AppPageStaticInfo).rootParams!,
984+
rootParams:
985+
(staticInfo as AppPageStaticInfo).rootParams?.map(
986+
(p) => p.param
987+
) || [],
985988
appDir: this.appDir!,
986989
pageExtensions: this.config.pageExtensions,
987990
rootDir: this.dir,
@@ -1105,7 +1108,10 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface {
11051108
page,
11061109
appPaths: entryData.appPaths,
11071110
pagePath,
1108-
rootParams: (staticInfo as AppPageStaticInfo).rootParams!,
1111+
rootParams:
1112+
(staticInfo as AppPageStaticInfo).rootParams?.map(
1113+
(p) => p.param
1114+
) || [],
11091115
appDir: this.appDir!,
11101116
pageExtensions: this.config.pageExtensions,
11111117
rootDir: this.dir,

packages/next/src/server/request/root-params.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,6 @@ export async function getRootParam(paramName: string): Promise<ParamValue> {
206206

207207
const actionStore = actionAsyncStorage.getStore()
208208
if (actionStore) {
209-
if (actionStore.isAppRoute) {
210-
// TODO(root-params): add support for route handlers
211-
throw new Error(
212-
`Route ${workStore.route} used ${apiName} inside a Route Handler. Support for this API in Route Handlers is planned for a future version of Next.js.`
213-
)
214-
}
215209
if (actionStore.isAction) {
216210
// Actions are not fundamentally tied to a route (even if they're always submitted from some page),
217211
// so root params would be inconsistent if an action is called from multiple roots.

packages/next/src/server/route-modules/app-route/module.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,7 @@ export class AppRouteRouteModule extends RouteModule<
388388
(prerenderStore = {
389389
type: 'prerender',
390390
phase: 'action',
391-
// This replicates prior behavior where rootParams is empty in routes
392-
// TODO we need to make this have the proper rootParams for this route
393-
rootParams: {},
391+
rootParams: requestStore.rootParams,
394392
implicitTags,
395393
renderSignal: prospectiveController.signal,
396394
controller: prospectiveController,
@@ -484,7 +482,7 @@ export class AppRouteRouteModule extends RouteModule<
484482
const finalRoutePrerenderStore: PrerenderStore = (prerenderStore = {
485483
type: 'prerender',
486484
phase: 'action',
487-
rootParams: {},
485+
rootParams: requestStore.rootParams,
488486
implicitTags,
489487
renderSignal: finalController.signal,
490488
controller: finalController,
@@ -569,7 +567,7 @@ export class AppRouteRouteModule extends RouteModule<
569567
prerenderStore = {
570568
type: 'prerender-legacy',
571569
phase: 'action',
572-
rootParams: {},
570+
rootParams: requestStore.rootParams,
573571
implicitTags,
574572
revalidate: defaultRevalidate,
575573
expire: INFINITE_CACHE,
@@ -694,10 +692,20 @@ export class AppRouteRouteModule extends RouteModule<
694692
null
695693
)
696694

695+
// Extract root params from context.params based on rootParamNames
696+
const rootParams: Record<string, string | string[] | undefined> = {}
697+
if (context.params && this.rootParamNames.length > 0) {
698+
for (const paramName of this.rootParamNames) {
699+
if (paramName in context.params) {
700+
rootParams[paramName] = context.params[paramName]
701+
}
702+
}
703+
}
704+
697705
const requestStore = createRequestStoreForAPI(
698706
req,
699707
req.nextUrl,
700-
{}, // TODO: real rootParams
708+
rootParams,
701709
implicitTags,
702710
undefined,
703711
context.prerenderManifest.preview
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { lang, locale } from 'next/root-params'
22

33
export async function GET() {
4-
return Response.json(
5-
// TODO(root-params): We're missing some wiring to set `requestStore.rootParams`,
6-
// so both of these will currently return undefined
7-
{ lang: await lang(), locale: await locale() }
8-
)
4+
return Response.json({ lang: await lang(), locale: await locale() })
95
}

0 commit comments

Comments
 (0)