@angular/ssr/node not working as intended
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 38/100
Research direction
Start with src/server.ts and functions/ssr/index.js, then compare the redirect from the /error route in src/app/app.routes.ts with the deployed Firebase Hosting request. Inspect the Cloud Function's 302 Location response and verify that the completed behavior preserves the public Hosting URL rather than exposing port 8080.
Written by the indexing model from the issue text.
Description
I am using @angular/[email protected], and I have the following:
src/server.ts
import {
AngularNodeAppEngine,
createNodeRequestHandler,
isMainModule,
writeResponseToNodeResponse,
} from "@angular/ssr/node";import express from "express";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, "../browser");const server = express();
const angularNodeAppEngine = new AngularNodeAppEngine();server.use(
express.static(browserDistFolder, {
maxAge: "1y",
index: false,
redirect: false,
}),
);server.use("/**", (serverRequest, serverResponse, serverNextFunction) => {
angularNodeAppEngine
.handle(serverRequest)
.then((response) => {
if (response) {
return writeResponseToNodeResponse(response, serverResponse);
} else {
return serverNextFunction();
}
})
.catch(serverNextFunction);
});if (isMainModule(import.meta.url)) {
const port = process.env["PORT"] || 4000;
server.listen(port, () => {
console.log(Node Express server listening on http://localhost:${port});
});
}export const reqHandler = createNodeRequestHandler(server);
src/app/app.routes.ts
import { Route } from "@angular/router";
import { ErrorComponent } from "./error/error.component";
import { errorResolver } from "./error/error.resolver";
import { HomeComponent } from "./home/home.component";
import { homeResolver } from "./home/home.resolver";export const routes: Route[] = [
{
path: "error",
component: ErrorComponent,
resolve: {
error: errorResolver,
},
},
{
path: "",
pathMatch: "full",
component: HomeComponent,
resolve: {
home: homeResolver,
},
},
{
path: "**",
redirectTo: "/error",
},
];
src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from "@angular/ssr";
export const serverRoutes: ServerRoute[] = [
{
path: "**",
renderMode: RenderMode.Server,
},
];
src/app/app.config.ts
import {
ApplicationConfig,
provideExperimentalZonelessChangeDetection,
} from "@angular/core";
import { initializeApp, provideFirebaseApp } from "@angular/fire/app";
import { getAnalytics, provideAnalytics } from "@angular/fire/analytics";
import { getFirestore, provideFirestore } from "@angular/fire/firestore";
import { getStorage, provideStorage } from "@angular/fire/storage";
import {
provideClientHydration,
withEventReplay,
} from "@angular/platform-browser";
import {
provideRouter,
withEnabledBlockingInitialNavigation,
withInMemoryScrolling,
} from "@angular/router";import { routes } from "./app.routes";
import { environment } from "../environments/environment.production";
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
provideClientHydration(withEventReplay()),
provideRouter(
routes,
withEnabledBlockingInitialNavigation(),
withInMemoryScrolling({
anchorScrolling: "enabled",
scrollPositionRestoration: "enabled",
}),
),
provideFirebaseApp(() => {
return initializeApp(environment.firebase);
}),
provideAnalytics(() => {
return getAnalytics();
}),
provideFirestore(() => {
return getFirestore();
}),
provideStorage(() => {
return getStorage();
}),
],
};
src/app/app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from "@angular/core";
import { provideServerRendering } from "@angular/platform-server";
import { provideServerRoutesConfig } from "@angular/ssr";import { appConfig } from "./app.config";
import { serverRoutes } from "./app.routes.server";const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRoutesConfig(serverRoutes),
],
};export const config = mergeApplicationConfig(appConfig, serverConfig);
Now, I am not using ng deploy or firebase deploy since they require @angular-devkit/build-angular and the SSR function being deployed is not working. Instead, I created a separate Firebase Repo and initialised both hosting and functions.
firebase.json
{
"functions": [
{
"source": "functions/ssr",
"codebase": "ssr",
"ignore": [...]
}
],
"hosting": {
"public": "hosting",
"ignore": [...],
"rewrites": [
{
"source": "**",
"function": {
"functionId": "ssr",
"region": "europe-west1"
}
}
]
}
}
functions/ssr/index.js
import { onRequest } from "firebase-functions/v2/https";
import { reqHandler } from "./build/template/server/server.mjs";
export const ssr = onRequest(
{
region: "europe-west1",
invoker: "public",
memory: "256MiB",
cpu: 1,
timeoutSeconds: 60,
concurrency: 80,
minInstances: 1,
maxInstances: 100,
ingressSettings: "ALLOW_ALL",
preserveExternalChanges: false,
},
reqHandler,
);
In the Firebase Repo, I am successfully deploying both hosting and functions using firebase deploy. The SSR function works, save for paths redirected to the ErrorComponent:
src/app/app.routes.ts
{
path: "**",
redirectTo: "/error",
},
When seeing the Google Cloud Console logs for the Cloud function, the express instance is giving a 302 response (which is what you'd expect from a redirect), however, instead of staying on port 80, the redirect URL is given on port 8080, which is the internal port being using by the express instance. I reckon that server.ts needs to be modified, however, I don't know how.
- Dominant language
- TypeScript
- Stars
- 7.8k
- Forks
- 2.1k
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 25
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from angular/angularfire
-
comp: compat comp: firestore comp: storage type: chore
Difficulty 4/5 3-5 days Newbie friendliness 48/100
angular/angularfire#3822 ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 55/100
angular/angularfire#3800 · 1 reaction ·
Maintainers usually reply within 3 days
-
Use `TransferState` to hand server-rendered Firestore data to the browser, instead of reading every document twiceMay be free again @armando-navarro claimed this 75 days ago, and no pull request is open. Opencomp: firestore comp: ssr priority: P0 (critical) type: feature version: current (v17+)
angular/angularfire#3757 · 1 assignee ·
Maintainers usually reply within 3 days
-
Six `firebase` entry points have no `@angular/fire` equivalent, so their exports are unreachableMay be free again @armando-navarro claimed this 75 days ago, and no pull request is open. Opencomp: core type: feature
angular/angularfire#3755 · 1 assignee ·
Maintainers usually reply within 3 days
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
angular/angularfire#3737 · 4 comments · 26 reactions ·
Maintainers usually reply within 3 days
All issues in angular/angularfire
Similar issues
-
bug parity
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
WenyuChiou/agent-board#1489 ·
Maintainers usually reply within 1 day
-
area: bazaar bug complexity: medium
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
accensa/x402-facilitator-stellar#779 ·
Maintainers usually reply within 1 day
-
bug good first issue P4 size:S web-ui
Difficulty 2/5 Half a day Newbie friendliness 85/100
josephfung/curia#2600 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
cloudflare/partykit#444 ·