Serverless maintenance page w/ Cloudflare
Tags: javascript, tailwindcss, webdev, hacktoberfest
Published: 2022-10-06 18:01:23

### History
A few weeks ago I realized that missed list from the hosting provider about the planned network equipment update and got `connection timeout` on production servers. Hopefully, downtime was less than 15 minutes and business wasn't impacted, but our client must know what's going on and we need a maintenance screen, which will not depend on our servers. After this accident, I start thinking about how to resolve it. Few days after i realized that i can pass traffic using Cloudflare Worker's routes even before it pass target servers. 

### Requirements
* Website proxied using Cloudflare
* Cloudflare Free plan
* Maintenance page HTML

### Project setup
**Init basic `npm` project**

```
mkdir maintenance && cd maintenance && npm init
```

**Init wrangler project**
```
npx wrangler init
```
_Select fetch handler in init script_

**Let's test it**
After adding scripts to `packages.json`
```
"workers:dev": "wrangler dev",
"workers:deploy": "wrangler publish",    
```

After these changes run 
```
npm run workers:dev
```
Result:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afjimuuilc8frx08cxj6.png)

Okay, now we have our worker handler, next steps are to create our `index.html` and its assets. 

Create `index.html`
```sh
mkdir public && touch public/index.html
```

Add `site` attribute to `wrangler.toml`

```
site = { bucket = "./public" }
```

**let's update our worker code**

Install KV package 
```sh
npm install -D @cloudflare/kv-asset-handler
```
Import `kv-asset-handler` and add assetManifest

```js
import {getAssetFromKV} from "@cloudflare/kv-asset-handler";
// @ts-ignore defined by workers
import manifestJSON from '__STATIC_CONTENT_MANIFEST'

const assetManifest = JSON.parse(manifestJSON)
```

Update `Env` interface

```js
export interface Env {
    __STATIC_CONTENT: KVNamespace;
}
```

**Fetch handler code**
> This code will be executed for any matched route

```js
const { pathname } = new URL(request.url);

try { 
    const page = await getAssetFromKV(
        {
            request,
            waitUntil(promise) {
                return ctx.waitUntil(promise)
            },
        },
        {
            mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/index.html`, req),
            ASSET_NAMESPACE: env.__STATIC_CONTENT,
            ASSET_MANIFEST: assetManifest
        },
    )

    return new Response(page.body, {status: 503})
} catch (e) {
    return new Response("Something went wrong", {
        status: 500
    })
}
```

**Let's test it again**
```sh
npm run workers:dev
```
`public/index.html`:

![index.html source code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vr66f3zt8s5s8p83s1sj.png)


Result: 

![index.html in browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9pwmjo4uerqg1jv90bn.png)


**Let's try to deploy it**

```sh
npm run workers:deploy
```
> Wrangler on the first run will ask you to authorize your requests using the OAuth screen. 


### How to enable maintenance mode on your website?
Simple as `console.log`!

1. Log in to your Cloudflare Console and Navigate to the Workers page
2. Click on your worker
![Worker card](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzdj1xk6td7yec9wahkn.png)
3. Go to Triggers -> Routes -> Add route
> You need to create 2 routes for common maintenance behavior. 

- domain.dev - your root page
- domain.dev/* - wildcard for all your URIs

4. Done! In a few seconds, all traffic will be forwarded to your Cloudflare Worker. 

#### Final
You can check full version of page with integrated TailwindCSS included on my github. (Feel free to check this in [template repository](https://github.com/oleghalin/cf-workers-maintenance))
> Thanks for reading, it's my first post. I will appreciate your feedback and advises!

#### Resources
[Example](https://maintenance-screen.khalin.workers.dev/)
[Github Template Repository](https://github.com/oleghalin/cf-workers-maintenance)

##### Screenshot
![Maintenance page example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fx9g1aj1znc4ap1qdc0g.png)