# Static site

> The files a build writes, served by nginx with nothing running behind it

A static site is the files a build writes — Astro, Hugo, Eleventy, Jekyll, a
Vite or React single-page application — served by nginx, with nothing running
behind it: no PHP, no server of your own, no database. Choose **Static site**
as the application when you order, and the machines run nginx alone. In front
of it is the platform's proxy, which terminates TLS and routes the hostname,
and the [CDN](cdn.md) where the environment has one.

## What it serves

The release's `public/` directory, read-only:

- a path is a file, a directory's `index.html`, or the page of that name:
  `/about` serves `about.html` or `about/index.html`;
- any other path is a 404, with your own `404.html` where `public/` has one;
- styles, scripts, images and fonts go out with a year's `Expires` and
  `Cache-Control: public`, so build them with hashed names, as every bundler
  does. `NGINX_STATIC_EXPIRES` shortens it.

A generator that writes somewhere else — `dist/` for Vite and Astro, `_site/`
for Jekyll and Eleventy, `out/` for a Next.js export — has its last build step
move it there: `mv dist public`.

## A static site

`vallic.yaml` at the root of the repository:

```yaml
version: 1
type: static

runtime:
  nginx: '1.31'

services:
  - node: '26'

build:
  steps:
    - name: Site
      image: node
      run: npm ci && npm run build && rm -rf public && mv dist public && rm -rf node_modules

health:
  path: /
```

The build runs in [Node.js](stack-node.md), a build tool that never runs beside
the site. A site committed as files needs no build at all: put them in
`public/` and leave `build` out.

**No `cache`, and `node_modules` removed.** Whatever is in the checkout when the
build ends is the release, copied to every machine, and nothing in a static
site's release runs its `node_modules`. npm's downloads are kept between builds
for you, so `npm ci` installs from them without going to the network.

`runtime.nginx` is the version of nginx that serves it, matched to the current
build of it the way a language version is. There is no `start` and no `port`:
nginx is the server, on its own port.

A redirect, a header or a location of your own goes in
`.vallic/nginx/server.conf`: see [Rules of your own](stack-nginx.md#rules-of-your-own).

## A single-page application

An application that routes in the browser — React Router, Vue Router — wants
every path that is not a file to load its `index.html` and answer 200, so that
the router draws the page. Say so in the server's settings:

```yaml
services:
  - static:
      version: '1.31'
      environment:
        NGINX_HTML_FALLBACK: index.html
```

Every entry under `services` needs a version, and which nginx runs is still
`runtime.nginx`, so keep the two the same. Without the setting, a path with no
file is a 404.

## Deploy steps, jobs and the shell

Deploy steps, scheduled jobs and [`vallic ssh`](shell.md) run in nginx's
container, which has `sh`, `curl` and `wget` and nothing to build with. That is
enough to empty the CDN after a release, which is what a static site's deploy
usually does: see [Clearing the cache from your code](cdn.md#clearing-the-cache-from-your-code).
A release goes live without nginx restarting, so no request is dropped.

No database, cache, search engine, queue or mail relay is offered to a static
site: nothing behind nginx would ever reach one. A site that needs any of them
is an application of its own — Node.js, PHP or another — rather than a static
site. The type is chosen when the project is bought, and changing it once a
release is serving is a migration rather than a setting; see
[Configuration](configuration.md#type-and-the-project-it-runs-in).

## Versions

| Version | Status |
| --- | --- |
| `1.31` | Supported, and the default |
| `1.30` | Supported |

Pin the version, not the build: name `1.31` and the platform matches it to the
current build, so a security rebuild reaches you without anybody editing a
repository.

## What you can change

In [`vallic.yaml`](configuration.md) — what each one does is on [Service settings](service-settings.md):

```yaml
services:
  - static:
      version: '1.31'
      environment:
        NGINX_KEEPALIVE_TIMEOUT: …
        NGINX_GZIP_COMP_LEVEL: …
        NGINX_STATIC_EXPIRES: …
        NGINX_ERROR_LOG_LEVEL: …
        NGINX_INDEX_FILE: …
        NGINX_HTML_FALLBACK: …
```

Anything not on this list refuses the deploy, naming the variable — rather
than being accepted and quietly ignored.
