Skip to content

Commit 43fce31

Browse files
committed
feat(@angular/ssr): Add support for route matchers with fine-grained render mode control
This commit adds support for custom route matchers in Angular SSR, allowing fine-grained control over the `renderMode` (Server, Client) for individual routes, including those defined with matchers. Routes with custom matchers are **not** supported during prerendering and must explicitly define a `renderMode` of either server or client. The following configuration demonstrates how to use glob patterns (including recursive `**`) to define server-side rendering (SSR) or client-side rendering (CSR) for specific parts of the 'product' route and its child routes. ```typescript // app.routes.ts import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', component: DummyComponent, }, { path: 'product', component: DummyComponent, children: [ { path: '', component: DummyComponent, }, { path: 'list', component: DummyComponent, }, { matcher: () => null, // Example custom matcher (always returns null) component: DummyComponent, }, ], }, ]; ``` ```typescript // app.routes.server.ts import { RenderMode, ServerRoute } from '@angular/ssr'; export const serverRoutes: ServerRoute[] = [ { path: '**', renderMode: RenderMode.Client }, { path: 'product', renderMode: RenderMode.Prerender }, { path: 'product/list', renderMode: RenderMode.Prerender }, { path: 'product/**/overview/details', renderMode: RenderMode.Server }, ]; ``` Closes #29284
1 parent 832bfff commit 43fce31

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

packages/angular/ssr/test/routes/ng-routes_spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,24 @@ describe('extractRoutesAndCreateRouteTree', () => {
167167
`The 'invalid' route does not match any route defined in the server routing configuration`,
168168
);
169169
});
170+
171+
it('should error when a route with a matcher when render mode is Prerender.', async () => {
172+
setAngularAppTestingManifest(
173+
[{ matcher: () => null, component: DummyComponent }],
174+
[
175+
{
176+
path: '**',
177+
renderMode: RenderMode.Prerender,
178+
},
179+
],
180+
);
181+
182+
const { errors } = await extractRoutesAndCreateRouteTree({ url });
183+
expect(errors[0]).toContain(
184+
`The route '**' is set for prerendering but has a defined matcher. ` +
185+
`Routes with matchers cannot use prerendering. Please specify a different 'renderMode'.`,
186+
);
187+
});
170188
});
171189

172190
describe('when `invokeGetPrerenderParams` is true', () => {
@@ -330,6 +348,54 @@ describe('extractRoutesAndCreateRouteTree', () => {
330348
});
331349
});
332350

351+
it('should extract routes with a route level matcher', async () => {
352+
setAngularAppTestingManifest(
353+
[
354+
{
355+
path: '',
356+
component: DummyComponent,
357+
},
358+
{
359+
path: 'product',
360+
component: DummyComponent,
361+
children: [
362+
{
363+
path: '',
364+
component: DummyComponent,
365+
},
366+
{
367+
matcher: () => null,
368+
component: DummyComponent,
369+
},
370+
{
371+
path: 'list',
372+
component: DummyComponent,
373+
},
374+
],
375+
},
376+
],
377+
[
378+
{ path: '**', renderMode: RenderMode.Client },
379+
{ path: 'product', renderMode: RenderMode.Client },
380+
{ path: 'product/*', renderMode: RenderMode.Client },
381+
{ path: 'product/**/overview/details', renderMode: RenderMode.Server },
382+
{ path: 'product/**/overview', renderMode: RenderMode.Server },
383+
{ path: 'product/**/overview/about', renderMode: RenderMode.Server },
384+
],
385+
);
386+
387+
const { routeTree, errors } = await extractRoutesAndCreateRouteTree({ url });
388+
expect(errors).toHaveSize(0);
389+
expect(routeTree.toObject()).toEqual([
390+
{ route: '/', renderMode: RenderMode.Client },
391+
{ route: '/product', renderMode: RenderMode.Client },
392+
{ route: '/product/**/overview', renderMode: RenderMode.Server },
393+
{ route: '/product/**/overview/details', renderMode: RenderMode.Server },
394+
{ route: '/product/**/overview/about', renderMode: RenderMode.Server },
395+
{ route: '/product/list', renderMode: RenderMode.Client },
396+
]);
397+
});
398+
333399
it('should extract nested redirects that are not explicitly defined.', async () => {
334400
setAngularAppTestingManifest(
335401
[

0 commit comments

Comments
 (0)