127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
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';
|
|
import { resolveRequestOrigin } from './core/request-origin';
|
|
import {
|
|
parseAcceptLanguage,
|
|
resolveInitialLanguage,
|
|
} from './app/core/i18n/language-resolution';
|
|
import { resolvePublicRedirectTarget } from './server-routing';
|
|
|
|
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: false,
|
|
}),
|
|
);
|
|
|
|
app.get('/', (req, res) => {
|
|
const userAgent = req.get('user-agent');
|
|
const preferredLanguages = parseAcceptLanguage(req.get('accept-language'));
|
|
const lang = resolveInitialLanguage({
|
|
preferredLanguages,
|
|
});
|
|
const stableRedirect = shouldUseStableRootRedirect(
|
|
userAgent,
|
|
preferredLanguages,
|
|
);
|
|
|
|
res.setHeader('Vary', 'Accept-Language, User-Agent');
|
|
res.setHeader('Cache-Control', 'private, no-store');
|
|
res.redirect(
|
|
stableRedirect ? 308 : 302,
|
|
`/${stableRedirect ? 'it' : lang}${querySuffix(req.originalUrl)}`,
|
|
);
|
|
});
|
|
|
|
app.get('**', (req, res, next) => {
|
|
const targetPath = resolvePublicRedirectTarget(req.path);
|
|
if (!targetPath) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
res.redirect(308, `${targetPath}${querySuffix(req.originalUrl)}`);
|
|
});
|
|
|
|
/**
|
|
* Handle all other requests by rendering the Angular application.
|
|
*/
|
|
app.get('**', (req, res, next) => {
|
|
const { originalUrl, baseUrl } = req;
|
|
const origin = resolveRequestOrigin(req);
|
|
|
|
commonEngine
|
|
.render({
|
|
bootstrap,
|
|
documentFilePath: indexHtml,
|
|
url: `${origin ?? 'http://localhost:4000'}${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;
|
|
|
|
function querySuffix(url: string): string {
|
|
const queryIndex = String(url ?? '').indexOf('?');
|
|
return queryIndex >= 0 ? String(url).slice(queryIndex) : '';
|
|
}
|
|
|
|
function shouldUseStableRootRedirect(
|
|
userAgent: string | undefined,
|
|
preferredLanguages: readonly string[],
|
|
): boolean {
|
|
return preferredLanguages.length === 0 || isLikelyCrawler(userAgent);
|
|
}
|
|
|
|
function isLikelyCrawler(userAgent: string | undefined): boolean {
|
|
const normalized = String(userAgent ?? '').toLowerCase();
|
|
if (!normalized) {
|
|
return false;
|
|
}
|
|
|
|
return /(bot|crawler|spider|slurp|bingpreview|google-read-aloud)/.test(
|
|
normalized,
|
|
);
|
|
}
|