Volodymyr Mishyn
Volodymyr Mishyn

Reputation: 216

Angular 17: SSR with i18n alongside

I am trying to build Angular 17 application with SSR, using built in i18n mechanism. And I don't get how to configure it to work together. v17 is brand new and there are blank spaces in documentation and not a lot of examples over the Internet.

When creating simple application with Angular+SSR it creates server.ts alongside base application

// imports

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const serverDistFolder = dirname(fileURLToPath(import.meta.url));
  const browserDistFolder = resolve(serverDistFolder, '../browser');
  const indexHtml = join(serverDistFolder, 'index.server.html');

  const commonEngine = new CommonEngine();

  server.set('view engine', 'html');
  server.set('views', browserDistFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(browserDistFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Angular engine
  server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;

    commonEngine
      .render({
        bootstrap,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

run();

and after building the app it creates the following structure in dist folder:

# simple-ssr

* [browser/](./simple-ssr/browser)
  * [first/](./simple-ssr/browser/first)
    * [index.html](./simple-ssr/browser/first/index.html)
  * [home/](./simple-ssr/browser/home)
    * [index.html](./simple-ssr/browser/home/index.html)
  * [second/](./simple-ssr/browser/second)
    * [index.html](./simple-ssr/browser/second/index.html)
  * [favicon.ico](./simple-ssr/browser/favicon.ico)
  * [index.html](./simple-ssr/browser/index.html)
  * [main-OUKHBY7S.js](./simple-ssr/browser/main-OUKHBY7S.js)
  * [polyfills-LZBJRJJE.js](./simple-ssr/browser/polyfills-LZBJRJJE.js)
  * [styles-Y4IFJ72L.css](./simple-ssr/browser/styles-Y4IFJ72L.css)
* [server/](./simple-ssr/server)
  * [chunk-53JWIC36.mjs](./simple-ssr/server/chunk-53JWIC36.mjs)
  * ... other chunks
  * [index.server.html](./simple-ssr/server/index.server.html)
  * [main.server.mjs](./simple-ssr/server/main.server.mjs)
  * [polyfills.server.mjs](./simple-ssr/server/polyfills.server.mjs)
  * [render-utils.server.mjs](./simple-ssr/server/render-utils.server.mjs)
  * [server.mjs](./simple-ssr/server/server.mjs)
* [3rdpartylicenses.txt](./simple-ssr/3rdpartylicenses.txt)
* [prerendered-routes.json](./simple-ssr/prerendered-routes.json)

running node dist/simple-ssr/server/server.mjs starts the Express server and everything works fine.

The problem starts after adding Angular built-in i18n. Afer seetting up everything and localizing the app it works okay with ng serve. But building dist version it generates another nested structure:

# simple-ssr-with-i18n

* [browser/](./my-app/browser)
  * [en-US/](./my-app/browser/en-US)
    * [assets/](./my-app/browser/en-US/assets)
      * [img/](./my-app/browser/en-US/assets/img)
    * [first/](./my-app/browser/en-US/first)
      * [index.html](./my-app/browser/en-US/first/index.html)
    * [home/](./my-app/browser/en-US/home)
      * [index.html](./my-app/browser/en-US/home/index.html)
    * [second/](./my-app/browser/en-US/second)
      * [index.html](./my-app/browser/en-US/second/index.html)
    * [favicon.ico](./my-app/browser/en-US/favicon.ico)
    * [index.html](./my-app/browser/en-US/index.html)
    * [main-VKL3SVOT.js](./my-app/browser/en-US/main-VKL3SVOT.js)
    * [polyfills-LQWQKVKW.js](./my-app/browser/en-US/polyfills-LQWQKVKW.js)
    * [styles-UTKJIBJ7.css](./my-app/browser/en-US/styles-UTKJIBJ7.css)
  * [uk/](./my-app/browser/uk)
    * [assets/](./my-app/browser/uk/assets)
      * [img/](./my-app/browser/uk/assets/img)
    * [first/](./my-app/browser/uk/first)
      * [index.html](./my-app/browser/uk/first/index.html)
    * [home/](./my-app/browser/uk/home)
      * [index.html](./my-app/browser/uk/home/index.html)
    * [second/](./my-app/browser/uk/second)
      * [index.html](./my-app/browser/uk/second/index.html)
    * [favicon.ico](./my-app/browser/uk/favicon.ico)
    * [index.html](./my-app/browser/uk/index.html)
    * [main-VKL3SVOT.js](./my-app/browser/uk/main-VKL3SVOT.js)
    * [polyfills-LQWQKVKW.js](./my-app/browser/uk/polyfills-LQWQKVKW.js)
    * [styles-UTKJIBJ7.css](./my-app/browser/uk/styles-UTKJIBJ7.css)
* [server/](./my-app/server)
  * [en-US/](./my-app/server/en-US)
    * [index.server.html](./my-app/server/en-US/index.server.html)
    * [main.server.mjs](./my-app/server/en-US/main.server.mjs)
    * [polyfills.server.mjs](./my-app/server/en-US/polyfills.server.mjs)
    * [render-utils.server.mjs](./my-app/server/en-US/render-utils.server.mjs)
    * [server.mjs](./my-app/server/en-US/server.mjs)
  * [uk/](./my-app/server/uk)
    * [index.server.html](./my-app/server/uk/index.server.html)
    * [main.server.mjs](./my-app/server/uk/main.server.mjs)
    * [polyfills.server.mjs](./my-app/server/uk/polyfills.server.mjs)
    * [render-utils.server.mjs](./my-app/server/uk/render-utils.server.mjs)
    * [server.mjs](./my-app/server/uk/server.mjs)
* [3rdpartylicenses.txt](./my-app/3rdpartylicenses.txt)
* [prerendered-routes.json](./my-app/prerendered-routes.json)

folder structure for i18n & ssr

Obviously pathes in server.ts and, as a result, in dist/simple-ssr-with-i18n/server/en-US/server.mjs are not set up right for working correctly with different locale versions. And as I imagined it should work simply with following changes

  const languageFolder = basename(serverDistFolder);
  const languagePath = `/${languageFolder}/`;
  const browserDistFolder = resolve(
    serverDistFolder,
    '../../browser' + languagePath
  );

and running separate express server instance for each locale. (Ideally one server serving all locales, of course)

But all of my attempts were not successful, running node dist/simple-ssr/server/server.mjs leads to unresponsive site with errors fetching static .js file chunks.

May somebody provide some comprehensive example for server.ts and setting up i18n+ssr together?

Only relible article I found is Angular-universal-and-i18n-working-together but it's outdated, and i get built-time errors on baseHref step. P.S. Chatgpt is aware of Angular 15 and Universal, so it's not also very helpfull.

Upvotes: 7

Views: 2681

Answers (1)

Volodymyr Mishyn
Volodymyr Mishyn

Reputation: 216

Found another blog post about the topic Building multi-language applications with Server Side Rendering in Angular 17

Contacted the developers, and they kindly provided example for Ssr I18n Angular17.

Everything works like a charm. Highly appreciate it.

Upvotes: 10

Related Questions