gateway: serve the SPA bundle same-origin (#1444)

New spa/serve-spa.ts: @fastify/static with wildcard:false plus a GET /*
catch-all that returns index.html for non-backend paths and a JSON 404
for unknown /api, /mcp, /socket.io paths. WEB_DIST_DIR unset disables
serving (dev uses the Vite dev server, which proxies to the gateway);
set but invalid fails loud at boot.

A wildcard route, not setNotFoundHandler: Nest installs its own
not-found handler at init and Fastify allows only one. find-my-way
matches most-specific-first, so declared routes win over the catch-all.
Cache semantics stay the library default (max-age=0 + ETag
revalidation); immutable hashed-asset caching is deferred to P6.

gateway.Dockerfile builds the web bundle and ships it at /app/web-dist
with WEB_DIST_DIR set.
This commit is contained in:
fred
2026-08-27 07:32:27 -05:00
parent 70c7311a46
commit 385b2b6886
5 changed files with 194 additions and 62 deletions
+7 -2
View File
@@ -8,14 +8,16 @@ WORKDIR /app
# Copy workspace manifests first for layer-cached install
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
COPY apps/gateway/package.json ./apps/gateway/
COPY apps/web/package.json ./apps/web/
COPY packages/ ./packages/
COPY plugins/ ./plugins/
# the root prepare script runs scripts/install-hooks.mjs on install
COPY scripts/ ./scripts/
RUN pnpm install --frozen-lockfile
COPY . .
# Build gateway and all of its workspace dependencies via turbo dependency graph
RUN pnpm turbo run build --filter @mosaicstack/gateway...
# Build gateway, the web SPA bundle it serves (#1444), and all of their
# workspace dependencies via the turbo dependency graph
RUN pnpm turbo run build --filter @mosaicstack/gateway... --filter @mosaicstack/web...
# Produce a self-contained deploy artifact: flat node_modules, no pnpm symlinks
# --legacy is required for pnpm v10 when inject-workspace-packages is not set
RUN pnpm --filter @mosaicstack/gateway --prod deploy --legacy /deploy
@@ -38,6 +40,9 @@ COPY --chown=node:node --from=builder /deploy/package.json ./package.json
# dist is declared in package.json "files" so pnpm deploy copies it into /deploy;
# copy from builder explicitly as belt-and-suspenders
COPY --chown=node:node --from=builder /app/apps/gateway/dist ./dist
# The built web SPA bundle; served by the gateway (apps/gateway/src/spa/serve-spa.ts)
COPY --chown=node:node --from=builder /app/apps/web/dist ./web-dist
ENV WEB_DIST_DIR=/app/web-dist
# gateway defaults to port 14242 (apps/gateway/src/main.ts)
EXPOSE 14242
USER node