Build apps
Deploy to Cloudflare
Deploy a Hane app to Cloudflare Workers as a BFF Worker with static assets, and publish it behind Cloudflare Access.
This page describes the general shape of deploying an app built on Hane to Cloudflare Workers. As in the reference app, one Worker serves both the BFF and the screen files, and Cloudflare Access in front of it handles login.
User's browser │ Cloudflare Access (login) ▼App Worker ├─ /bff/*, /auth/*, /healthz → BFF (exchanges the Access token with Hane) └─ everything else → static assets (screens), served with a CSP │ ▼Hane public API (gateway)- The Worker has no bindings to Hane internals and calls the gateway at its public URL. Customer apps always use only the public API.
- The Worker keeps no sessions. It exchanges the user token that Access adds to each request (
Cf-Access-Jwt-Assertion) as the id_token, and reuses the resulting access token in memory only while the same Access token is presented.
-
Build the screens
ターミナルウィンドウ vite build --config app/vite.config.ts # output goes to app/dist -
Write the Worker entry point
Pass your BFF routes (Hono, for example) a function that looks up the session from the Access token. Pass only paths that are not API routes to the static assets.
export default {fetch(req: Request, env: Env, ctx: ExecutionContext) {const app = createApp(env); // routes for /bff/* and /auth/*app.get("*", (c) => {const path = c.req.path;if (path.startsWith("/bff/") || path.startsWith("/auth/")) return c.notFound();return env.ASSETS.fetch(c.req.raw);});return app.fetch(req, env, ctx);},};Treat requests whose Access token has no email address (requests that passed with a service token) as not logged in, because there is no user.
-
Write the wrangler configuration
{"name": "my-hane-app","main": "bff/worker.ts","compatibility_date": "2026-10-01","assets": {"directory": "app/dist","binding": "ASSETS","not_found_handling": "single-page-application","run_worker_first": true},"vars": {"HANE_API": "https://<Hane gateway URL>","HANE_ORG": "acme","HANE_PROJECT": "fleet","HANE_ENV": "prod"}}With
run_worker_first, the Worker receives every request first, so it can add the CSP to screens too. -
Add secrets
Put your app’s service account API key in a Worker secret. Never write it in code or in the wrangler configuration.
ターミナルウィンドウ npx wrangler secret put HANE_API_KEYIn environments where Cloudflare Access also sits in front of the Hane gateway, also store an Access service token for the gateway as secrets, and add the
cf-access-client-idandcf-access-client-secretheaders to every request to the gateway. -
Deploy
ターミナルウィンドウ npx wrangler deploy -
Put Cloudflare Access in front
In the Zero Trust dashboard, create an Access application that covers the Worker’s hostname, and set the login methods and the policy for who may enter. Logout is at
/cdn-cgi/access/logout. -
Verify
If the BFF provides
/healthz?gateway=1, you can check that it reaches the Hane gateway by getting{"ok":true,"gateway":200}. Finally, log in as a user who is a member of the organization and confirm the screens open. Users who are not members getFORBIDDENfrom the token exchange.
Separate environments
Section titled “Separate environments”- Create a separate service account and API key for each environment. An API key is bound to one project and environment.
- Switch values such as
HANE_ENVwith wrangler’s per-environment configuration; don’t encode environment differences in code. - For definitions, promote a ReleaseBundle verified in dev to stage and prod with
hanectl release promote. Activation in prod is done by a person with their own login (hanectl).
Let Hane serve the app Preview
Section titled “Let Hane serve the app ”You can also have Hane serve your app instead of using your own Cloudflare account.
| Form | What it is |
|---|---|
| Static app | Create a scaffold with hanectl app create and upload files with hanectl app deploy |
| App container | Register a container image after it passes the release gate, the vulnerability check, and signing, and pin it in an Application definition. The app front performs token exchange and passes a short-lived access token with each request, so the container holds no API key |
The scope and steps of both forms may change. Check with your provider before using them.