---
id: platform/static-sites
title: Static sites
description: Publish static files from Git, configure redirects and headers, and troubleshoot build and routing failures.
keywords: [bex, static site, publish path, redirects, rewrites, headers]
last_updated: 2026-09-23
---

A static site serves HTML, CSS, JavaScript, and other files from object storage
through Bex's shared static server. It does not run a dedicated application
server. Use a [web service](./web-services.md) if your application requires
server-side rendering, API handlers, or runtime computation per request.

Self-hosted installations need a configured object store, publishing pipeline,
static server, and public routing. An operator-only local installation is not
a complete static hosting setup.

## Publish files without a build

This example publishes the repository's sample site as-is:

```yaml
services:
  - name: static-site
    type: web
    runtime: static
    repo: https://github.com/bex-co/bex
    rootDir: examples/static-site
    branch: main
    staticPublishPath: .
```

`staticPublishPath` selects the directory to publish. Here, `.` refers to the
selected root directory. Check that it contains the intended `index.html` and
assets. Follow the [Blueprint guide](./app-resource.md) to authenticate, validate,
and deploy the manifest.

## Build a site before publishing

For a Node-based static project with a committed lockfile and a build script
that writes to `dist`, configure:

```yaml
services:
  - name: frontend
    type: web
    runtime: static
    repo: https://github.com/your-org/frontend
    branch: main
    buildCommand: npm ci && npm run build
    staticPublishPath: dist
```

Replace the repository and commands with your project's values. A static
build needs no start command: Bex extracts and publishes the output files.
Confirm that your framework is producing a static export rather than a server
bundle. Configuration embedded into browser assets is public; do not put
server credentials into frontend build variables.

## Redirects, rewrites, and response headers

A redirect tells the browser to request another URL. A rewrite serves a
different file without changing the browser's URL. Rules are ordered, so place
specific redirects before a catch-all rewrite.

For API examples, set `BEX_API_URL` to your API origin, `BEX_TOKEN` to an
authorized token, and `SERVICE_ID` to the static site's id. The following
requests **replace the complete list**. Read the current list with GET and
include any rules you want to keep before sending PUT.

```bash
curl --fail-with-body -X PUT "$BEX_API_URL/v1/services/$SERVICE_ID/routes" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"type":"redirect","source":"/old/*","destination":"/new/:splat"},{"type":"rewrite","source":"/*","destination":"/index.html"}]'

curl --fail-with-body -X PUT "$BEX_API_URL/v1/services/$SERVICE_ID/headers" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"path":"/*","name":"X-Frame-Options","value":"DENY"}]'
```

Both endpoints accept a JSON **array**, not a `routes` or `headers` wrapper.
Redirects return 301. Rewrites serve the destination object when it exists.
The `/*` to `/index.html` rewrite is useful for a client-side router; include it
only when your app should handle those paths. These rule changes do not require
rebuilding the site, but allow time for the configuration to propagate.

## Verify and troubleshoot

Open the site's URL after deployment, then test an asset and a nested page
by navigating directly to their URLs.

- **Build fails:** check the build command, lockfile, root directory, and build logs.
- **Publish fails:** verify that the output directory exists and contains files;
  on a self-hosted instance, inspect publishing and object-store configuration.
- **Homepage returns 404:** check the publish path and `index.html` location.
- **Nested client routes return 404:** add an appropriate rewrite if the app
  uses client-side routing.
- **Missing assets:** check base paths, filename capitalization, and whether
  the build included the referenced files.

See [Custom domains](./custom-domains.md), [TLS](./tls.md), and
[How deploys work](./how-deploys-work.md) for routing and release details.
