ci: prerender against the internal mesh API + fail loud if empty
All checks were successful
deploy / Build api + worker (static musl) (push) Successful in 6m11s
deploy / Build prerendered web (push) Successful in 7m28s
deploy / Deploy web to oolon (push) Successful in 55s
deploy / Deploy moments-api to nikola (push) Successful in 59s
deploy / Deploy moments-worker to frootmig (push) Successful in 1m1s
All checks were successful
deploy / Build api + worker (static musl) (push) Successful in 6m11s
deploy / Build prerendered web (push) Successful in 7m28s
deploy / Deploy web to oolon (push) Successful in 55s
deploy / Deploy moments-api to nikola (push) Successful in 59s
deploy / Deploy moments-worker to frootmig (push) Successful in 1m1s
The fedora-44 runner reaches the public internet (the CV gist on api.github.com prerendered fine) but not the public rob.tn — split-horizon DNS — so every moments-API-sourced route baked empty loading states while the build still "succeeded". prefetchQuery swallows fetch errors, so the empty snapshot shipped silently. - Point VITE_API_BASE at the internal API the deploy already reaches (http://nikola.kosherinata.internal:42424/v1), verified reachable over the mesh. Only affects the SSR/prerender build; the browser bundle still uses the relative /api/v1. - run-prerender.mjs now aborts (exit 1) if zero dynamic routes are enumerated, i.e. the API was unreachable — so an empty snapshot can never deploy again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
This commit is contained in:
@@ -48,9 +48,12 @@ env:
|
|||||||
API_UPSTREAM_SCHEME: http
|
API_UPSTREAM_SCHEME: http
|
||||||
API_UPSTREAM_ADDR: nikola.kosherinata.internal:42424
|
API_UPSTREAM_ADDR: nikola.kosherinata.internal:42424
|
||||||
MUSL_TARGET: x86_64-unknown-linux-musl
|
MUSL_TARGET: x86_64-unknown-linux-musl
|
||||||
# The prerender fetches the live public API at build time; the client bundle
|
# Build-time prerender base: the runner is on the internal mesh and can't
|
||||||
# keeps the same-origin relative /api/v1 that nginx proxies.
|
# resolve/reach the public rob.tn, so fetch the API directly over the mesh
|
||||||
VITE_API_BASE: https://rob.tn/api/v1
|
# (same host the deploy reaches). The api binary serves its routes under /v1.
|
||||||
|
# The client bundle still ships the same-origin relative /api/v1 that nginx
|
||||||
|
# proxies (VITE_API_BASE only affects the SSR build, not the browser bundle).
|
||||||
|
VITE_API_BASE: http://nikola.kosherinata.internal:42424/v1
|
||||||
DEPLOY_KEY: |
|
DEPLOY_KEY: |
|
||||||
${{ secrets.RSYNC_SSH_KEY }}
|
${{ secrets.RSYNC_SSH_KEY }}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ env:
|
|||||||
API_PORT: "42424"
|
API_PORT: "42424"
|
||||||
API_UPSTREAM_SCHEME: http
|
API_UPSTREAM_SCHEME: http
|
||||||
API_UPSTREAM_ADDR: nikola.kosherinata.internal:42424
|
API_UPSTREAM_ADDR: nikola.kosherinata.internal:42424
|
||||||
VITE_API_BASE: https://rob.tn/api/v1
|
# Internal mesh API base (the runner can't reach the public rob.tn); see
|
||||||
|
# deploy.yml. The api serves its routes under /v1.
|
||||||
|
VITE_API_BASE: http://nikola.kosherinata.internal:42424/v1
|
||||||
DEPLOY_KEY: |
|
DEPLOY_KEY: |
|
||||||
${{ secrets.RSYNC_SSH_KEY }}
|
${{ secrets.RSYNC_SSH_KEY }}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,22 @@ function outPathFor(route) {
|
|||||||
const template = await readFile(join(distDir, 'index.html'), 'utf8');
|
const template = await readFile(join(distDir, 'index.html'), 'utf8');
|
||||||
let routes = await collectRoutes();
|
let routes = await collectRoutes();
|
||||||
|
|
||||||
|
// Guard: the dynamic routes (blog posts, projects) are enumerated from the
|
||||||
|
// moments API. If none came back, the API was unreachable at build time — fail
|
||||||
|
// loudly rather than bake and deploy an empty, content-free snapshot. (The CV,
|
||||||
|
// sourced from the public GitHub gist, can still succeed independently.)
|
||||||
|
const dynamicRouteCount = routes.filter(
|
||||||
|
(r) => r.startsWith('/project/') || r.startsWith('/blog/'),
|
||||||
|
).length;
|
||||||
|
if (dynamicRouteCount === 0) {
|
||||||
|
console.error(
|
||||||
|
'prerender: 0 dynamic routes enumerated — the moments API was unreachable ' +
|
||||||
|
'at build time. Aborting so an empty snapshot is never deployed. ' +
|
||||||
|
`(VITE_API_BASE=${process.env.VITE_API_BASE ?? '(unset)'})`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Optional filter args: `node run-prerender.mjs /activity /cv` bakes only the
|
// Optional filter args: `node run-prerender.mjs /activity /cv` bakes only the
|
||||||
// routes whose path starts with one of the given prefixes (useful for quick
|
// routes whose path starts with one of the given prefixes (useful for quick
|
||||||
// iteration). With no args, every route is baked.
|
// iteration). With no args, every route is baked.
|
||||||
|
|||||||
Reference in New Issue
Block a user