Description
Which @angular/* package(s) are the source of the bug?
platform-server
Is this a regression?
No
Description
Angular 19 with developer preview SSR features, using the new server engine.
What I'm about to describe is either buggy/incomplete behavior or my setup is flawed.
Basically, what I am trying to do is setup a multilanguage server using AngularNodeAppEngine
. I have a setup where source (which is English) language is setup with no additional slug. Meaning that /
or /somepage
should get an English version. While other languages, for example German, do have a language slug: /de
, /de/somepage
.
I was able to make the new engine work with German language fully and with English - only the home page (/
). Any other English path (e.g. /somepage
) results in AngularNodeAppEngine
not being able to render anything.
My setup is also different from, what would you probably call, "default" in that locale codes
are with region (e.g. de-DE), but baseHrefs
are without the region part. This will be visible in project.json
later.
I will provide an excerpt from project.json
and full server.ts
here. If this won't be enough to understand my issue, I can create a reproduction project, but hopefully this will be enough 🙏.
project.json
:
"i18n": {
"sourceLocale": {
"code": "en-US",
"baseHref": "/"
},
"locales": {
"de-DE": {
"translation": "src/locale/messages.de-DE.xlf",
"baseHref": "/de/"
}
}
}
and localize
is set to true
for build
option.
Here is server.ts
. It is basically the default one, but I have added two static file middlewares instead of the default one. With the default setup Angular will look for browser files in /de/ folder instead of /de-DE/ where they were actually generated.
import {
AngularNodeAppEngine,
createNodeRequestHandler,
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import 'zone.js/node';
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
app.use(
'/de',
express.static(join(browserDistFolder, 'de-DE'), {
maxAge: '1y',
index: false,
redirect: false,
}),
);
app.use(
'/',
express.static(join(browserDistFolder, 'en-US'), {
maxAge: '1y',
index: false,
redirect: false,
}),
);
app.get('/**', (req, res, next) => {
angularApp
.handle(req)
.then(response =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 3000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
export const reqHandler = createNodeRequestHandler(app);
When I run the server with an English path that is not the home page (e.g. /somepage
), I get the default Node.js "Cannot Get..." response, because the "next()" function is being triggered in the server meaning that Angular engine did not provide a meaningful response.
.then(response =>
response ? writeResponseToNodeResponse(response, res) : next() <--- next is triggered
)
I went in to the source code of AngularAppEngine
(@angulr/ssr/fesm2022/ssr.mjs
) and I see that the empty response results from getEntryPointExports
hitting the
if (!entryPoint) {
return undefined;
}
part.
I tried printing out the manifest (this.manifest
) and this is what I get with the before mentioned project.json
setup:
{
basePath: '/',
entryPoints: { '': [Function (anonymous)], de: [Function: de] }
}
The functions when stringified look like this:
Anonymous function: () => import('./en-US/main.server.mjs')
Named function 'de': () => import('./de-DE/main.server.mjs')
The problem is that the potentialLocale
parameter which comes from getPotentialLocaleIdFromUrl
function returns an empty string (which maps to the English bundle) only for the English home page. For other paths it returns whatever the path is. For /somepage
it returns to "somepage" and then the
const entryPoint = entryPoints[potentialLocale];
in getEntryPointExports
obviously cannot find any matching entryPoint.
getPotentialLocaleIdFromUrl
returns empty string ''
only for the home page (/
).
If I change the code to default to the empty string entry point ''
the server works flawlessly, everything gets served as expected.
The German version works flawlessly by default because getPotentialLocaleIdFromUrl
is always able to extract /de/
from the beginning of the url.
So my question is - is there something wrong with my setup? I cannot change the setup of having English url with no slug, because we would need to migrate all our english urls in a big project - it's just not doable. How can I accommodate my use case with AngularAppEngine
? Is this perhaps a bug and could be fixed on your side?
Please provide a link to a minimal reproduction of the bug
No response
Please provide the exception or error you saw
Please provide the environment you discovered this bug in (run ng version
)
Angular CLI: 19.0.6
Node: 20.18.0
Package Manager: npm 11.0.0
OS: win32 x64
Angular: 19.0.5
... animations, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, platform-server, router
Package Version
---------------------------------------------------------
@angular-devkit/architect 0.1900.6
@angular-devkit/build-angular 19.0.6
@angular-devkit/core 19.0.6
@angular-devkit/schematics 19.0.6
@angular/cdk 19.0.4
@angular/cli 19.0.6
@angular/ssr 19.0.6
@schematics/angular 19.0.6
ng-packagr 19.0.1
rxjs 7.8.1
typescript 5.6.3
zone.js 0.15.0
Anything else?
No response