Deploy Static Sites

Temps builds your frontend, extracts the static files, and serves them directly from the proxy — no running container at runtime. You get automatic SPA routing, gzip compression, cache headers, and ETag support out of the box.


How static deployments work

Static deployments follow a different path than server deployments:

  1. Build — Temps generates a multi-stage Dockerfile, installs dependencies, and runs your build command inside a container
  2. Extract — The compiled output (dist/, build/, etc.) is copied from the container to the filesystem
  3. Serve — The Temps proxy serves the files directly. No container runs at runtime.

This means your static site uses zero memory and zero CPU after the build completes. The proxy handles compression, caching, and routing.

What counts as static

A deployment is static when Temps can determine that the build output is a set of HTML, CSS, and JavaScript files with no server component. The following frameworks produce static output by default:

FrameworkBuild outputDetection signal
Vite (React, Vue, Svelte)dist/vite in package.json dependencies
Create React Appbuild/react-scripts in package.json dependencies
Astro (static mode)dist/astro in package.json dependencies
Angulardist/@angular/core in package.json dependencies
Docusaurusbuild/@docusaurus/core in package.json dependencies
Rsbuilddist/@rsbuild/core in package.json dependencies

Vite

Vite is the most common static framework on Temps. It works for React, Vue, Svelte, and vanilla TypeScript projects.

Minimal project

package.json

{
  "name": "my-app",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "vite": "^7.0.0",
    "@vitejs/plugin-react": "^4.0.0"
  }
}

Push this repository to Temps. It detects Vite, runs npm run build, and serves the dist/ directory. No configuration needed.

Environment variables

Vite only exposes environment variables prefixed with VITE_ to the client bundle. Set them in the Temps dashboard:

VariableAvailable in browserAvailable at build time
VITE_API_URLYesYes
VITE_PUBLIC_KEYYesYes
DATABASE_URLNoYes
SECRET_KEYNoYes

src/config.ts

const apiUrl = import.meta.env.VITE_API_URL;

Custom build output

If your Vite config uses a non-default output directory, override it in .temps.yaml:

.temps.yaml

build:
  output_dir: out

Create React App

CRA projects are detected by react-scripts in package.json. Temps runs npm run build and serves the build/ directory.

Environment variables prefixed with REACT_APP_ are embedded in the client bundle:

REACT_APP_API_URL=https://api.example.com
const apiUrl = process.env.REACT_APP_API_URL;

Astro

Astro projects in static mode (the default) are detected and deployed as static files. Temps runs npm run build and serves the dist/ directory.

astro.config.mjs

import { defineConfig } from 'astro/config';

export default defineConfig({
  // Static mode is the default — no output config needed
});

Angular

Angular projects are detected by @angular/core in package.json. Temps runs npm run build and serves the dist/ directory.

For Angular projects that use a subfolder output structure (e.g. dist/my-app/browser/), override the output directory:

.temps.yaml

build:
  output_dir: dist/my-app/browser

Docusaurus

Docusaurus v2 and v3 projects are detected by @docusaurus/core in package.json. Temps runs npm run build and serves the build/ directory.


SPA routing

Single-page applications use client-side routing — paths like /about or /dashboard/settings do not correspond to files on disk. The Temps proxy handles this automatically.

When a request arrives for a path that:

  • Has no file extension (e.g. /about, not /about.css)
  • Does not match any file on disk

The proxy serves index.html instead. Your client-side router (React Router, Vue Router, etc.) then handles the route.

This works out of the box with no configuration. You do not need to add _redirects, vercel.json, or any rewrite rules.


Compression and caching

The Temps proxy applies performance optimizations to all static file responses:

Gzip compression

Text-based files larger than 1 KB are gzip-compressed automatically when the browser supports it. Compressed content types include:

  • text/html, text/css
  • application/javascript, application/json
  • application/xml, image/svg+xml

Cache headers

Temps sets Cache-Control headers based on the file path:

PatternCache-ControlUse case
/assets/*, /static/*, /_next/static/*, .chunk.*, .hash.*public, max-age=31536000, immutableHashed assets that never change
Everything elsepublic, max-age=0, must-revalidateHTML files and non-hashed resources

This means your hashed JS and CSS bundles (e.g. assets/index-a1b2c3.js) are cached for one year, while index.html is always revalidated to pick up new deployments.

ETag support

Every file gets a weak ETag based on its content hash. On subsequent requests, if the browser sends If-None-Match and the content has not changed, the proxy responds with 304 Not Modified — saving bandwidth and latency.


Custom build commands

Override the auto-detected install and build commands in .temps.yaml:

.temps.yaml

build:
  install_command: pnpm install --frozen-lockfile
  build_command: pnpm run build:production
  output_dir: dist
  • Name
    install_command
    Type
    string
    Description

    Override the dependency install step. Defaults to the detected package manager's install command (npm ci, yarn install --frozen-lockfile, pnpm install --frozen-lockfile, or bun install).

  • Name
    build_command
    Type
    string
    Description

    Override the build step. Defaults to npm run build (or equivalent for your package manager).

  • Name
    output_dir
    Type
    string
    Description

    Override the output directory. Defaults to the framework's standard output directory (dist/, build/, etc.).


Package manager detection

Temps detects your package manager from lockfiles:

LockfilePackage managerInstall command
pnpm-lock.yamlpnpmpnpm install --frozen-lockfile
package-lock.jsonnpmnpm ci
yarn.lockYarnyarn install --frozen-lockfile
bun.lockbBunbun install

If no lockfile is found, npm is used as the default. The correct lockfile is detected before the build starts and the matching package manager is used in the generated Dockerfile.


Troubleshooting

Blank page after deployment

Your SPA routes to / but the build output is missing index.html. Verify that your build command produces an index.html in the output directory. For Vite, check that vite.config.ts does not override build.outDir to a non-standard path without updating .temps.yaml.

404 on page refresh

If client-side routes return 404, the SPA fallback may not be matching. This happens when the URL contains a file extension (e.g. /api/data.json) — the proxy serves those literally. For API calls, use a separate backend deployment or ensure your API paths are proxied correctly.

Environment variables not available at runtime

Vite, CRA, and other static frameworks embed environment variables at build time. If you change a VITE_* variable in the dashboard, you must trigger a new deployment for the change to take effect. The variable is baked into the JavaScript bundle during the build step.

Wrong output directory

If your build produces files in a non-standard directory, Temps may not find the output. Set build.output_dir in .temps.yaml to the correct path. Check the build logs to see where files are written.


What to explore next

Deploy server-rendered applications React deployment tutorial Set up a custom domain Preview deployments

Was this page helpful?