feat: add option to disable route extraction feature
Command
build
Description
When migrating apps to SSR, route extraction basically gets in the way and makes it harder to understand where the issue/error is coming from, as it tries to run before everything else either during build or server.
Instead of fixing other SSR related errors, I have to go and fix route extraction issues.
Also, there is no way to know if route extraction is happening. But even if it was possible the code would be full of checks on what mode is Angular running, and having to deal with all the states instead of just handling a normal case of -> open the url -> render angular -> serve html.
Describe the solution you'd like
Add a flag to disable route extraction during build or serve totally, at least just for development purposes. When routes are dynamically loaded based on config, having the extraction happen before the config can be loaded breaks the dynamic apps. If I build my app with a config that has 3 routes, and then on runtime I want to use another config.json file to load other routes (that also include the first three but with different configs), would cause my app to misbehave because of the initial extracted routes.
NG_BUILD_PARTIAL_SSR=1 partially solves this by skipping the route extraction and doing it initial request.
Repo
Comment out line 9 to better understand that serving or building the app is painful, even though on normal conditions opening localhost:4200/config.json loads fine.
https://github.com/eneajaho/dx-issue/blob/main/src/main.server.ts#L9
Also, having errors without stacktrace showing up is not that great.
The current behavior is expected due Node.js fetch URL Requirement, you are using Node.js fetch, which requires absolute URLs. Relative URLs are not valid in this environment and will cause an error. Angular's built-in HttpClient has logic to handle URL transformations that fetch lacks.
I am curios why this configuration loading approach was chosen? IMHO, using DI to load the config is much cleaner, and in your case actually would be more performant as the configuration will not be requested both on the browser and client.
Example:
export const appConfig = {
providers: [
// other providers.
provideHttpClient(withFetch()),
{
provide: CONFIG_TOKEN,
useFactory: () => {
const httpClient = inject(HttpClient);
return httpClient.get('/config.json').toPromise();
},
},
],
};
@alan-agius4 Your alternative will provide a Promise<Config>, not a Config, won't it?
So all the code dealing with this config would have to be asynchronous and handle a potential loading error, whereas the original solution allows the code to be synchronous.
One other alternative would be to use the provideAppInitializer
@Injectable()
export class ConfigService {
private config: any;
setConfig(data: any): void {
this.config = data;
}
getConfig(): any {
return this.config;
}
}
function initializeAppConfig() {
const http = inject(HttpClient);
const configService = inject(ConfigService);
return firstValueFrom(
http.get('/config.json').pipe(
tap(config => {
configService.setConfig(config);
})
)
);
}
bootstrapApplication(AppComponent, {
providers: [
…,
provideAppInitializer(initializeAppConfig),
],
});
We use angular server side in a AWS Lambda Function and then cache all the responses on the CDN.
Therefore we never want a Pre-Rendering with SSG and would also like to completely disable this "Route extraction".
It makes it particular annoying as we're providing env vars to the Lambda like the ClientConfig (so we have it completely sync in angular by reading from process.env in ssr and then using TransferState to give to the browser).
As consequence we now have to provide those env vars - or at least dummy values - already when building which is quite confusing.
Also when we would use APP_INITIALIZER we would face another problem: while building the angular app, the api to fetch the data from is not yet available.
@alan-agius4 any words on the actual issue? Imo it would really make sense to allow to disable the whole route extraction / SSG step when using SSR.
You can shift route extraction from build time to runtime by setting the NG_BUILD_PARTIAL_SSR=1 environment variable when running ng build.
Please note that this impacts runtime performance, as route extraction must now occur during execution. The extraction is needed even when an application is fully SSR without any static routes as this is needed to construct the server routing table.
Yes I know - but this is not a solution as it will slow down the lambda for every cold start..
@alan-agius4 Maybe I miss something but why shouldn't it be possible to simply skip the SSG / "Route extraction" part completely? I mean it was possible before.
Yes I know - but this is not a solution as it will slow down the lambda for every cold start..
Yes, and hence why the route extraction is needed during build time.
Maybe I miss something but why shouldn't it be possible to simply skip the SSG / "Route extraction" part completely? I mean it was possible before.
Route extraction is required to generate a server routing table for SSR. This serves as a filter to ensure performance efficiency. It ensures the application only bootstraps for valid Angular routes, Without this table, middleware fallbacks can cause the app to bootstrap unnecessarily for static or missing assets. Example: If a page requests a missing image (http://example.com/invalid.jpeg), the server shouldn't spin up the Angular app to handle that error. The routing table prevents this execution.
I see. not connected to SSG then.
But still, it definitely could work without this Route Extraction since it is only used for performance efficiency. For us this "optimization" is useless as we only call the express app to render the page - static assets like the js files are delivered directly from S3/Blob Storage.
So the question is still here: why not provide an option to opt out from this "optimization"?
imho this should have been made as opt in from the beginning. This current behavior breaks all our production apps and wasn't even listed somewhere in the changelog afaik.