@@ -12,6 +12,7 @@ import { ServerAssets } from './assets';
12
12
import { Hooks } from './hooks' ;
13
13
import { getAngularAppManifest } from './manifest' ;
14
14
import { RenderMode } from './routes/route-config' ;
15
+ import { RouteTreeNodeMetadata } from './routes/route-tree' ;
15
16
import { ServerRouter } from './routes/router' ;
16
17
import { sha256 } from './utils/crypto' ;
17
18
import { InlineCriticalCssProcessor } from './utils/inline-critical-css' ;
@@ -107,10 +108,7 @@ export class AngularServerApp {
107
108
* @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
108
109
*/
109
110
render ( request : Request , requestContext ?: unknown ) : Promise < Response | null > {
110
- return Promise . race ( [
111
- this . createAbortPromise ( request ) ,
112
- this . handleRendering ( request , /** isSsrMode */ true , requestContext ) ,
113
- ] ) ;
111
+ return this . handleAbortableRendering ( request , /** isSsrMode */ true , undefined , requestContext ) ;
114
112
}
115
113
116
114
/**
@@ -125,10 +123,7 @@ export class AngularServerApp {
125
123
renderStatic ( url : URL , signal ?: AbortSignal ) : Promise < Response | null > {
126
124
const request = new Request ( url , { signal } ) ;
127
125
128
- return Promise . race ( [
129
- this . createAbortPromise ( request ) ,
130
- this . handleRendering ( request , /** isSsrMode */ false ) ,
131
- ] ) ;
126
+ return this . handleAbortableRendering ( request , /** isSsrMode */ false ) ;
132
127
}
133
128
134
129
/**
@@ -143,10 +138,72 @@ export class AngularServerApp {
143
138
* if available, or `null` if the request does not match a prerendered route or asset.
144
139
*/
145
140
async serve ( request : Request ) : Promise < Response | null > {
146
- const url = stripIndexHtmlFromURL ( new URL ( request . url ) ) ;
141
+ return this . handleServe ( request ) ;
142
+ }
143
+
144
+ /**
145
+ * Handles incoming HTTP requests by serving prerendered content or rendering the page.
146
+ *
147
+ * This method first attempts to serve a prerendered page. If the prerendered page is not available,
148
+ * it falls back to rendering the requested page using server-side rendering. The function returns
149
+ * a promise that resolves to the appropriate HTTP response.
150
+ *
151
+ * @param request - The incoming HTTP request to be processed.
152
+ * @param requestContext - Optional additional context for rendering, such as request metadata.
153
+ * @returns A promise that resolves to the HTTP response object resulting from the request handling,
154
+ * or null if no matching content is found.
155
+ */
156
+ async process ( request : Request , requestContext ?: unknown ) : Promise < Response | null > {
157
+ const url = new URL ( request . url ) ;
147
158
this . router ??= await ServerRouter . from ( this . manifest , url ) ;
148
159
149
- const matchedRoute = this . router . match ( new URL ( request . url ) ) ;
160
+ const matchedRoute = this . router . match ( url ) ;
161
+ if ( ! matchedRoute ) {
162
+ // Not a known Angular route.
163
+ return null ;
164
+ }
165
+
166
+ if ( matchedRoute . renderMode === RenderMode . Prerender ) {
167
+ const response = await this . handleServe ( request , matchedRoute ) ;
168
+ if ( response ) {
169
+ // During development, prerendered pages may not exist, hence fallback to render on the fly.
170
+ return response ;
171
+ }
172
+ }
173
+
174
+ return this . handleAbortableRendering (
175
+ request ,
176
+ /** isSsrMode */ true ,
177
+ matchedRoute ,
178
+ requestContext ,
179
+ ) ;
180
+ }
181
+
182
+ /**
183
+ * Retrieves the matched route for the incoming request based on the request URL.
184
+ *
185
+ * @param request - The incoming HTTP request to match against routes.
186
+ * @returns A promise that resolves to the matched route metadata or `undefined` if no route matches.
187
+ */
188
+ private async getMatchedRoute ( request : Request ) : Promise < RouteTreeNodeMetadata | undefined > {
189
+ this . router ??= await ServerRouter . from ( this . manifest , new URL ( request . url ) ) ;
190
+
191
+ return this . router . match ( new URL ( request . url ) ) ;
192
+ }
193
+
194
+ /**
195
+ * Handles serving a prerendered static asset if available for the matched route.
196
+ *
197
+ * @param request - The incoming HTTP request for serving a static page.
198
+ * @param matchedRoute - Optional parameter representing the metadata of the matched route for rendering.
199
+ * If not provided, the method attempts to find a matching route based on the request URL.
200
+ * @returns A promise that resolves to a `Response` object if the prerendered page is found, or `null`.
201
+ */
202
+ private async handleServe (
203
+ request : Request ,
204
+ matchedRoute ?: RouteTreeNodeMetadata ,
205
+ ) : Promise < Response | null > {
206
+ matchedRoute ??= await this . getMatchedRoute ( request ) ;
150
207
if ( ! matchedRoute ) {
151
208
return null ;
152
209
}
@@ -156,7 +213,8 @@ export class AngularServerApp {
156
213
return null ;
157
214
}
158
215
159
- const assetPath = stripLeadingSlash ( joinUrlParts ( url . pathname , 'index.html' ) ) ;
216
+ const { pathname } = stripIndexHtmlFromURL ( new URL ( request . url ) ) ;
217
+ const assetPath = stripLeadingSlash ( joinUrlParts ( pathname , 'index.html' ) ) ;
160
218
if ( ! this . assets . hasServerAsset ( assetPath ) ) {
161
219
return null ;
162
220
}
@@ -176,41 +234,43 @@ export class AngularServerApp {
176
234
}
177
235
178
236
/**
179
- * Handles incoming HTTP requests by serving prerendered content or rendering the page.
180
- *
181
- * This method first attempts to serve a prerendered page. If the prerendered page is not available,
182
- * it falls back to rendering the requested page using server-side rendering. The function returns
183
- * a promise that resolves to the appropriate HTTP response.
237
+ * Handles the server-side rendering process for the given HTTP request, allowing for abortion
238
+ * of the rendering if the request is aborted. This method matches the request URL to a route
239
+ * and performs rendering if a matching route is found.
184
240
*
185
- * @param request - The incoming HTTP request to be processed.
241
+ * @param request - The incoming HTTP request to be processed. It includes a signal to monitor
242
+ * for abortion events.
243
+ * @param isSsrMode - A boolean indicating whether the rendering is performed in server-side
244
+ * rendering (SSR) mode.
245
+ * @param matchedRoute - Optional parameter representing the metadata of the matched route for
246
+ * rendering. If not provided, the method attempts to find a matching route based on the request URL.
186
247
* @param requestContext - Optional additional context for rendering, such as request metadata.
187
- * @returns A promise that resolves to the HTTP response object resulting from the request handling,
188
- * or null if no matching content is found.
189
- */
190
- async process ( request : Request , requestContext ?: unknown ) : Promise < Response | null > {
191
- return ( await this . serve ( request ) ) ?? ( await this . render ( request , requestContext ) ) ;
192
- }
193
-
194
- /**
195
- * Creates a promise that rejects when the request is aborted.
196
248
*
197
- * @param request - The HTTP request to monitor for abortion .
198
- * @returns A promise that never resolves but rejects with an `AbortError` if the request is aborted .
249
+ * @returns A promise that resolves to the rendered response, or null if no matching route is found .
250
+ * If the request is aborted, the promise will reject with an `AbortError`.
199
251
*/
200
- private createAbortPromise ( request : Request ) : Promise < never > {
201
- return new Promise < never > ( ( _ , reject ) => {
202
- request . signal . addEventListener (
203
- 'abort' ,
204
- ( ) => {
205
- const abortError = new Error (
206
- `Request for: ${ request . url } was aborted.\n${ request . signal . reason } ` ,
207
- ) ;
208
- abortError . name = 'AbortError' ;
209
- reject ( abortError ) ;
210
- } ,
211
- { once : true } ,
212
- ) ;
213
- } ) ;
252
+ private async handleAbortableRendering (
253
+ request : Request ,
254
+ isSsrMode : boolean ,
255
+ matchedRoute ?: RouteTreeNodeMetadata ,
256
+ requestContext ?: unknown ,
257
+ ) : Promise < Response | null > {
258
+ return Promise . race ( [
259
+ new Promise < never > ( ( _ , reject ) => {
260
+ request . signal . addEventListener (
261
+ 'abort' ,
262
+ ( ) => {
263
+ const abortError = new Error (
264
+ `Request for: ${ request . url } was aborted.\n${ request . signal . reason } ` ,
265
+ ) ;
266
+ abortError . name = 'AbortError' ;
267
+ reject ( abortError ) ;
268
+ } ,
269
+ { once : true } ,
270
+ ) ;
271
+ } ) ,
272
+ this . handleRendering ( request , isSsrMode , matchedRoute , requestContext ) ,
273
+ ] ) ;
214
274
}
215
275
216
276
/**
@@ -219,25 +279,26 @@ export class AngularServerApp {
219
279
*
220
280
* @param request - The incoming HTTP request to be processed.
221
281
* @param isSsrMode - A boolean indicating whether the rendering is performed in server-side rendering (SSR) mode.
282
+ * @param matchedRoute - Optional parameter representing the metadata of the matched route for rendering.
283
+ * If not provided, the method attempts to find a matching route based on the request URL.
222
284
* @param requestContext - Optional additional context for rendering, such as request metadata.
223
285
*
224
286
* @returns A promise that resolves to the rendered response, or null if no matching route is found.
225
287
*/
226
288
private async handleRendering (
227
289
request : Request ,
228
290
isSsrMode : boolean ,
291
+ matchedRoute ?: RouteTreeNodeMetadata ,
229
292
requestContext ?: unknown ,
230
293
) : Promise < Response | null > {
231
- const url = new URL ( request . url ) ;
232
- this . router ??= await ServerRouter . from ( this . manifest , url ) ;
233
-
234
- const matchedRoute = this . router . match ( url ) ;
294
+ matchedRoute ??= await this . getMatchedRoute ( request ) ;
235
295
if ( ! matchedRoute ) {
236
- // Not a known Angular route.
237
296
return null ;
238
297
}
239
298
240
299
const { redirectTo, status } = matchedRoute ;
300
+ const url = new URL ( request . url ) ;
301
+
241
302
if ( redirectTo !== undefined ) {
242
303
// Note: The status code is validated during route extraction.
243
304
// 302 Found is used by default for redirections
0 commit comments