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
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
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.
This commit is contained in:
1 parent
9561339cf5
commit
e44f27d1cd
4 files changed
+221
-22
No files matched your search
+51
-4
@@ -28,7 +28,9 @@ vi.mock('node:path', () => ({
|
||||
}));
|
||||
|
||||
// Mock the engine manifest that only exists after ng build
|
||||
vi.mock('./angular-app-engine-manifest.mjs', () => ({}));
|
||||
vi.mock('./angular-app-engine-manifest.mjs', () => ({
|
||||
default: {},
|
||||
}));
|
||||
|
||||
describe('SSR server — server.ts', () => {
|
||||
it('should export a request handler', async () => {
|
||||
@@ -36,12 +38,12 @@ describe('SSR server — server.ts', () => {
|
||||
expect(mod.requestHandler).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have spider detection imports and serveStaticShell', async () => {
|
||||
it('should have spider detection imports and serveCsrShell', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toContain('isSpider');
|
||||
expect(content).toContain('getUserAgent');
|
||||
expect(content).toContain('serveStaticShell');
|
||||
expect(content).toContain('serveCsrShell');
|
||||
expect(content).toContain('spider_detection');
|
||||
});
|
||||
|
||||
@@ -50,7 +52,45 @@ describe('SSR server — server.ts', () => {
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
// Verify the spider detection gate exists
|
||||
expect(content).toContain("if (!isSpider(userAgent))");
|
||||
expect(content).toContain('serveStaticShell(res)');
|
||||
expect(content).toContain('serveCsrShell(res)');
|
||||
});
|
||||
|
||||
it('should inject fresh station config as window.__STATION_CONFIG', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toContain('window.__STATION_CONFIG');
|
||||
expect(content).toContain('buildConfigScript');
|
||||
expect(content).toContain('fetchStationConfig');
|
||||
});
|
||||
|
||||
it('should inject inline theme CSS custom properties', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toContain('buildThemeStyle');
|
||||
expect(content).toContain('COLOR_MAP');
|
||||
expect(content).toContain('--color-primary');
|
||||
expect(content).toContain(':root{');
|
||||
});
|
||||
|
||||
it('should serveCsrShell be async and accept (res)', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toMatch(/async function serveCsrShell\s*\(\s*res/);
|
||||
});
|
||||
|
||||
it('should fetch station config from SSR_API_BASE_URL with 2s timeout', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toContain('SSR_API_BASE_URL');
|
||||
expect(content).toContain('api/station-config');
|
||||
expect(content).toContain('setTimeout(2000');
|
||||
});
|
||||
|
||||
it('should set no-cache headers on CSR shell responses', async () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./server.ts', 'utf-8');
|
||||
expect(content).toContain("Cache-Control");
|
||||
expect(content).toContain("no-cache, no-store, must-revalidate");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,6 +314,13 @@ describe('nginx.conf.template — SSR proxy chain', () => {
|
||||
expect(content).toContain('location /api/');
|
||||
expect(content).toContain('__API_UPSTREAM__');
|
||||
});
|
||||
|
||||
it('should add Cache-Control no-cache headers on HTML page responses', () => {
|
||||
const fs = require('fs');
|
||||
const content = fs.readFileSync('./nginx.conf.template', 'utf-8');
|
||||
expect(content).toContain('add_header Cache-Control "no-cache, no-store, must-revalidate" always');
|
||||
expect(content).toContain('add_header Pragma "no-cache" always');
|
||||
});
|
||||
});
|
||||
|
||||
describe('main.server.ts — SSR API base URL injection', () => {
|
||||
|
||||
Reference in new issue
Block a user