UNPKG

8.88 kBMarkdownView Raw
1# @cloudflare/kv-asset-handler
2
3 * [Installation](#installation)
4 * [Usage](#usage)
5 * [`getAssetFromKV`](#-getassetfromkv-)
6 - [Example](#example)
7 + [Return](#return)
8 + [Optional Arguments](#optional-arguments)
9 - [`mapRequestToAsset`](#-maprequesttoasset-)
10 - [Example](#example-1)
11 - [`cacheControl`](#-cachecontrol-)
12 * [`browserTTL`](#-browserttl-)
13 * [`edgeTTL`](#-edgettl-)
14 * [`bypassCache`](#-bypasscache-)
15 - [`ASSET_NAMESPACE`](#-asset-namespace-)
16 - [`ASSET_MANIFEST` (optional)](#-asset-manifest---optional-)
17- [Helper functions](#helper-functions)
18 * [`mapRequestToAsset`](#-maprequesttoasset--1)
19 * [`serveSinglePageApp`](#-servesinglepageapp-)
20- [Cache revalidation and etags](#cache-revalidation-and-etags)
21
22## Installation
23
24Add this package to your `package.json` by running this in the root of your
25project's directory:
26
27```
28npm i @cloudflare/kv-asset-handler
29```
30
31## Usage
32
33This package was designed to work with [Worker Sites](https://workers.cloudflare.com/sites).
34
35## `getAssetFromKV`
36
37getAssetFromKV(FetchEvent) => Promise<Response>
38
39`getAssetFromKV` is an async function that takes a `FetchEvent` object and returns a `Response` object if the request matches an asset in KV, otherwise it will throw a `KVError`.
40
41#### Example
42
43This example checks for the existence of a value in KV, and returns it if it's there, and returns a 404 if it is not. It also serves index.html from `/`.
44
45### Return
46
47`getAssetFromKV` returns a `Promise<Response>` with `Response` being the body of the asset requested.
48
49Known errors to be thrown are:
50
51- MethodNotAllowedError
52- NotFoundError
53- InternalError
54
55```js
56import { getAssetFromKV, NotFoundError, MethodNotAllowedError } from '@cloudflare/kv-asset-handler'
57
58addEventListener('fetch', event => {
59 event.respondWith(handleEvent(event))
60})
61
62async function handleEvent(event) {
63 if (event.request.url.includes('/docs')) {
64 try {
65 return await getAssetFromKV(event)
66 } catch (e) {
67 if (e instanceof NotFoundError) {
68 // ...
69 } else if (e instanceof MethodNotAllowedError) {
70 // ...
71 } else {
72 return new Response("An unexpected error occurred", { status: 500 })
73 }
74 }
75 } else return fetch(event.request)
76}
77```
78
79### Optional Arguments
80
81You can customize the behavior of `getAssetFromKV` by passing the following properties as an object into the second argument.
82
83```
84getAssetFromKV(event, { mapRequestToAsset: ... })
85```
86
87#### `mapRequestToAsset`
88
89mapRequestToAsset(Request) => Request
90
91Maps the incoming request to the value that will be looked up in Cloudflare's KV
92
93By default, mapRequestToAsset is set to the exported function [`mapRequestToAsset`](#maprequesttoasset-1). This works for most static site generators, but you can customize this behavior by passing your own function as `mapRequestToAsset`. The function should take a `Request` object as its only argument, and return a new `Request` object with an updated path to be looked up in the asset manifest/KV.
94
95For SPA mapping pass in the [`serveSinglePageApp`](#servesinglepageapp) function
96
97#### Example
98
99Strip `/docs` from any incoming request before looking up an asset in Cloudflare's KV.
100
101```js
102import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'
103...
104const customKeyModifier = request => {
105 let url = request.url
106 //custom key mapping optional
107 url = url.replace('/docs', '').replace(/^\/+/, '')
108 return mapRequestToAsset(new Request(url, request))
109}
110let asset = await getAssetFromKV(event, { mapRequestToAsset: customKeyModifier })
111```
112
113#### `cacheControl`
114
115type: object
116
117`cacheControl` allows you to configure options for both the Cloudflare Cache accessed by your Worker, and the browser cache headers sent along with your Workers' responses. The default values are as follows:
118
119```javascript
120let cacheControl = {
121 browserTTL: null, // do not set cache control ttl on responses
122 edgeTTL: 2 * 60 * 60 * 24, // 2 days
123 bypassCache: false, // do not bypass Cloudflare's cache
124}
125```
126
127##### `browserTTL`
128
129type: number | null
130nullable: true
131
132Sets the `Cache-Control: max-age` header on the response returned from the Worker. By default set to `null` which will not add the header at all.
133
134##### `edgeTTL`
135
136type: number | null
137nullable: true
138
139Sets the `Cache-Control: max-age` header on the response used as the edge cache key. By default set to 2 days (`2 * 60 * 60 * 24`). When null will not cache on the edge at all.
140
141##### `bypassCache`
142
143type: boolean
144
145Determines whether to cache requests on Cloudflare's edge cache. By default set to `false` (recommended for production builds). Useful for development when you need to eliminate the cache's effect on testing.
146
147#### `ASSET_NAMESPACE`
148
149type: KV Namespace Binding
150
151The binding name to the KV Namespace populated with key/value entries of files for the Worker to serve. By default, Workers Sites uses a [binding to a Workers KV Namespace](https://developers.cloudflare.com/workers/reference/storage/api/#namespaces) named `__STATIC_CONTENT`.
152
153It is further assumed that this namespace consists of static assets such as HTML, CSS, JavaScript, or image files, keyed off of a relative path that roughly matches the assumed URL pathname of the incoming request.
154
155```
156return getAssetFromKV(event, { ASSET_NAMESPACE: MY_NAMESPACE })
157```
158
159#### `ASSET_MANIFEST` (optional)
160
161type: text blob (JSON formatted)
162
163The mapping of requested file path to the key stored on Cloudflare.
164
165Workers Sites with Wrangler bundles up a text blob that maps request paths to content-hashed keys that are generated by Wrangler as a cache-busting measure. If this option/binding is not present, the function will fallback to using the raw pathname to look up your asset in KV. If, for whatever reason, you have rolled your own implementation of this, you can include your own by passing a stringified JSON object where the keys are expected paths, and the values are the expected keys in your KV namespace.
166
167```
168let assetManifest = { "index.html": "index.special.html" }
169return getAssetFromKV(event, { ASSET_MANIFEST: JSON.stringify(assetManifest) })
170```
171
172#### `defaultMimeType` (optional)
173
174type: string
175
176This is the mime type that will be used for files with unrecognized or missing extensions. The default value is `'text/plain'`.
177
178If you are serving a static site and would like to use extensionless HTML files instead of index.html files, set this to `'text/html'`.
179
180# Helper functions
181
182## `mapRequestToAsset`
183
184mapRequestToAsset(Request) => Request
185
186The default function for mapping incoming requests to keys in Cloudflare's KV.
187
188Takes any path that ends in `/` or evaluates to an HTML file and appends `index.html` or `/index.html` for lookup in your Workers KV namespace.
189
190## `serveSinglePageApp`
191
192serveSinglePageApp(Request) => Request
193
194A custom handler for mapping requests to a single root: `index.html`. The most common use case is single-page applications - frameworks with in-app routing - such as React Router, VueJS, etc. It takes zero arguments.
195
196```js
197import { getAssetFromKV, serveSinglePageApp } from '@cloudflare/kv-asset-handler'
198...
199let asset = await getAssetFromKV(event, { mapRequestToAsset: serveSinglePageApp })
200```
201
202# Cache revalidation and etags
203
204All responses served from cache (including those with `cf-cache-status: MISS`) include an `etag` response header that identifies the version of the resource. The `etag` value is identical to the path key used in the `ASSET_MANIFEST`. It is updated each time an asset changes and looks like this: `etag: <filename>.<hash of file contents>.<extension>` (ex. `etag: index.54321.html`).
205
206Resources served with an `etag` allow browsers to use the `if-none-match` request header to make conditional requests for that resource in the future. This has two major benefits:
207
208* When a request's `if-none-match` value matches the `etag` of the resource in Cloudflare cache, Cloudflare will send a `304 Not Modified` response without a body, saving bandwidth.
209* Changes to a file on the server are immediately reflected in the browser - even when the cache control directive `max-age` is unexpired.
210
211#### Disable the `etag`
212
213To turn `etags` **off**, you must bypass cache:
214
215```js
216/* Turn etags off */
217let cacheControl = {
218 bypassCache: true
219}
220```
221
222#### Syntax and comparison context
223
224`kv-asset-handler` sets and evaluates etags as [strong validators](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests#Strong_validation). To preserve `etag` integrity, the format of the value deviates from the [RFC2616 recommendation to enclose the `etag` with quotation marks](https://tools.ietf.org/html/rfc2616#section-3.11). This is intentional. Cloudflare cache applies the `W/` prefix to all `etags` that use quoted-strings -- a process that converts the `etag` to a "weak validator" when served to a client.