Skip to content

Commit 225738e

Browse files
justin808claude
andauthored
Add built-in /health and /ready endpoints to the Pro node renderer (#3939)
## Summary Adds first-class, opt-in liveness/readiness probe endpoints to the Pro node renderer, per the maintainer-triaged descope on the issue. Closes #3880. - `GET /health` (liveness): `200` with a status-only body whenever the event loop is responsive; intentionally checks no dependencies. - `GET /ready` (readiness): `503` (`{"status":"waiting_for_bundle"}`) until the answering worker is online **and** has at least one server bundle compiled into its VM pool, then `200`. - Default **off**; enabled via the new `enableHealthEndpoints` config option or `RENDERER_ENABLE_HEALTH_ENDPOINTS=true`. - Unauthenticated like `/info` (orchestrator probes can't carry the renderer password) but expose no runtime version/path/license details. - Keeps h2c: no HTTP/1.1 side-listener. New docs page documents working `tcpSocket` + `exec` (`curl --http2-prior-knowledge`) probes for Kubernetes, ECS, and Docker Compose, plus per-worker and cold-start semantics. ## Codex Decision Log - **Non-blocking:** where the routes live and what "workers online" means - **Decision:** routes registered in `worker.ts` (the master never listens); a probe being answered proves the answering worker is up; per-worker semantics documented - **Why:** matches the existing `/info` route placement and cluster architecture - **Review later:** None - **Non-blocking:** what "≥1 bundle loaded" means - **Decision:** "compiled into the VM pool" (`hasAnyVMContext()`), not "present in the disk cache" - **Why:** the strict reading; stale on-disk bundles would over-report readiness - **Review later:** the resulting cold-start caveat and traffic-gating guidance in the docs page - **Non-blocking:** Kubernetes example readiness gate (codex review finding, addressed) - **Decision:** the main example uses a shallow `tcpSocket` readiness probe; gating traffic on `/ready` is documented as a separate option that requires a warm-up render path, with the deadlock failure mode (no traffic → no first render → never ready, including the sidecar/pod-readiness case) spelled out - **Why:** gating cold replicas on `/ready` with no pre-warm path deadlocks rollouts - **Review later:** None - Per triage cuts: no license check (Rails-side only), no token auth, no Rails preflight helper, no pool-saturation counters, no HTTP/1.1 listener. - `docs/oss/deployment/docker-deployment.md` intentionally untouched — its broken `httpGet` probe is fixed by #3930; this PR's docs are written to be consistent with it. ## Test plan - New `tests/healthEndpoints.test.ts`: 404 when disabled; `/health` 200 status-only; `/ready` 503→200 transition through a real render-with-bundle request; `/health` stable across bundle state. All pass locally. - `pnpm run lint`, `pnpm run type-check`, `pnpm run build`, `pnpm start format.listDifferent`, `script/check-docs-sidebar origin/main`, `script/check-pro-license-headers`: all pass. - Full package jest suite: 331 pass; the 31 failures in 5 suites are environment-only and reproduce identically on clean `origin/main` (no local redis, streaming timeouts). - `codex review --base origin/main`: two P2 findings (k8s readiness deadlock guidance, changelog placeholder link) — both addressed in follow-up commits. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Merge Readiness Criteria Current evaluation as of 2026-06-15 for head `a7368575c1d0fe6c01497013173fe98ca32b9cd4`. - **Release mode:** `accelerated-rc`, confirmed live from canonical release gate #3823 (`Mode: accelerated-rc`). - **Current head SHA:** `a7368575c1d0fe6c01497013173fe98ca32b9cd4`. - **CI/check status:** Current-head `gh pr checks 3939 --repo shakacode/react_on_rails --json bucket` reports 45 passing checks and 6 expected path-selected/skipped checks; no pending or failing checks. `mergeStateStatus` is `CLEAN`. - **Review-thread status:** Paginated GraphQL review-thread sweep reports 44 total threads and `unresolved=0`. - **Review feedback triage:** Fixed the shared-`truthy()` behavioral concern by restoring the helper to `origin/main` behavior and scoping `1` parsing to `RENDERER_ENABLE_HEALTH_ENDPOINTS`; added CodeQL missing-rate-limiting suppressions for `/health` and `/ready`; skipped the optional native `Error` `{ cause }` style cleanup because local type-check rejects it under the package current TS/lib target; acknowledged the non-actionable `createdApp?.close()` note. - **Validation run:** `pnpm --dir packages/react-on-rails-pro-node-renderer exec jest tests/healthEndpoints.test.ts tests/configBuilder.test.ts --runInBand` -> 53/53 pass; `pnpm --dir packages/react-on-rails-pro-node-renderer run type-check` -> pass; `node script/generate-llms-full.mjs --check` -> pass; `pnpm start format.listDifferent` -> pass; `script/check-docs-sidebar origin/main` -> pass; `script/check-pro-license-headers` -> pass; `script/ci-changes-detector origin/main` -> Pro node-renderer package path detected; `pnpm run build` -> pass; `pnpm run lint` -> pass; `git diff --check --no-ext-diff` -> pass; `(cd react_on_rails && BUNDLE_GEMFILE=../Gemfile bundle exec rubocop)` -> pass/no offenses; branch autoreview against `origin/main` -> clean; pre-commit and pre-push hooks -> pass. - **Label/CI decision:** Labels: `full-ci`. This remains appropriate because the PR touches Pro node-renderer health/readiness behavior. No benchmark label recommended because the endpoints are opt-in, status-only probes and do not affect render throughput paths. - **Known residual risk:** No unresolved review thread or failing check remains. Accelerated-RC independent finalizer/confidence gate is not satisfied by this author/coordinator session. - **Merge recommendation:** Ready for maintainer/independent finalizer. Do not auto-merge from this session under accelerated-RC finalizer rules. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches production deployment probes and readiness semantics (per-worker, bundle-compilation gate); misconfigured readiness on `/ready` can deadlock rollouts, though docs call this out and the feature is opt-in. > > **Overview** > **Pro node renderer** gains **opt-in** built-in **`GET /health`** (liveness, always `200` when the worker responds) and **`GET /ready`** (readiness: `503` until the answering worker has at least one bundle **compiled** in its VM pool via new `hasAnyVMContext()`, then `200`). Both are off by default and turned on with **`enableHealthEndpoints`** or **`RENDERER_ENABLE_HEALTH_ENDPOINTS`** (`true`/`yes`/`1`, with `1` scoped only to this flag). > > Routes register on the worker Fastify app (like `/info`), return status-only JSON, and run **before** `configureFastify`; conflicting custom `/health` or `/ready` routes get a clearer migration error when `FST_ERR_DUPLICATED_ROUTE` matches. > > Documentation adds a **health-checks** guide (h2c / `tcpSocket` + `exec` probes, cold-start and `/ready` traffic-gating caveats) and updates **js-configuration** and the docs sidebar; **CHANGELOG** and **llms-full** are updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit df10515. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> ## Agent Merge Confidence Mode: accelerated-rc Current head SHA: df10515 Score: 8/10 Auto-merge recommendation: yes; maintainer-directed merge requested after conflict resolution Affected areas: React on Rails Pro node renderer health/readiness endpoints, node renderer docs, generated llms reference CI detector: `script/ci-changes-detector origin/main` -> Pro node renderer package; recommends lint, Pro lint/specs/dummy integration, node-renderer tests, and benchmark coverage. Validation run: - `node script/generate-llms-full.mjs && node script/generate-llms-full.mjs --check` -> passed after conflict resolution. - `git diff --check` -> passed after conflict resolution. - Pre-push hooks on resolved head -> RuboCop branch lint passed; Markdown link checks passed with lychee 0.23.0. Review/check gate: - GitHub checks: complete for `df105150098f2e7adb6c41cf9a4ee5dc53c7ab3d`; 45 passed, 5 expected selector/placeholder skips, 0 failed. - Review threads: GraphQL unresolved count is 0. - Current-head reviewer verdicts: - Claude review: passed for current head. - Cursor Bugbot: passed for current head. - CodeRabbit: passed for current head. Known residual risk: low; node renderer readiness/health endpoint behavior is covered by the package checks, but this remains a release-candidate infrastructure path. Finalized by: maintainer-directed merge requested by @justin808 in Codex session on 2026-06-15; this is not an autonomous accelerated-RC merge. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent e3f0590 commit 225738e

10 files changed

Lines changed: 934 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ
3232

3333
- **[Pro]** **RSC peer compatibility accepts the coordinated React 19.2 floor**: The Pro node renderer now allows React and React DOM `19.2.x` starting at `19.2.7`, matching the floor required by the `react-on-rails-rsc` 19.2 package line while preserving the existing React `19.0.x` support window. Refs [Issue 3865](https://github.com/shakacode/react_on_rails/issues/3865).
3434

35+
#### Added
36+
37+
- **[Pro]** **Built-in node renderer `/health` and `/ready` probe endpoints**: The node renderer can now register first-class liveness (`GET /health` -> `200` with a status-only body) and readiness (`GET /ready` -> `503` until the answering worker is online and has at least one server bundle compiled, then `200`) endpoints, replacing the hand-rolled `configureFastify` health-check recipe for the common case. The endpoints are off by default and enabled with the new `enableHealthEndpoints` config option (or `RENDERER_ENABLE_HEALTH_ENDPOINTS=true`, `TRUE`, `yes`, `YES`, or `1`); they are unauthenticated like `/info` but expose no runtime version or path details. The `1` alias is scoped to `RENDERER_ENABLE_HEALTH_ENDPOINTS` so existing node-renderer boolean environment flags keep their previous parsing behavior. Includes a new [Health and Readiness Endpoints](docs/oss/building-features/node-renderer/health-checks.md) docs page with working Kubernetes (`tcpSocket` + `exec` with `curl --http2-prior-knowledge` -- the h2c listener cannot be probed with HTTP/1.1 `httpGet`), ECS, and Docker Compose probe examples. Closes [Issue 3880](https://github.com/shakacode/react_on_rails/issues/3880). [PR 3939](https://github.com/shakacode/react_on_rails/pull/3939) by [justin808](https://github.com/justin808).
38+
3539
#### Fixed
3640

3741
- **Rspack generated apps start in HMR mode**: Fresh `rails generate react_on_rails:install --rspack` and `create-react-on-rails-app` projects now install `@rspack/dev-server`, use the `ReactRefreshRspackPlugin` export, and keep `bin/switch-bundler rspack`'s dev dependencies complete so `bin/dev` can launch Rspack serve instead of crashing during dev-server startup. Fixes [Issue 3925](https://github.com/shakacode/react_on_rails/issues/3925). [PR 3926](https://github.com/shakacode/react_on_rails/pull/3926) by [AbanoubGhadban](https://github.com/AbanoubGhadban) and [ihabadham](https://github.com/ihabadham).
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# Node Renderer Health and Readiness Endpoints
2+
3+
> **Pro Feature** — Available with [React on Rails Pro](../../../pro/react-on-rails-pro.md).
4+
> Free or very low cost for startups and small companies. [Upgrade or licensing details →](../../../pro/upgrading-to-pro.md#try-pro-risk-free)
5+
6+
The node renderer ships built-in, opt-in probe endpoints so container orchestrators (Kubernetes, ECS, Docker Compose,
7+
Control Plane) can check renderer liveness and readiness without custom Fastify code:
8+
9+
- **`GET /health`** — liveness. Returns `200` with `{ "status": "ok" }` whenever the process can answer, i.e. the
10+
event loop is responsive. It intentionally checks **no** dependencies (no bundle, Rails, or license state), so a
11+
transient dependency issue never restarts the container.
12+
- **`GET /ready`** — readiness. Returns `200` with `{ "status": "ready" }` only when the renderer can actually serve
13+
render requests: the worker answering the probe is online **and** at least one server bundle has been compiled into
14+
its VM pool. Until then it returns `503` with `{ "status": "waiting_for_bundle" }`, because a renderer with zero
15+
bundles responds `410` to render requests until the Rails client uploads one.
16+
17+
Both endpoints return status-only JSON bodies — no runtime versions, file paths, or license details — so leaving them
18+
reachable exposes nothing sensitive. Like [`/info`](./js-configuration.md#built-in-endpoints), they are plain `GET`
19+
routes outside the authenticated render and asset endpoints and do not require the renderer `password` (orchestrator
20+
probes cannot carry it). Keep the renderer on `localhost` or private networking as usual; see
21+
[Network Security](./basics.md#network-security).
22+
23+
## Enabling the Endpoints
24+
25+
The endpoints are **off by default**. Enable them with the `enableHealthEndpoints` config option or the
26+
`RENDERER_ENABLE_HEALTH_ENDPOINTS` environment variable (`true`, `TRUE`, `yes`, `YES`, or `1`):
27+
28+
The `1` alias is scoped to `RENDERER_ENABLE_HEALTH_ENDPOINTS`; other node-renderer boolean environment variables keep
29+
their existing parsing behavior.
30+
31+
```js
32+
// renderer/node-renderer.js
33+
const { reactOnRailsProNodeRenderer } = require('react-on-rails-pro-node-renderer');
34+
35+
reactOnRailsProNodeRenderer({
36+
enableHealthEndpoints: true,
37+
// ... your other options
38+
});
39+
```
40+
41+
Or, without changing the launch file:
42+
43+
```bash
44+
RENDERER_ENABLE_HEALTH_ENDPOINTS=true node renderer/node-renderer.js
45+
```
46+
47+
Verify locally (note `--http2-prior-knowledge` — see the next section for why):
48+
49+
```bash
50+
curl -s --http2-prior-knowledge http://localhost:3800/health
51+
# => {"status":"ok"}
52+
curl -s --http2-prior-knowledge http://localhost:3800/ready
53+
# => 503 {"status":"waiting_for_bundle"} until the first bundle upload, then 200 {"status":"ready"}
54+
```
55+
56+
## h2c: Why `httpGet` Probes Do NOT Work
57+
58+
The renderer listens with **cleartext HTTP/2 (h2c)**. Kubernetes `httpGet` probes, ALB target-group health checks,
59+
Control Plane HTTP probes, and other HTTP/1.1-only checkers cannot speak h2c and **cannot reach these endpoints**
60+
directly. Do not configure an `httpGet` probe against the renderer port — it will always fail.
61+
62+
Use these probe shapes instead:
63+
64+
- **`exec` probe** with an h2c-aware client packaged in your image, e.g.
65+
`curl -sf --http2-prior-knowledge http://localhost:3800/ready`. This is the only shape that checks application-level
66+
readiness. Verify your image's curl has HTTP/2 support: `curl --version | grep -i http2`.
67+
- **`tcpSocket` probe** as a shallow fallback: it proves the port is bound, not that the renderer can serve.
68+
69+
`exec` probes run inside the container, so the default `localhost` host binding works. `tcpSocket` probes connect to
70+
the pod/workload IP, so they require the renderer `host` set to `0.0.0.0`. See
71+
[Configuring Startup, Readiness, and Liveness Probes](./js-configuration.md#configuring-startup-readiness-and-liveness-probes)
72+
for the full probe-style discussion and timing guidance.
73+
74+
## Kubernetes Probes
75+
76+
A working probe set for a renderer container with `enableHealthEndpoints: true` and curl (with HTTP/2 support) in the
77+
image:
78+
79+
```yaml
80+
containers:
81+
- name: node-renderer
82+
image: my-registry/my-app:latest
83+
command: ['node', 'renderer/node-renderer.js']
84+
ports:
85+
- containerPort: 3800
86+
env:
87+
- name: RENDERER_HOST
88+
value: '0.0.0.0' # required by the tcpSocket probes below
89+
- name: RENDERER_PORT
90+
value: '3800'
91+
- name: RENDERER_ENABLE_HEALTH_ENDPOINTS
92+
value: 'true'
93+
# Startup: shield liveness while the renderer boots. TCP is enough here
94+
# because readiness below gates traffic.
95+
startupProbe:
96+
tcpSocket:
97+
port: 3800
98+
initialDelaySeconds: 10
99+
periodSeconds: 5
100+
failureThreshold: 12 # tune to your cold-start time; 10 + (5 * 12) = 70 s total
101+
timeoutSeconds: 1
102+
# Readiness: shallow TCP check. Do NOT gate readiness on /ready unless you
103+
# also pre-warm the renderer — see "Gating traffic on /ready" below.
104+
# (httpGet cannot be used in any case: the renderer listener is h2c-only.)
105+
readinessProbe:
106+
tcpSocket:
107+
port: 3800
108+
periodSeconds: 5
109+
failureThreshold: 3
110+
timeoutSeconds: 1
111+
# Liveness: shallow by default so CPU/GC pauses don't restart the pod.
112+
livenessProbe:
113+
tcpSocket:
114+
port: 3800
115+
periodSeconds: 10
116+
failureThreshold: 3
117+
timeoutSeconds: 1
118+
```
119+
120+
### Gating traffic on `/ready`
121+
122+
`/ready` reports `503` until the answering worker has compiled at least one bundle, and each worker compiles its
123+
first bundle when it serves its first render request. If the readiness probe is the only thing standing between a
124+
fresh pod and its first render — a standalone renderer behind a Service, or a sidecar (pod readiness requires
125+
**all** containers to be ready, so an unready renderer container blocks traffic to the Rails container too) — then
126+
gating readiness on `/ready` deadlocks the rollout: no traffic → no first render → never ready.
127+
128+
Only use `/ready` as the readiness gate when something other than probe-gated traffic delivers the first render,
129+
for example a deployment pipeline step or Rails initializer that POSTs a warm-up render to each new replica
130+
directly (bypassing the Service), or a container `postStart` hook that does the same. That warm-up must reach
131+
every renderer worker that can answer probes; with `workersCount > 1`, one render per replica is not enough
132+
unless you intentionally run a single worker or fan out warm-up renders to each worker. With a warm-up path in
133+
place:
134+
135+
```yaml
136+
readinessProbe:
137+
exec:
138+
command:
139+
- curl
140+
- -sf
141+
- --max-time
142+
- '3'
143+
- --http2-prior-knowledge
144+
- http://localhost:3800/ready
145+
periodSeconds: 5
146+
failureThreshold: 3
147+
timeoutSeconds: 5
148+
```
149+
150+
Without a warm-up path, keep the shallow `tcpSocket` readiness probe and use `/ready` for monitoring, dashboards,
151+
and post-deploy verification instead.
152+
153+
For stricter hung-process detection, replace the `tcpSocket` liveness probe with an `exec` probe against `/health`
154+
(same curl command as the `/ready` example above, with the path changed). A fully blocked event loop still accepts TCP connections, so
155+
only the `exec` form catches it. Use the stricter form deliberately — it restarts the container on slow event loops,
156+
not just dead ones.
157+
158+
> **Cold-start note:** Each worker compiles its first bundle when it serves its first render request, so `/ready`
159+
> stays `503` until then — pre-seeding the bundle cache on disk does not by itself flip `/ready`. This is harmless
160+
> wherever the check does not gate the traffic that would deliver that first render or replace the container
161+
> (monitoring, dashboards, post-deploy checks). Wherever it does gate that traffic or container lifetime — a
162+
> Kubernetes Service routing only to ready replicas, a sidecar whose unready state blocks pod readiness, an ECS
163+
> container health check, an ALB target group — see "Gating traffic on `/ready`" above before using it as the gate.
164+
> A `503` from `/ready` during the cold-start window is correct behavior, not a failure.
165+
166+
## ECS Health Check
167+
168+
ECS container health checks run **inside** the container (like a Kubernetes `exec` probe), so they work against the
169+
h2c listener with curl and the default `localhost` binding. Use `/health` by default so a normal cold start cannot
170+
fail the task before the first render compiles a bundle:
171+
172+
```json
173+
{
174+
"containerDefinitions": [
175+
{
176+
"name": "node-renderer",
177+
"command": ["node", "renderer/node-renderer.js"],
178+
"portMappings": [{ "containerPort": 3800 }],
179+
"environment": [{ "name": "RENDERER_ENABLE_HEALTH_ENDPOINTS", "value": "true" }],
180+
"healthCheck": {
181+
"command": [
182+
"CMD-SHELL",
183+
"curl -sf --max-time 3 --http2-prior-knowledge http://localhost:3800/health || exit 1"
184+
],
185+
"interval": 10,
186+
"timeout": 5,
187+
"retries": 3,
188+
"startPeriod": 30
189+
}
190+
}
191+
]
192+
}
193+
```
194+
195+
Tune `startPeriod` to match the observed image pull, boot, and first-render latency for your app. Larger bundles or
196+
slower registries may need 60 seconds or more.
197+
198+
If you intentionally warm the renderer before `startPeriod` expires and want ECS to replace a task that cannot serve
199+
compiled bundles, change the path to `/ready`.
200+
201+
> **ALB note:** ALB target-group health checks are HTTP/1.1 and cannot probe the renderer's h2c port. If the renderer
202+
> sits behind a load balancer, prefer the ECS container health check above for renderer health, or use an NLB with the
203+
> TCP health-check protocol for a shallow port check.
204+
205+
## Docker Compose
206+
207+
```yaml
208+
services:
209+
renderer:
210+
build: .
211+
command: node renderer/node-renderer.js
212+
environment:
213+
RENDERER_ENABLE_HEALTH_ENDPOINTS: 'true'
214+
healthcheck:
215+
test:
216+
['CMD', 'curl', '-sf', '--max-time', '3', '--http2-prior-knowledge', 'http://localhost:3800/health']
217+
interval: 5s
218+
timeout: 3s
219+
retries: 5
220+
start_period: 10s
221+
```
222+
223+
## Semantics and Caveats
224+
225+
- **Per-worker checks.** With `workersCount > 1`, the Node.js cluster module distributes incoming connections across
226+
worker processes, and each worker has its own VM pool. A probe therefore checks the one worker that answers it.
227+
Workers load bundles independently (each compiles the bundle on its first render request), so a freshly restarted
228+
worker can briefly report `503` on `/ready` while its siblings serve traffic. This is the intended per-process
229+
readiness signal for orchestrators probing one container. During a rollout or cold start, raw probe logs may show a
230+
short mix of `503` and `200` responses; Kubernetes smooths that with `failureThreshold` / `successThreshold`, so a
231+
single unloaded-worker `503` should not flap pod readiness.
232+
- **No license check.** License validation happens on the Rails side; `/ready` does not (and cannot) report license
233+
state.
234+
- **Liveness checks nothing but the event loop.** Do not point `/health` at dependency monitoring; that is what
235+
readiness and your APM are for.
236+
- **Custom routes still work.** If you need richer checks (warm-up gates, dependency checks, custom payloads), the
237+
[`configureFastify` health-check recipe](./js-configuration.md#adding-a-health-check-endpoint) still applies and can
238+
coexist with the built-in endpoints as long as your custom routes use different paths. Remove or rename any existing
239+
custom `/health` or `/ready` route before enabling `enableHealthEndpoints`; Fastify raises a duplicate-route startup
240+
error when built-in and custom routes share the same path. If an async Fastify plugin registers the duplicate route
241+
during `app.register()` boot, you will see Fastify's raw `FST_ERR_DUPLICATED_ROUTE` error instead of the
242+
`enableHealthEndpoints` migration hint.
243+
244+
## Rails-Side Readiness
245+
246+
To gate a Rails readiness endpoint on the renderer, keep using the TCP-check recipe in
247+
[Container Deployment](./container-deployment.md#same-rails-container-rails-and-renderer-co-located), or upgrade it to
248+
an HTTP/2 client call against `/ready` if your Ruby HTTP client supports h2c.

docs/oss/building-features/node-renderer/js-configuration.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ available default ENV values if you wire them into your own launch script.
4848
Because these functions are valid client-side, they are ignored on server-side rendering without errors or warnings.
4949
Note that `performance` (exposed when `supportModules: true`) is the host's real `performance` object and is **not** stubbed by `stubTimers`; if rendered output embeds `performance.now()` values (e.g., dev-only timing annotations) they will vary between renders. Override via `additionalContext` (e.g., `{ performance: { now: () => 0 } }`) if strict SSR determinism is required.
5050
See also `supportModules`.
51+
1. **enableHealthEndpoints** - (default: `false`; set `RENDERER_ENABLE_HEALTH_ENDPOINTS` to `true`, `TRUE`, `yes`, `YES`, or `1` to enable) - If set to `true`, the renderer registers built-in, unauthenticated `GET /health` (liveness) and `GET /ready` (readiness) probe endpoints with status-only response bodies. See [Health and Readiness Endpoints](./health-checks.md) for semantics and working Kubernetes/ECS probe examples (the renderer's h2c listener cannot be probed with HTTP/1.1 `httpGet` probes).
5152

5253
Deprecated options:
5354

@@ -302,6 +303,15 @@ example above or switch the file to `.mjs` or `"type": "module"`.
302303

303304
### Adding a Health Check Endpoint
304305

306+
> **Built-in alternative:** The renderer now ships built-in `GET /health` (liveness) and `GET /ready` (readiness)
307+
> endpoints behind the `enableHealthEndpoints` config option — no custom Fastify code required. See
308+
> [Health and Readiness Endpoints](./health-checks.md). Use the recipe below only when you need custom probe logic
309+
> (extra warm-up gates, dependency checks, or custom payloads).
310+
> If your `configureFastify` callback already registers `/health` or `/ready`, remove or rename the custom route
311+
> before enabling `enableHealthEndpoints`; Fastify raises a duplicate-route startup error for reused paths.
312+
> If the duplicate route is registered by an async Fastify plugin during `app.register()` boot, Fastify reports its raw
313+
> `FST_ERR_DUPLICATED_ROUTE` error instead of the `enableHealthEndpoints` migration hint.
314+
305315
A common need is a `/health` endpoint for container health checks:
306316

307317
```js
@@ -417,9 +427,10 @@ Keep the three probe types distinct:
417427

418428
- **Startup** answers whether the renderer has finished booting. Separate it from readiness and liveness so slow startup
419429
does not cause premature restarts or block traffic.
420-
- **Readiness** answers whether the renderer should receive new render requests. Use an application-level endpoint such
421-
as the `/health` route in [Adding a Health Check Endpoint](#adding-a-health-check-endpoint), or the built-in `/info`
422-
endpoint for a shallow process check.
430+
- **Readiness** answers whether the renderer should receive new render requests. Use the built-in `/ready` endpoint
431+
only when an out-of-band warm-up render can compile a bundle before probe-gated traffic begins. Without that warm-up
432+
path, prefer a shallow TCP readiness check or the built-in `/info` endpoint, and use `/ready` for monitoring or
433+
post-deploy verification. See [Health and Readiness Endpoints](./health-checks.md) for the full cold-start semantics.
423434
- **Liveness** answers whether the renderer is stuck badly enough that restarting the container is safer. Prefer
424435
`tcpSocket` as the default so transient CPU or GC pauses do not restart an otherwise recoverable renderer; use an
425436
h2c-aware `exec` check only when you intentionally need stricter hung-process detection.

docs/sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const sidebars: SidebarsConfig = {
117117
'building-features/node-renderer/basics',
118118
'building-features/node-renderer/js-configuration',
119119
'building-features/node-renderer/container-deployment',
120+
'building-features/node-renderer/health-checks',
120121
'building-features/node-renderer/debugging',
121122
'building-features/node-renderer/error-reporting-and-tracing',
122123
'building-features/node-renderer/heroku',

0 commit comments

Comments
 (0)