# Host a static website in a bucket

> Serve a bucket as a website with an index document, a custom error page or a single page app fallback, on its public URL and your own domain.

Source: https://www.coritan.com/docs/object-storage/static-websites/

In the dashboard:

- /dashboard/storage/…/buckets/…/public: https://www.coritan.com/dashboard/storage

A bucket can serve a static website: HTML, CSS, JavaScript, images and any other files that a browser reads as they are. In website mode, an address with no file name answers with the folder's index document, and a missing page answers with your error page or your app's own page. The site answers on the bucket's [public URL](/docs/object-storage/public-access/) and its [custom domains](/docs/object-storage/custom-domains/).

A bucket runs no code of its own. For a site that needs a server, use [App Deployment](/docs/managed-containers/static-sites/) instead.

## Before you begin

- Everything in [Serve a bucket publicly](/docs/object-storage/public-access/#before-you-begin).
- The site's files, built and ready to upload. Upload them with the dashboard ([Upload, download and delete objects](/docs/object-storage/objects/)) or an S3 client ([Upload the site](#upload-the-site)).
- The public URL turned on, or a verified custom domain. Search engines do not index the public URL, so a site you want found needs a custom domain.

## Turn on website mode

1. Open the service, select the **Buckets** tab, then select the bucket.
2. Select the **Public access** tab.
3. On the **Website** card, tick **Serve as a website**.
4. In **Index document**, keep `index.html` or type the key of your index page.
5. Choose what a missing page answers:
   - For a site of separate pages, type the key of your error page in **Error document**, such as `404.html`. Leave it empty for a plain `Not Found`.
   - For a single page app (React, Vue, Svelte and the like) whose own router shows each page, tick **Single page app**.
6. Select **Save website settings**.

A message confirms it: `Website settings saved.` Saving purges what our edge cached for the bucket, so visitors see the new settings at once.

## Result

With website mode on, the bucket answers like this, here with the index document `index.html` and the error document `404.html`:

| Request | Answer |
| --- | --- |
| `/` | `index.html`, status `200` |
| `/docs/` | `docs/index.html`, status `200` |
| `/docs`, when there is no object `docs` and there is a `docs/index.html` | A redirect to `/docs/` |
| `/about.html` | The object `about.html` |
| A key with no object | `404.html`, status `404` |
| A key with no object, with **Single page app** ticked | `index.html`, status `200` |

When the error document or the index document is missing too, the answer is a plain `Not Found` with status `404`. Everything else in [What visitors can do](/docs/object-storage/public-access/#what-visitors-can-do) still applies.

Keys are case sensitive, and a key is the whole path after the domain. `docs/index.html` is a different object from `Docs/index.html`.

## Upload the site {#upload-the-site}

An S3 client uploads a whole folder at once. With the AWS CLI and a profile for your access key ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/)):

```bash
aws --profile coritan s3 sync ./dist s3://u7-site \
  --endpoint-url https://s3.fra.coritan.com:7337 --delete
```

`--delete` removes objects that are no longer in the folder. The AWS CLI sets each file's `Content-Type` from its extension, which is what browsers need to show a page or run a script.

After a deploy, [purge the cache](/docs/object-storage/public-access/#cache) so visitors get the new files at once. Or give the HTML a short `Cache-Control` when you upload it, so our edge and browsers check for a new copy sooner:

```bash
aws --profile coritan s3 cp ./dist/index.html s3://u7-site/index.html \
  --endpoint-url https://s3.fra.coritan.com:7337 --cache-control "max-age=60"
```

## Turn website mode off

Untick **Serve as a website** and select **Save website settings**. The message says `Website mode off.` Objects stay readable by their full key, and `/` answers `404` again.

## Troubleshooting

`/` answers `Not Found`
: Website mode is off, or the bucket holds no object with the index document's key. Check **Index document** against the key in the object browser.

`Enter the index document as an object key, such as index.html.`
: Type the key without a trailing `/`, and with no `.` or `..` folder in it. A `/` at the start is dropped for you.

`The single page fallback answers every missing path with the index document. Turn it off to use an error document.`
: A single page app cannot also have an error document. In the dashboard, ticking **Single page app** clears **Error document** for you.

The browser downloads a page instead of showing it
: The object's `Content-Type` is wrong, such as `application/octet-stream` for an HTML file. Upload it again with `--content-type "text/html"`, or with a client that sets the type from the extension.

A page that worked shows the old version
: [Purge the cache](/docs/object-storage/public-access/#cache), then reload the page.

## Related

- [Serve a bucket on your own domain](/docs/object-storage/custom-domains/)
- [Serve a bucket publicly](/docs/object-storage/public-access/)
- [Let websites use a bucket with CORS rules](/docs/object-storage/cors/)
- [Static sites on App Deployment](/docs/managed-containers/static-sites/)

## With the API

[`PUT /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/website`](/docs/api/reference/client/object-storage/object-storage-buckets/#op-put-api-v1-client-object-storage-service-id-buckets-bucket-id-website) saves the website settings and answers with the whole public access view, as [`GET .../public`](/docs/object-storage/public-access/#with-the-api) does:

```bash
curl -X PUT https://api.coritan.com/api/v1/client/object-storage/1207/buckets/31/website \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "index": "index.html", "error": "404.html", "spa": false}'
```

`enabled`
: Whether the bucket serves as a website.

`index`
: The index document's key. Leave it out for `index.html`.

`error`
: The error document's key, served with status `404`. Leave it out, or send `null`, for a plain `Not Found`.

`spa`
: Answer every missing key with the index document and status `200`. It cannot be `true` while `error` is set.

| Status | `detail` | Cause |
| --- | --- | --- |
| `409` | `This service is pending; it can be changed once it is active` | The service is not `active`. The message names its status. |
| `422` | `Enter the index document as an object key, such as index.html.` | The key ends in `/`, or has an empty, `.` or `..` folder. |
| `422` | `The single page fallback answers every missing path with the index document. Turn it off to use an error document.` | `spa` and `error` were both set. |
| `429` | An object with `"error": "rate_limited"` and `retry_after_seconds` | Your account made 60 public access changes in the last hour. |

## API

- `PUT /api/v1/client/object-storage/{service_id}/buckets/{bucket_id}/website`: Serve the bucket as a static website on its public URL and custom domains (https://www.coritan.com/docs/api/reference/client/object-storage/object-storage-buckets/#op-put-api-v1-client-object-storage-service-id-buckets-bucket-id-website)
