feat(ssr): spider-only SSR serve CSR shell for regular users #11

Merged
kfj001 merged 5 commits from bugfix/spider-ssr-gate into main 2026-08-08 21:11:32 -07:00
Member

Summary

Reconfigure SSR so only search engine spiders receive full server-side rendering with fresh API data. Regular browser users get the static CSR app shell from disk.

## Summary Reconfigure SSR so only search engine spiders receive full server-side rendering with fresh API data. Regular browser users get the static CSR app shell from disk.
robot added 1 commit 2026-08-08 10:50:59 -07:00
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
c5d80f4caa
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)
Owner

Breaks in deployment. Visting website as normal user gets: "Internal server error — index.html not found" (HTTP 500).. its not a good thing there are no unit tests or CI/CD build tests that capture this scenario.

Breaks in deployment. Visting website as normal user gets: "Internal server error — index.html not found" (HTTP 500).. its not a good thing there are no unit tests or CI/CD build tests that capture this scenario.
robot added 1 commit 2026-08-08 13:25:48 -07:00
fix(ssr): resolve index.html from production path for CSR shell
CI Pipeline / backend-test (push) Successful in 20m50s
CI Pipeline / frontend-test (push) Successful in 24s
CI Pipeline / frontend-build (push) Successful in 32s
CI Pipeline / backend-lint (push) Successful in 15s
CI Pipeline / backend-test (pull_request) Successful in 37m43s
CI Pipeline / frontend-test (pull_request) Successful in 45s
CI Pipeline / frontend-build (pull_request) Successful in 39s
CI Pipeline / backend-lint (pull_request) Successful in 16s
CI Pipeline / quality-gate (push) Successful in 5s
CI Pipeline / quality-gate (pull_request) Successful in 4s
9561339cf5
The CSR shell path was hardcoded to a local dev directory
(app/server/browser/index.html) which does not exist in the
Docker container. Browser files are copied to /usr/share/nginx/html
by the Dockerfile. browserIndexPath() now checks both production
and development paths, falling back to the first that exists.
Author
Member

Fixed the production deployment issue. Root cause: browserIndexPath() was hardcoded to app/server/browser/index.html which only exists during local development. In the Docker container, browser files are copied to /usr/share/nginx/html/ by the Dockerfile, so the CSR shell was never found for regular users, returning the HTTP 500 error.

Fix: browserIndexPath() now checks both production (/usr/share/nginx/html/index.html) and development paths, returning the first that exists.

Fixed the production deployment issue. Root cause: `browserIndexPath()` was hardcoded to `app/server/browser/index.html` which only exists during local development. In the Docker container, browser files are copied to `/usr/share/nginx/html/` by the Dockerfile, so the CSR shell was never found for regular users, returning the HTTP 500 error. Fix: `browserIndexPath()` now checks both production (`/usr/share/nginx/html/index.html`) and development paths, returning the first that exists.
Owner

This deploys! But it doesn't fix the underlying issue. Other clients are still getting SSR pages at first.

This deploys! But it doesn't fix the underlying issue. Other clients are still getting SSR pages at first.
robot added 1 commit 2026-08-08 17:53:53 -07:00
fix(ssr): inject fresh station config into CSR shell to eliminate flash
CI Pipeline / backend-test (push) Successful in 18m48s
CI Pipeline / frontend-test (push) Successful in 24s
CI Pipeline / frontend-build (push) Successful in 32s
CI Pipeline / backend-lint (push) Successful in 10s
CI Pipeline / backend-test (pull_request) Successful in 20m7s
CI Pipeline / frontend-test (pull_request) Successful in 24s
CI Pipeline / frontend-build (pull_request) Successful in 32s
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 4s
e44f27d1cd
The CSR shell previously rendered with hardcoded DEFAULT_CONFIG values,
then the client-side API call replaced them 50-500ms later, causing a
visible 'flash of stale content' (wrong colors, wrong station name).

Fix: SSR server fetches live station config from the backend API before
serving the CSR shell, injecting it as:
- Inline <script> seeding window.__STATION_CONFIG (client reads sync)
- Inline <style> setting CSS custom properties (correct colors first paint)

Also adds no-cache Cache-Control headers on HTML page responses to
prevent browsers from caching stale content.

StationConfigService now reads window.__STATION_CONFIG on construction
instead of always starting with DEFAULT_CONFIG.
Author
Member

Addressed the review feedback. The root cause was that the CSR shell rendered with hardcoded DEFAULT_CONFIG values, then the async API call replaced them 50-500ms later, causing the visible flash.

Changes:

  • server.ts — serveCsrShell() is now async. It fetches live station config from the backend API before serving the shell, injecting:

    • Inline <script> seeding window.__STATION_CONFIG (client reads sync on bootstrap)
    • Inline <style> setting CSS custom properties on :root (correct colors on first paint)
    • Graceful degradation: if the API fetch fails, the shell is served unmodified
    • Cache-Control: no-cache headers on CSR responses
    • Browser path resolution now checks for index.csr.html (Angular 21 SSG output)
  • src/app/services/station-config.service.ts — config signal now reads window.__STATION_CONFIG synchronously on construction instead of always starting with DEFAULT_CONFIG

  • nginx.conf.template — Added Cache-Control: no-cache, no-store, must-revalidate + Pragma: no-cache on HTML page responses to prevent browser caching of stale content

  • server.spec.ts — 6 new tests for config injection, theme injection, async shell serving, API timeout, no-cache headers, and nginx headers (45/45 passing)

The client now bootstraps with real data — no DEFAULT_CONFIG → real data transition window.

Addressed the review feedback. The root cause was that the CSR shell rendered with hardcoded DEFAULT_CONFIG values, then the async API call replaced them 50-500ms later, causing the visible flash. **Changes:** - `server.ts` — `serveCsrShell()` is now `async`. It fetches live station config from the backend API before serving the shell, injecting: - Inline `<script>` seeding `window.__STATION_CONFIG` (client reads sync on bootstrap) - Inline `<style>` setting CSS custom properties on `:root` (correct colors on first paint) - Graceful degradation: if the API fetch fails, the shell is served unmodified - `Cache-Control: no-cache` headers on CSR responses - Browser path resolution now checks for `index.csr.html` (Angular 21 SSG output) - `src/app/services/station-config.service.ts` — `config` signal now reads `window.__STATION_CONFIG` synchronously on construction instead of always starting with `DEFAULT_CONFIG` - `nginx.conf.template` — Added `Cache-Control: no-cache, no-store, must-revalidate` + `Pragma: no-cache` on HTML page responses to prevent browser caching of stale content - `server.spec.ts` — 6 new tests for config injection, theme injection, async shell serving, API timeout, no-cache headers, and nginx headers (45/45 passing) The client now bootstraps with real data — no DEFAULT_CONFIG → real data transition window.
robot added 1 commit 2026-08-08 17:56:32 -07:00
chore: add placeholder angular-app-engine-manifest.mjs for tests
CI Pipeline / backend-test (push) Successful in 19m58s
CI Pipeline / frontend-test (push) Successful in 25s
CI Pipeline / frontend-build (push) Successful in 32s
CI Pipeline / backend-lint (push) Successful in 10s
CI Pipeline / backend-test (pull_request) Successful in 20m7s
CI Pipeline / frontend-test (pull_request) Successful in 25s
CI Pipeline / frontend-build (pull_request) Successful in 32s
CI Pipeline / backend-lint (pull_request) Successful in 11s
CI Pipeline / quality-gate (push) Successful in 3s
CI Pipeline / quality-gate (pull_request) Successful in 3s
b20fefc432
robot added 1 commit 2026-08-08 18:14:01 -07:00
fix(ssr): prefer index.csr.html over prerendered index.html for CSR users
CI Pipeline / backend-test (push) Successful in 20m24s
CI Pipeline / frontend-test (push) Successful in 25s
CI Pipeline / frontend-build (push) Successful in 31s
CI Pipeline / backend-lint (push) Successful in 11s
CI Pipeline / backend-test (pull_request) Successful in 20m14s
CI Pipeline / frontend-test (pull_request) Successful in 24s
CI Pipeline / frontend-build (pull_request) Successful in 32s
CI Pipeline / backend-lint (pull_request) Successful in 11s
CI Pipeline / quality-gate (push) Successful in 3s
CI Pipeline / quality-gate (pull_request) Successful in 4s
1b387700d3
The browserIndexPath() function was resolving index.html first, which is
the prerendered output with stale build-time content baked into <app-root>.
Even with window.__STATION_CONFIG injection, the prerendered innerHTML
still shows old content on the first paint until Angular re-renders.

By preferring index.csr.html (the pure CSR shell with empty <app-root>),
regular users get a clean first paint — no stale prerendered DOM to flash.
Author
Member

Fixed. The root cause was that browserIndexPath() was resolving index.html first, which is the prerendered output with stale build-time DEFAULT_CONFIG baked into <app-root>. Even with window.__STATION_CONFIG injection, the prerendered innerHTML still showed old content on the first paint until Angular re-rendered.

Fix: Prefer index.csr.html (the pure CSR shell with empty <app-root>) over index.html. Regular users now get a clean first paint — no stale prerendered DOM to flash.

Commit: 1b38770

Fixed. The root cause was that `browserIndexPath()` was resolving `index.html` first, which is the prerendered output with stale build-time DEFAULT_CONFIG baked into `<app-root>`. Even with `window.__STATION_CONFIG` injection, the prerendered innerHTML still showed old content on the first paint until Angular re-rendered. **Fix:** Prefer `index.csr.html` (the pure CSR shell with empty `<app-root>`) over `index.html`. Regular users now get a clean first paint — no stale prerendered DOM to flash. Commit: `1b38770`
kfj001 approved these changes 2026-08-08 21:11:26 -07:00
kfj001 merged commit 849856aecb into main 2026-08-08 21:11:32 -07:00
kfj001 deleted branch bugfix/spider-ssr-gate 2026-08-08 21:11:32 -07:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: WesternTechnologies/kmtnflower#11