feat(ssr): spider-only SSR — serve CSR shell for regular users
CI Pipeline / backend-test (push) Successful in 12m23s
CI Pipeline / frontend-test (push) Successful in 24s
CI Pipeline / frontend-build (push) Successful in 31s
CI Pipeline / backend-lint (push) Successful in 10s
CI Pipeline / backend-test (pull_request) Successful in 17m40s
CI Pipeline / frontend-test (pull_request) Successful in 24s
CI Pipeline / frontend-build (pull_request) Successful in 31s
CI Pipeline / backend-lint (pull_request) Successful in 10s
CI Pipeline / quality-gate (push) Successful in 3s
CI Pipeline / quality-gate (pull_request) Successful in 3s

Add spider detection gate to SSR server so that search engine
crawlers get full server-side rendering with fresh API data,
while regular users receive the static CSR app shell from disk.

- backend/app/spider_detection.py: Python spider detection module
- spider_detection.ts: TypeScript mirror of spider detection patterns
- server.ts: SSR gate — isSpider() check before dispatching to
  Angular SSR engine; non-spiders get static index.html
- server.spec.ts: Added spider detection unit tests
- tsconfig.app.json: Include spider_detection.ts in compilation
- backend/app/models.py: Fix SQLAlchemy FK resolution (user_models import)
This commit is contained in:
Hermes Robot committed 2026-08-08 17:48:37 +00:00
1 parent 96011ef200
commit c5d80f4caa
7 files changed
+860 -21

No files matched your search

+44
View File
@@ -4,7 +4,10 @@ import {
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as path from 'node:path';
import { isSpider, getUserAgent } from './spider_detection';
/**
* Manually import and register the Angular app engine manifest.
@@ -20,6 +23,36 @@ import { ɵsetAngularAppEngineManifest } from '@angular/ssr';
const appEngine = new AngularNodeAppEngine();
/**
* Resolve the static browser index.html path.
* In the built output the browser files live in the `browser/` subdirectory.
*/
function browserIndexPath(): string {
// __dirname in ESM resolves to this file's directory
const dir = typeof __dirname !== 'undefined' ? __dirname : path.dirname(new URL(import.meta.url).pathname);
return path.join(dir, 'browser', 'index.html');
}
/**
* Serve the static CSR app shell from disk.
*
* Regular browser users get the prerendered HTML shell — Angular hydrates
* client-side and fetches fresh config via the API. This avoids the SSR
* overhead for every human request while still delivering a fast first paint.
*/
function serveStaticShell(res: http.ServerResponse): void {
const indexPath = browserIndexPath();
try {
const content = fs.readFileSync(indexPath, 'utf-8');
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('X-Render-Mode', 'CSR');
res.end(content);
} catch {
res.statusCode = 500;
res.end('Internal server error — index.html not found');
}
}
const requestHandler = createNodeRequestHandler(async (req, res, next) => {
try {
const response = await appEngine.handle(req);
@@ -39,6 +72,17 @@ if (isMainModule(import.meta.url)) {
const server = http.createServer();
server.on('request', async (req, res) => {
try {
// ── Spider detection gate ──────────────────────────
// Only search engine spiders get full SSR rendering.
// Regular users receive the static CSR app shell from disk,
// which hydrates client-side and fetches fresh data from the API.
const userAgent = getUserAgent(req.headers);
if (!isSpider(userAgent)) {
serveStaticShell(res);
return;
}
// Spider — render full HTML via Angular SSR with live API data
await requestHandler(req, res, () => {
res.statusCode = 404;
res.end('Not Found');