Dynamic baseHref in Angular 19 SSR application

Discussão em 'Angular' iniciado por Lukas, Janeiro 22, 2025.

  1. Lukas

    Lukas Guest

    Problem


    With my old setup, I can host localized angular apps with dynamic baseHref (determined at runtime). The Dockerfile runs an nginx server, which serves index.html, polyfills.js (and other non-bundle files).
    All bundles are served from a CDN, using --deploy-url (using multiple builds).

    The way this worked, was I used a APP_BASE_HREF along with window.location, as suggested here Angular 2+ - Set base href dynamically. So with the correct kubernetes ingress setup and a single docker build, I could host multiple angular apps with different baseHrefs:

    myapp.com/965168ba2981/home
    myapp.com/2c17f3db70e2/home
    myapp.com/876e04c3d6d2/home

    Routing to the apps is setup using Kubernetes ingresses.

    I want to keep this setup, however assets from CDN are not a priority


    I am however migrating to Angular 19 with SSR, which means that now a server is required for the app, and this is where I run into the problem - APP_BASE_HREF is now accessed during when prerendering, during build time, which means I cannot change it in runtime.

    Suboptimal Solution


    The only 'solution' I have found, is to set a placeholder for browser and server baseHref, like this:

    server.ts

    import {
    AngularNodeAppEngine,
    createNodeRequestHandler,
    isMainModule,
    writeResponseToNodeResponse,
    } from '@angular/ssr/node';
    import { environment } from '@env/environment';
    import express from 'express';
    import * as fs from 'fs';
    import https from 'node:https';
    import { dirname, 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 qaBaseHref = "/ENVIRONMENT_ID_PLACEHOLDER";

    const app = express();
    const angularApp = new AngularNodeAppEngine();

    app.use(
    baseHref,
    express.static(browserDistFolder, { maxAge: '1y', index: false, redirect: false }),
    );

    app.get('/**', (req, res, next) => {
    angularApp
    .handle(req)
    .then(
    response =>
    response ? writeResponseToNodeResponse(response, res) : next(), // TODO: redirect to 404 and send app insights event
    )
    .catch(next); // TODO: send errors to app insights
    });

    if (isMainModule(import.meta.url)) {
    const port = process.env['PORT'] || 3000;
    app.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
    });
    }

    /**
    * The request handler used by the Angular CLI (dev-server and during build).
    */
    export const reqHandler = createNodeRequestHandler(app);


    project.json configuration

    "qa": {
    "outputPath": "dist-qa",
    "fileReplacements": [
    {
    "replace": "src/environments/environment.ts",
    "with": "src/environments/environment.qa.ts"
    }
    ],
    "optimization": true,
    "outputHashing": "all",
    "sourceMap": false,
    "namedChunks": false,
    "baseHref": "/ENVIRONMENT_ID_PLACEHOLDER/"
    },


    And then replace them at the Dockerfile entrypoint with the actual baseHref I want.

    What I tried

    1. Rewriting the req.url in the server in middleware - this doesn't work for the most part, and if some files do get served correctly, then the entire route changes and does not include the dynamic baseHref anymore (myapp.com/965168ba2981/home routes to myapp.com/home when loaded, which is not configured in the ingress)
    2. Overriding the baseHref in the index https://symflower.com/en/company/blog/2021/path-independent-angular/

    Continue reading...

Compartilhe esta Página