Static sites and SPAs
Frameworks such as Vite, Create React App, Vue, Angular, and Astro (static mode) produce a folder of HTML, CSS, and JavaScript files. On Zenifra, a lightweight HTTP server delivers that folder.
Which path to choose
| Path | Advantage | Best for |
|---|---|---|
GitHub + Node.js with serve | No Dockerfile, deploys on every push | Most sites and SPAs |
| OCI Image with NGINX | Full control over caching, headers, and redirects | Teams already using NGINX or needing advanced rules |
Option 1: GitHub with the Node.js runtime
Add serve as a dependency:
npm install serveCheck the folder your framework builds into:
| Framework | Folder |
|---|---|
| Vite (React, Vue, Svelte) | dist |
| Create React App | build |
| Angular 17+ | dist/<project-name>/browser |
| Astro (static) | dist |
Fill in the project in the console:
| Field | Value |
|---|---|
| Runtime | Node.js |
| Port | 3000 |
build | npm run build |
start | npx serve -s dist -l tcp://0.0.0.0:3000 |
Replace dist with the folder from the previous table.
The -s option makes serve answer unknown routes with index.html, which SPAs using React Router, Vue Router, or Angular Router need. For multi-page sites such as Astro, drop -s.
Environment variables in static sites
Variables such as VITE_API_URL are embedded in the JavaScript at build time. On Zenifra, project variables exist during the build, so they work normally. After changing one, deploy again to produce a new bundle. Never put secrets in these variables: they are visible in the browser.
Option 2: OCI Image with NGINX
Use the official NGINX image, which listens on port 80:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80server {
listen 80;
root /usr/share/nginx/html;
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
}Build, push to your registry, and create an OCI Image project with Port 80. See Ready-made Dockerfiles for build and push commands and Deploy via OCI image for the console fields.
Domain and HTTPS
The project gets a *.clients.zenifra.com URL with HTTPS. To use your own domain, follow Custom domain.
Common problems
| Symptom | Fix |
|---|---|
Blank page and 404s for .js files | Wrong folder in start or incorrect base in vite.config |
A route such as /login returns 404 on reload | Use serve -s or try_files ... /index.html |
VITE_* variable is empty | Add it before the build and deploy again |
serve: not found | serve is not in dependencies |
Next steps
- Next.js for server-rendered sites
- Preview Environments
- Framework guides
Last updated on
Node.js APIs with Express, Fastify, and NestJS
Configure Node.js APIs on Zenifra with Express, Fastify, or NestJS: port, commands, TypeScript, migrations, WebSockets, health check, and common errors.
Python with FastAPI and Flask
Deploy Python APIs on Zenifra with FastAPI or Flask: requirements.txt, uvicorn, gunicorn, workers, port, Alembic migrations, and common errors.