> For the complete documentation index, see [llms.txt](https://docs.hollaex.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hollaex.com/advanced/customizing-your-frontend-projects-with-hollacloud-json.md).

# Customizing Your Frontend Projects with hollacloud.json

## Configure routing with `hollacloud.json`

When you host a website on HollaCloud, you can control how it routes traffic - redirects, rewrites, custom headers, trailing slashes, lowercase URLs, path mounts, and SPA fallback behavior - by adding a single file called `hollacloud.json` to your project. If you have used `vercel.json` before, this will feel right at home.

You do not need this file for a normal site. Add it only when you want to customize routing, host an independent project under a path, or override HollaCloud's automatic SPA behavior.

### Adding the file

1. Create `hollacloud.json` in the root of your project, next to files such as `package.json`, `index.html`, or `hugo.toml`. For a monorepo, place it at the root of the project path you publish.
2. Add the settings and rules you need. Every section is optional.
3. Republish your project from the dashboard. The config is applied on every publish.

Changes take effect only after you republish. Editing the file in your repository is not enough on its own.

### A quick example

This minimal file sends visitors from an old URL to a new one:

```json
{
  "redirects": [
    {
      "source": "/old-pricing",
      "destination": "/pricing"
    }
  ]
}
```

### Site settings

The optional `site` object controls where an independently published project is mounted and whether HollaCloud should override its automatic SPA behavior.

```json
{
  "site": {
    "basePath": "/docs",
    "spa": true
  }
}
```

Both fields are optional for a root site. A project hosted under a path must declare a matching `basePath`.

#### SPA behavior

For most sites, leave `site.spa` out. HollaCloud automatically handles the common cases:

* Real files, clean URLs, and directory index files are served first.
* A site-provided `404.html` handles unknown routes when it exists.
* Otherwise, an eligible browser navigation can fall back to `index.html`, so a client-side router can handle the route.
* Static asset requests and non-HTML requests do not use the SPA fallback.

This default works for most static sites and SPAs. If you already use `hollacloud.json` for other rules, simply leave `site.spa` out.

If automatic detection does not fit your build, or you know that every eligible client-side route must be handled by your SPA, set `site.spa` to `true`:

```json
{
  "site": {
    "spa": true
  }
}
```

With `true`, eligible HTML navigations fall back to `index.html` even if the site includes a `404.html`. Real files still take priority, and missing assets never receive a successful SPA `index.html` fallback. A site-local `404.html` may still be returned with a `404` status.

Use this override deliberately. In most cases, omitting `spa` is the better choice because it lets HollaCloud distinguish static-site 404 pages from client-side routes automatically.

To explicitly disable SPA fallback for a static or multi-page site, set it to `false`:

```json
{
  "site": {
    "spa": false
  }
}
```

Real files, clean URLs, directory indexes, and a site-local `404.html` continue to work when SPA fallback is disabled.

The `spa` field must be inside `site`. A top-level `"spa": true` setting is not recognized.

### Hosting independent sites under a path

HollaCloud can serve separately built and separately published projects from the same hostname. For example:

```
https://exchange.example.com/          root project
https://exchange.example.com/docs/     docs project
https://exchange.example.com/pricing/  pricing project
```

Each project owns its configuration. The root project does not list or manage the other projects' mount paths.

In the docs project, add:

```json
{
  "site": {
    "basePath": "/docs"
  }
}
```

In the pricing project, add:

```json
{
  "site": {
    "basePath": "/pricing"
  }
}
```

The declared path must exactly match the path where the project is published. It is case-sensitive, must start with `/`, and must not end with `/`. A base path can contain at most 32 path segments and 1,024 characters. Query strings, fragments, percent escapes, backslashes, repeated slashes, `.` or `..` segments, control characters, and paths ending in a known static-file extension are not allowed.

The project's build must also be configured for the same base path so that generated asset URLs point inside the mount. For example, a docs build hosted at `/docs` should generate asset URLs such as `/docs/assets/app.js`, not `/assets/app.js`.

Mounted projects are independent sites:

* Files are resolved relative to that project's root.
* Redirect, rewrite, header, lowercase URL, and trailing-slash rules are matched relative to the mount path.
* `index.html`, `404.html`, and `site.spa` apply only to that project.
* `/docs` redirects to `/docs/` so relative browser URLs stay inside the mounted project. Query strings are preserved.
* The longest matching path wins. `/docs/admin` can be a separate mount inside `/docs`.
* Mounts match complete path segments. `/docs-test` does not match `/docs`.

For a `/docs` project, this config redirects `/docs/old` to `/docs/new`:

```json
{
  "site": {
    "basePath": "/docs"
  },
  "redirects": [
    {
      "source": "/old",
      "destination": "/new",
      "statusCode": 302
    }
  ]
}
```

To force SPA behavior for only the docs project:

```json
{
  "site": {
    "basePath": "/docs",
    "spa": true
  }
}
```

The root project can also use `site.spa`, but it normally omits `basePath`. An explicit root declaration of `"basePath": "/"` is allowed but unnecessary.

### Redirects

Redirects send visitors to a different URL. The address bar changes.

```json
{
  "redirects": [
    {
      "source": "/old-blog",
      "destination": "/blog"
    },
    {
      "source": "/docs/:path*",
      "destination": "/help/:path*"
    },
    {
      "source": "/sale",
      "destination": "/summer-sale",
      "statusCode": 302
    },
    {
      "source": "/chat",
      "destination": "https://discord.gg/your-invite"
    }
  ]
}
```

* `source` is the path to match. It must start with `/`.
* `destination` is where to send the visitor. Use an internal path such as `/blog` or a full external URL such as `https://example.com`.
* A redirect is permanent (`301`) by default. Override it with `statusCode`. Supported values are `301`, `302`, `307`, and `308`; use `302` or `307` for a temporary redirect.

Inside a mounted project, use a normalized internal destination such as `/blog`, without `.` or `..` path segments, to keep the redirect inside that mount. Use a full `https://` URL for an external destination.

### Rewrites

A rewrite serves a different file while keeping the visitor's URL unchanged. This can be useful when only part of a site should be handled by a client-side application:

```json
{
  "rewrites": [
    {
      "source": "/app/:path*",
      "destination": "/index.html"
    }
  ]
}
```

Now any matching URL under `/app/...` serves `index.html`, and the client-side router can take over. A rewrite in a mounted project cannot escape that project's published root.

You usually do not need a catch-all rewrite for a conventional SPA because HollaCloud's automatic SPA behavior already handles eligible browser routes.

### Custom headers

Add response headers to specific paths, for example for caching or security:

```json
{
  "headers": [
    {
      "source": "/assets/:path*",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=86400"
        }
      ]
    }
  ]
}
```

`X-Static-Gateway-Site` and `X-Static-Gateway-Fallback` are reserved for HollaCloud's internal cache isolation and are removed from visitor responses. Do not use them as custom response headers.

### Trailing slashes

Control whether visitor-facing URLs end with `/`:

```json
{
  "trailingSlash": true
}
```

* `true`: `/about` redirects to `/about/`.
* `false`: `/about/` redirects to `/about`.
* Omitted: URLs stay as visitors typed them.

If you set `trailingSlash` to `true`, write redirect source paths with the trailing slash, for example `/old-page/`. The slash is added before redirect rules run, so a source without it will not match. When in doubt, leave this setting out.

The root of a mounted project is always canonicalized with a slash. For example, `/docs` redirects to `/docs/` regardless of this setting.

### Lowercase URLs

Redirect visitor-facing paths to lowercase:

```json
{
  "lowercaseUrls": true
}
```

With this enabled, `/BtC` redirects to `/btc`. Query strings and static files with extensions are preserved.

Write redirect, rewrite, and header source paths in lowercase when this option is enabled. URL normalization happens before those rules run.

### Matching patterns

Two tokens are available in `source` and `destination`:

* `:name` matches one path segment. `/users/:id` matches `/users/42`, but not `/users/42/posts`.
* `:path*` matches the rest of the path. `/docs/:path*` matches `/docs/a/b/c`.

Captured values can be reused in a destination, such as `/help/:path*`.

Full regular expressions are not supported. Only `:name` and `:path*` are interpreted, which keeps matching fast and predictable.

### Full example

This root-site config uses all routing features and explicitly opts into SPA fallback. Most sites should omit the `site` object and use automatic SPA behavior instead.

```json
{
  "site": {
    "spa": true
  },
  "lowercaseUrls": true,
  "trailingSlash": false,
  "redirects": [
    {
      "source": "/old-blog",
      "destination": "/blog"
    },
    {
      "source": "/docs/:path*",
      "destination": "/help/:path*"
    },
    {
      "source": "/u/:id",
      "destination": "/users/:id"
    },
    {
      "source": "/sale",
      "destination": "/summer-sale",
      "statusCode": 302
    },
    {
      "source": "/chat",
      "destination": "https://discord.gg/your-invite"
    }
  ],
  "rewrites": [
    {
      "source": "/app/:path*",
      "destination": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/assets/:path*",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=86400"
        }
      ]
    },
    {
      "source": "/:path*",
      "headers": [
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        }
      ]
    }
  ]
}
```

### Good to know

* Rules are applied in this order: mount selection, lowercase URL normalization, trailing-slash normalization, redirects, rewrites, files, and response headers.
* Each routing section can contain up to 400 rules.
* `hollacloud.json` can be at most 256 KiB.
* The HollaCloud publish flow validates `hollacloud.json`. If validation fails, publishing stops with a message explaining what to fix, and the currently live site remains untouched.
* If publish validation is bypassed, the Worker rejects an invalid mounted-site config instead of falling back to the root site.
* Config contents are never served to visitors. A direct request or rewrite to a valid `hollacloud.json` path returns `404`; a site-resolution error may return `503` instead.
* `site`, `X-Static-Gateway-Site`, and `X-Static-Gateway-Fallback` are reserved by HollaCloud.
* A mounted project's `site.basePath` must match its published path exactly.
* A root-project deployment must not delete separately published child paths. Each project should publish only to the path it owns.
