Skip to content

Angular 19 AngularAppEngine getEntryPointExports does not handle source locale paths well #29350

Closed as duplicate of#28967
@vzarskus

Description

@vzarskus

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions