- Overview
- Production traffic
- Request flow
- Key files
- Render modes
- Configuration
- SEO-related app behavior
- Local development
- Adding or changing SSR routes
- Related docs
OSF uses Angular SSR so search-engine crawlers receive fully rendered HTML for public pages (projects, registrations, preprints, discover pages, and similar).
The SSR stack has two layers:
- Angular SSR — renders the app on the server (
app.config.server.ts,app.routes.server.ts). - Express server — serves static assets, delegates HTML to Angular, and sends render metrics after the response (
src/server.ts,src/server/*).
In production, only bot traffic is routed to the SSR build. Human traffic uses the client-only build. See Production traffic.
Crawler → cloud (bot detection) → SSR Node server → HTML + metrics
Browser → cloud → static/CSR build → SPA shell + client render
- Request hits Express (
src/server.ts). - Static files (
/assets, hashed JS/CSS, etc.) are served frombrowser/when possible. - HTML navigations go through
createSsrMetricsMiddleware(src/server/ssr-metrics.middleware.ts). - Angular SSR renders the page (
AngularNodeAppEngine.handle). - HTML is sent to the client immediately.
- In the background (
setImmediate):- for bot + HTTP 200: HTML is inspected for completeness and
content_type - a metric payload is POSTed to the OSF API
- for bot + HTTP 200: HTML is inspected for completeness and
Bot timing is not blocked by steps 6. See SSR metrics.
| File | Role |
|---|---|
src/server.ts |
Express entry, static files, wires metrics middleware |
src/main.server.ts |
Angular server bootstrap |
src/app/app.config.server.ts |
Server providers: routes, config, i18n loader |
src/app/app.routes.server.ts |
Per-route render mode (Server / Client / Prerender) |
src/server/ssr-metrics.middleware.ts |
Render timing, HTML inspection trigger, metric queue |
src/server/ssr-html-metrics.ts |
is_complete and content_type checks |
src/server/ssr-metrics.ts |
POST metric payload to API |
src/server/ssr-server-config.ts |
Load config.json + env for metrics |
src/server/static-cache-headers.ts |
Cache headers for static assets |
src/app/shared/services/meta-tags.service.ts |
Dynamic SEO meta tags (osf:type, osf-dynamic-meta) |
Defined in src/app/app.routes.server.ts:
| Mode | When to use | Bot receives |
|---|---|---|
| Server | Public pages that should be indexed (project overview, preprint detail, discover, …) | Full SSR HTML |
| Client | Auth, forms, dashboards, moderation, editors | CSR shell; metrics may report is_complete: false |
| Prerender | Static legal/help pages (terms-of-use, privacy-policy, …) |
Pre-built HTML at build time |
Rules of thumb
- Public read-only detail or listing page → Server
- Login, submit, edit, settings, “my …” pages → Client
- Fixed copy that never changes at runtime → Prerender
More specific routes must appear before broader patterns (e.g. preprints/:providerId/:id before preprints/:providerId). Unmatched paths fall through to ** → Client.
Copied from assets/config/template.json for local dev. Deployed with the build. Used for URLs and third-party keys.
Relevant to SSR / metrics:
| Field | Used by |
|---|---|
apiDomainUrl |
SSR API calls (via OSFConfigService) and metrics POST target host |
webUrl |
Canonical URLs in meta tags; full URL in metric payloads |
| Variable | Used by |
|---|---|
THROTTLE_TOKEN |
X-Throttle-Token on SSR API calls (auth interceptor) and metrics POST |
API_DOMAIN_URL |
Fallback if apiDomainUrl missing from config.json |
WEB_URL |
Fallback if webUrl missing from config.json |
PORT |
Express listen port (default 4000) |
Throttle token: SSR page-render API calls read THROTTLE_TOKEN from the process environment only (app.config.server.ts). It is not taken from config.json for auth. Metrics use the same env var via loadSsrServerConfig.
On the server, OSFConfigService does not HTTP-fetch config.json. It uses SSR_CONFIG, populated at startup from disk in app.config.server.ts.
Translations on SSR are loaded from browser/assets/i18n/en.json (cached at module load). Browser builds use the HTTP loader as usual.
MetaTagsService writes dynamic tags with class osf-dynamic-meta. Pages also emit osf:type (API type: nodes, registrations, preprints, files, users). Metrics read osf:type from the rendered HTML for content_type.
Built in MetaTagsBuilderService for project, registration, preprint, and file pages. Profile pages call updateMetaTags with only osfType: users and mergeDefaults: false, so they emit a single osf:type tag.
| Command | What it does |
|---|---|
npm start |
CSR dev server (port 4200) |
npm run start:ssr |
Dev server with SSR (dev-ssr configuration) |
npm run build:ssr |
Production SSR build → dist/osf/ |
npm run serve:ssr:osf |
Run built Express server (port 4000) |
Typical local SSR test:
npm run build:ssr
npm run serve:ssr:osfSet THROTTLE_TOKEN in the shell if SSR API calls should bypass throttling locally.
Docker start:docker runs the development configuration (CSR), not the SSR server. Use build:ssr + serve:ssr:osf or the Dockerfile ssr stage for SSR.
When SSR fetches a resource and the API returns 401 / 403 / 404 / 410 / 5xx, the error interceptor sets RESPONSE_INIT.status on the requested URL. Network/timeout errors (status: 0) map to 503. First non-OK wins, except a later 401 / 403 / 404 / 410 may replace an earlier 5xx. No navigation/toasts on the server. Bypass skips this. Silent requests (X-No-Auth-Redirect or request-access URL) do not set status for any code. Anonymous SSR usually sees 401 for private resources (not 403). GUID guards catch errors and return false, so routing falls through to ** (not-found page HTML with that HTTP status). See Error handling.
- Add the route in
app.routes.ts(browser routes). - Add a matching entry in
app.routes.server.tswith the correctRenderMode. - If the page should contribute SEO meta, ensure the feature calls
MetaTagsService.updateMetaTagsand setsosfTypewhere applicable. - For public indexable pages, prefer Server. Do not SSR authenticated workflows unless there is a specific SEO need.
- SSR metrics — payload,
is_complete, dashboards - Architecture — folder layout
- Docker — container workflows