Deployment
Production needs three things on the server: the .NET app, Node.js (for the SSR sidecar), and the built frontend assets.
Build
npm ci
npm run build # client bundle + manifest → wwwroot/react-dotnetcore/client
# SSR bundle → dist/server/entry-server.js
dotnet publish -c Release -o ./publishMake sure the published output includes:
wwwroot/react-dotnetcore/client/**(hashed JS/CSS +.vite/manifest.json)dist/server/entry-server.js(the SSR bundle)ssr-server.mjsand thenode_modulesneeded by it (at minimum@react-dotnetcore/runtime,react,react-dom)
TIP
Run npm run build before dotnet publish, and ensure your .csproj doesn't exclude the built dist/ and wwwroot/react-dotnetcore/ outputs from publish.
Runtime requirements
- Node.js 18+ must be on the server. If
nodeisn't onPATH, setoptions.NodePathto its absolute path. - The app spawns and supervises the sidecar automatically — no separate service to manage by default.
- The sidecar listens on
127.0.0.1only and is never exposed; don't open its port.
Docker
Use a base image (or install steps) that has both the .NET runtime and Node.js. Sketch:
# --- build ---
FROM node:20 AS frontend
WORKDIR /src
COPY . .
RUN npm ci && npm run build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS dotnet
WORKDIR /src
COPY --from=frontend /src .
RUN dotnet publish -c Release -o /app
# --- runtime: needs .NET + Node ---
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dotnet /app .
# include the node_modules the sidecar needs (or `npm ci --omit=dev` here)
ENTRYPOINT ["dotnet", "MyApp.dll"]The key constraint: the runtime image must contain Node and the sidecar's node_modules.
Health & startup
The app gates startup on the sidecar's health check (SidecarStartupTimeout, default 30s). If the sidecar can't start (missing Node, missing bundle), startup fails fast with a clear error — wire your orchestrator's readiness probe to the app's own health endpoint as usual.
Scaling
Each app instance runs its own sidecar on an auto-selected loopback port, so horizontal scaling (more replicas) needs no coordination. Within an instance, the sidecar handles concurrent /render calls.
Checklist
- [ ]
npm run buildran andwwwroot/react-dotnetcore/client+dist/server/entry-server.jsexist - [ ] Node.js present in the runtime environment (
NodePathset if not onPATH) - [ ]
ssr-server.mjsand itsnode_modulesshipped alongside the app - [ ]
app.UseStaticFiles()is enabled (serves the client assets) - [ ] Running as
Production(orDevMode = false) so the built bundle is used