Cloudflare Pages

Cloudflare Pages is where Emet is designed to be deployed. The repository’s functions/ directory is a set of Pages Functions, so the OAuth token exchange that sign-in depends on gets deployed alongside the static files without any extra setup. Hosting is free at the scale a CMS runs at, and you get HTTPS and a *.pages.dev hostname out of the box.

There are two ways to get the build onto Pages: push it yourself with wrangler, or connect the repository and let Cloudflare build it on every push. The first is quicker to try; the second is nicer to live with.

What you’ll need#

  • Node.js 22.12 or later.
  • A Cloudflare account.
  • A GitHub App with its client ID and client secret to hand. You can create it now with a placeholder callback URL and fix the URL once you know your Pages hostname.
  • For Google Search Console, a Google OAuth client ID and secret. This is optional; skip it and the GSC functions just answer “not configured”.

Deploying with wrangler#

From your clone of Emet, with the client ID in the environment:

VITE_GITHUB_CLIENT_ID=Iv23.your_client_id npm run deploy

npm run deploy is an alias for deploy:cloudflare, which sets DEPLOY_TARGET=cloudflare-pages, runs the production build and then calls wrangler pages deploy dist. The project name (emetcms) and output directory come from wrangler.toml. Wrangler will ask you to log in the first time, and if a project with that name doesn’t exist yet it will offer to create one.

Setting DEPLOY_TARGET makes the build write a _routes.json into dist/ that tells Pages to run Functions only for /api/* and serve everything else straight from the CDN. It’s not essential, but it keeps static requests off your Functions quota.

When wrangler finishes it prints the deployment URL. Open it and you should see Emet’s sign-in screen, though you can’t sign in yet.

Adding the secrets#

The functions read their secrets from the Pages project’s environment at request time. In the Cloudflare dashboard, open the project, go to Settings > Variables and Secrets and add:

Variable Type What it’s for
GITHUB_CLIENT_SECRET Secret Your GitHub App’s client secret. Required for sign-in.
GOOGLE_CLIENT_ID Text Google OAuth client ID. Only for Search Console.
GOOGLE_CLIENT_SECRET Secret Google OAuth client secret. Only for Search Console.

Note that GOOGLE_CLIENT_ID is needed twice: here at runtime, for the token exchange, and as VITE_GOOGLE_CLIENT_ID at build time, for the browser to start the OAuth flow. Without the runtime copy the /api/gsc/* functions return 501.

Then set the callback URL on your GitHub App to your Pages URL, with a trailing slash, i.e. https://emetcms.pages.dev/. Redeploy (secrets added after a deployment don’t apply to it) and sign-in should now work end to end.

Trying it locally first#

npm run preview:cloudflare does the same build and then runs wrangler pages dev dist, which serves the static files and the functions together on http://localhost:8788. Put GITHUB_CLIENT_SECRET=... in a .dev.vars file in the project root for wrangler to pick up; it’s gitignored. This is the closest you’ll get to the real thing without deploying, and it’s also what the development auth mode proxies to.

Building on push instead#

If you’d rather not run wrangler by hand, connect the repository to Pages and it will build on every push.

  1. In the Cloudflare dashboard, go to Workers & Pages > Create > Pages > Connect to Git and pick your fork of Emet.

  2. Set the build command to npm run build and the build output directory to dist. Leave the root directory as / unless you’ve put Emet in a subdirectory of a larger repository.

  3. Add the environment variables. Anything prefixed VITE_ is read at build time and ends up in the JavaScript bundle; the rest are read by the functions when they run.

    Variable Value
    NODE_VERSION 22.12.0 or later. Pages’ default image is older than Emet needs.
    DEPLOY_TARGET cloudflare-pages, to get _routes.json.
    VITE_GITHUB_CLIENT_ID Your GitHub App’s client ID.
    VITE_GITHUB_APP_SLUG The App’s slug (optional). Adds an “Install GitHub App” link to the repository picker.
    VITE_GOOGLE_CLIENT_ID Google OAuth client ID (optional).
    GITHUB_CLIENT_SECRET Your GitHub App’s client secret, as a secret.
    GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET Google OAuth credentials (optional).
  4. Click Save and Deploy.

Don’t set VITE_API_BASE_URL. It exists for deployments where the proxy lives on a different origin from the static files, and on Pages that isn’t the case. Leave it unset and Emet calls /api/* on its own origin.

The functions/ directory is detected automatically from the repository root, so there’s nothing to configure for the proxy. Once the first build is green, set the GitHub App callback URL to the Pages URL as above.

Custom domains#

Add the domain under the project’s Custom domains tab. If your DNS is on Cloudflare the record is created for you; otherwise point a CNAME at your-project.pages.dev. Remember to add the new URL as a callback URL on the GitHub App, since the one you sign in from has to match exactly.

Where next?#