Skip to content

i18n with SSR configuration #30166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mmart1n opened this issue Apr 23, 2025 · 3 comments
Closed

i18n with SSR configuration #30166

mmart1n opened this issue Apr 23, 2025 · 3 comments

Comments

@mmart1n
Copy link

mmart1n commented Apr 23, 2025

Describe the problem that you experienced

Hi, I'm building an Angular SSR app with localization, I have followed the steps in the official documentation of adding the i18n package and ssr package as well.

The latest generated the following server.ts file:

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import express from 'express';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import bootstrap from './main.server';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');

const app = express();
const commonEngine = new CommonEngine();

/**
 * Example Express Rest API endpoints can be defined here.
 * Uncomment and define endpoints as necessary.
 *
 * Example:
 * ```ts
 * app.get('/api/**', (req, res) => {
 *   // Handle API request
 * });
 * ```
 */

/**
 * Serve static files from /browser
 */
app.get(
  '**',
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html'
  }),
);

/**
 * Handle all other requests by rendering the Angular application.
 */
app.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));
});

/**
 * Start the server if this module is the main entry point.
 * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
 */
if (isMainModule(import.meta.url)) {
  const port = process.env['PORT'] || 4000;
  app.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

export default app;

When i run ng build --localize and then node dist/ssr-new/server/en/server.js the path of all of the static resources cannot be resolved. I see that an angular-app-engine-manifest.mjs file is being genarated.
Any suggestions of how to run locally the produced build and how to deploy it so that the static files are resolved correctly? I assume there are multiple ways (using proxy server or nginx redirects and etc.) but what is the suggested way of doing so and could it be documented somewhere?

Enter the URL of the topic with the problem

No response

Describe what you were looking for in the documentation

No response

Describe the actions that led you to experience the problem

No response

Describe what you want to experience that would fix the problem

No response

Add a screenshot if that helps illustrate the problem

No response

If this problem caused an exception or error, please paste it here


If the problem is browser-specific, please specify the device, OS, browser, and version


Provide any additional information here in as much as detail as you can


@ngbot ngbot bot modified the milestone: needsTriage Apr 23, 2025
@alan-agius4 alan-agius4 transferred this issue from angular/angular Apr 23, 2025
@alan-agius4
Copy link
Collaborator

Duplicate of #25726

Note: with the new SSR APIs i18n is handled out of the box.

@mmart1n
Copy link
Author

mmart1n commented Apr 24, 2025

Duplicate of #25726

Note: with the new SSR APIs i18n is handled out of the box.

Thanks for your response, Alan!

I have two follow-up questions:

  1. The documentation mentions that the new SSR APIs are currently in developer preview. Do you have any plans to officially release them, and is there a timeline we can expect?

  2. I’ve noticed a difference in the server.ts file generated by the Angular CLI between versions 18 and 19. Is there any documentation or discussion explaining the rationale behind this change? If possible, could you point me to an issue or design decision that outlines the reasoning? From what I can tell, the previous approach made it more straightforward to configure i18n support on the server side. I assume there are good reasons for introducing this new structure, but I'd like to better understand the motivation and whether there are any potential drawbacks to reverting to the earlier setup.

@v18

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr/node';
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';

// 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',
    index: 'index.html',
  }));

  // 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();

@v19

import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import express from 'express';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import bootstrap from './main.server';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');

const app = express();
const commonEngine = new CommonEngine();

/**
 * Example Express Rest API endpoints can be defined here.
 * Uncomment and define endpoints as necessary.
 *
 * Example:
 * ```ts
 * app.get('/api/**', (req, res) => {
 *   // Handle API request
 * });
 * ```
 */

/**
 * Serve static files from /browser
 */
app.get(
  '*.*',
  express.static(browserDistFolder, {
    maxAge: '1y',
    index: 'index.html'
  }),
);

/**
 * Handle all other requests by rendering the Angular application.
 */
app.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));
});

/**
 * Start the server if this module is the main entry point.
 * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
 */
if (isMainModule(import.meta.url)) {
  const port = process.env['PORT'] || 4000;
  app.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

export default app;

Thanks in advance!

@alan-agius4
Copy link
Collaborator

The documentation mentions that the new SSR APIs are currently in developer preview. Do you have any plans to officially release them, and is there a timeline we can expect?

The are marked as stable in v20 which is currently in prerelease and will be released as stable later next month.

I’ve noticed a difference in the server.ts file generated by the Angular CLI between versions 18 and 19. Is there any documentation or discussion explaining the rationale behind this change? If possible, could you point me to an issue or design decision that outlines the reasoning? From what I can tell, the previous approach made it more straightforward to configure i18n support on the server side. I assume there are good reasons for introducing this new structure, but I'd like to better understand the motivation and whether there are any potential drawbacks to reverting to the earlier setup.

The Express initialization was wrapped unnecessarily. You can change the code in server.ts as per your needs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants