Deploy to CloudFront
This page serves a site from an Amazon S3
bucket behind a CloudFront
distribution. cloudfront() from @pagedeck/adapter-cloudfront compiles the
site's routing into two CloudFront Functions and the configuration fragments
they need. Nothing here creates AWS resources for you: the build writes files,
and you install them. Deploy a site covers what every
host shares: what to upload, the plan pagedeck diff writes, and rolling
back.
No deploy has run this setup end to end yet. Try it on a staging distribution before you rely on it, and see the last section for what to check.
Name the adapter
npm install @pagedeck/adapter-cloudfrontimport { defineConfig } from "@pagedeck/core";
import { cloudfront } from "@pagedeck/adapter-cloudfront";
export default defineConfig({
collections: [],
build: {
pages: [],
components: {},
adapter: cloudfront(),
},
});pagedeck build writes these into edge/, beside site/. None of them is
part of the upload:
| File | What it is |
|---|---|
routing.request.js |
the viewer-request function: the redirects, and the refusal of the reserved deploy keys |
routing.response.js |
the viewer-response function: the header rules |
error-responses.json |
the custom error response that serves the 404 page, when the site declares one |
upstreams.json |
an origin and cache behaviours per path prefix, when the site sends one to an app |
routing.kvs.json, routing.request.config.json |
the redirect table and its store association, when the redirects outgrow the function |
Every site gets the two functions, even one that declares no routing, because
the request function is what keeps /manifest.json and /.pagedeck/ private.
Upload the site to S3
Turn on static website hosting
for the bucket, with index.html as its index document. The functions do not
map a directory to its index.html, and the website endpoint does: it serves
about/index.html for /about/. AWS documents that a website endpoint is
public and answers over HTTP only, so the distribution reaches it as a custom
origin over HTTP.
Let the bucket policy allow s3:ListBucket as well as s3:GetObject. S3
answers a request for a missing key with 403 when the reader may not list the
bucket, and 404 when it may
(GetObject permissions).
The build's custom error response, and the refusal of the reserved deploy
keys, rely on the 404.
Upload site/, with manifest.json last:
npx pagedeck sync
npx pagedeck build
aws s3 sync site/ s3://my-bucket/ --exclude manifest.json
aws s3 cp site/manifest.json s3://my-bucket/manifest.jsonaws s3 sync sets each file's Content-Type from its extension. It deletes
nothing: delete the files a new build no longer writes with the prune rows
of the plan pagedeck diff writes, after the time the plan gives, as
Deploy a site describes.
Publish the functions
Create and publish each function. A CloudFront Function has to be published before a distribution can run it:
aws cloudfront create-function --name my-site-request \
--function-config '{"Comment":"Pagedeck redirects","Runtime":"cloudfront-js-1.0"}' \
--function-code fileb://edge/routing.request.js
aws cloudfront publish-function --name my-site-request --if-match "$ETAG"$ETAG is the ETag that create-function printed. Do the same for
routing.response.js under another name. On a later deploy, replace each
function's code with aws cloudfront update-function and publish it again,
whenever the build changed the file.
Then associate the two with the distribution's default cache behaviour: the request function on the viewer-request event, the response function on the viewer-response event (associating functions). A cache behaviour runs one function per event. If your distribution already runs a viewer-request function of its own, combine the two into one function: Pagedeck's stages first, then yours.
If the site declares a 404 page, merge the entries of error-responses.json
into the distribution's custom error responses. A request for a reserved
deploy key, or for a path with no file, then gets the site's 404 page.
Many redirects. A CloudFront Function's code is limited to 10 KB. Past
that, the build moves the redirect table into routing.kvs.json, a
KeyValueStore
dataset, and the request function reads it on the cloudfront-js-2.0
runtime. Create a KeyValueStore, import routing.kvs.json into it, put its
ARN in place of ${PAGEDECK_ROUTING_KVS_ARN} in
routing.request.config.json, and create the request function with that file
as its function config, in place of the JSON above.
Send a path prefix to an app on Lambda
A site that declares build.routing.upstreams sends each prefix to an app you
deploy yourself, as Run an app beside the site
describes. Behind CloudFront, the app can run on
AWS Lambda with a
function URL.
A Hono app runs there through the handle function from hono/aws-lambda
(Hono on AWS Lambda), and
answers the same paths it answers on any other host.
Deploy the function first, then give its URL's origin as the upstream's
url, which cloudfront() requires:
import { defineConfig } from "@pagedeck/core";
import { cloudfront } from "@pagedeck/adapter-cloudfront";
export default defineConfig({
collections: [],
build: {
pages: [],
components: {},
adapter: cloudfront(),
routing: {
upstreams: [{ prefix: "/api/", url: "https://abc123.lambda-url.eu-west-1.on.aws" }],
},
},
});The build writes edge/upstreams.json, a fragment of a distribution config.
For each prefix it holds one custom origin, HTTPS only, and two cache
behaviours, /api and /api/*. Each behaviour allows all seven methods,
uses the managed CachingDisabled cache policy, so no answer of the app is
cached, and the managed AllViewerExceptHostHeader origin request policy, so
the function URL gets its own host name. It runs no function, so the site's
header rules never reach the app's answers, and the app sets its own.
Merge it into the distribution:
aws cloudfront get-distribution-config --id "$DISTRIBUTION_ID" > distribution.jsonAdd the fragment's Origins.Items to the config's origins and its
CacheBehaviors.Items to its cache behaviours, ahead of any behaviour of
your own whose path pattern could also match under the prefix, since the
first match wins. Set both Quantity fields to the new counts, because an
update replaces the whole list. Then send the config back with aws cloudfront update-distribution, with
the ETag that get-distribution-config printed as its if-match value
(updating a distribution).
Each prefix takes two of the distribution's cache behaviours, 75 by default,
so the build refuses more than 37 prefixes. An account whose quota AWS has
raised sets it with cloudfront({ limits: { cacheBehaviors } }).
The function URL still answers on its own address. CloudFront does not hide it, so an app that must only be reached through the site checks for that itself.
Check it on a staging distribution
These rest on CloudFront and S3 behaviour that no deploy of this setup has checked. Request each path on the staging distribution and read the status and headers:
- A page and its other trailing-slash spelling, such as
/about/and/about. /manifest.jsonand/.pagedeck/x, which should answer the site's 404 page with a 404, not a 403.- A path with no file, whose 404 page should carry the header rules: that rests on CloudFront running the viewer-response function over a custom error response.
/apiand aPOSTunder/api/, which should reach the app with its method, body and query.
The "The edge artifacts" and "An app beside the site" sections of the deploy recipe list these facts beside every other host's.