Static sites
How to use the Brandfine SDK from a site with no server runtime (Astro static export, Hugo, plain HTML, WordPress).
The Brandfine SDK is server-side only in v1 — your workspace API key is broad-scope and should never reach a browser bundle. For sites with a server runtime (Next.js, Remix, Nuxt, Astro SSR, SvelteKit, Express), that's straightforward: every recipe in the SDK docs does it.
If your site has no server runtime, you have three options.
Option 1 — Add a server runtime to your existing site (recommended)
Every modern static-site framework supports a "hybrid" mode where most pages stay static but a few selected routes render at request time. This is the cleanest path and what we recommend.
Astro
// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
export default defineConfig({
output: 'static',
adapter: node({ mode: 'standalone' }),
})Then mark specific pages/routes as on-demand:
---
// src/pages/api/contact.ts
export const prerender = false // ← this route renders on every request
import { createBrandfineClient } from '@brandfine/client'
const bf = createBrandfineClient({
baseUrl: 'https://api.brandfine.co',
apiKey: import.meta.env.BRANDFINE_API_KEY,
})
export const POST: APIRoute = async ({ request }) => {
await bf.submissions.create(/* ... */)
// ...
}
---Static pages stay static. The /api/contact route runs on a tiny
Node server. Your hosting changes from "static file CDN" to "static
file CDN + Node runtime" — most platforms (Netlify, Vercel,
Cloudflare Pages, Railway, Fly.io) make this a one-click switch.
Next.js
Already SSR by default. If you're on output: 'export' for a
fully-static deploy, drop that line and most platforms will deploy
your route handlers as serverless functions automatically.
Other frameworks
- Eleventy / Hugo / Jekyll: pair with a small Node/Bun/Deno
proxy on a separate domain (e.g.
api.yourdomain.com) — see Option 2 below. - WordPress: install the Brandfine WordPress plugin. It ships the WP REST proxy + booking widget + analytics auto-inject, no custom PHP needed.
- Webflow / Squarespace / Wix: built-in form handling, then webhook the data into Brandfine via a serverless function.
Option 2 — Tiny Node proxy on its own domain
If switching your main site to hybrid isn't appealing, run a small Node server (~30 lines) on a sibling domain. Your static site posts to it; it proxies to Brandfine.
// server.ts (Hono example — works on Node, Bun, Deno, Workers)
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { createBrandfineClient } from '@brandfine/client'
const bf = createBrandfineClient({
baseUrl: 'https://api.brandfine.co',
apiKey: process.env.BRANDFINE_API_KEY!,
})
const app = new Hono()
app.use('/*', cors({ origin: ['https://yourdomain.com'] }))
app.post('/contact', async (c) => {
const body = await c.req.json()
const created = await bf.submissions.create(body)
return c.json(created)
})
app.post('/appointments', async (c) => {
const body = await c.req.json()
const created = await bf.appointments.createRequest(body)
return c.json(created)
})
export default appDeploy this to Railway, Fly.io, a small VPS, or Cloudflare Workers. Cost: a few dollars a month for low-traffic sites. Trade-off: one more service to maintain.
Option 3 — Wait for publishable keys
We're working on scoped publishable keys (bfpk_*) — the
two-key model Stripe, GitHub, and Mapbox all use. A publishable key
would be:
- Safe to embed directly in your static HTML / JS bundle
- Capability-gated to a specific allowlist of endpoints (e.g.
appointments:writeonly) - Independent revocation from your server-side key
- Per-key rate limiting for spam resistance
Until they ship, Options 1 and 2 are the supported paths. We'll update this page with a "It's live!" notice when they're ready.
If you're blocked specifically by this and would prefer to wait rather than refactor your site, email hello@brandfine.co so we can prioritize.
Why we don't just let you put the broad-scope key in HTML
The workspace API key (bfwk_*) you get from the CMS today reads
posts, navigations, contact-form submissions, analytics, AND can
write submissions + appointments. It's the equivalent of a Stripe
secret key — meant for server-side use only.
Putting it in HTML wouldn't expose anything currently sensitive
(everything /external/* exposes is either already public content
or a write-only ingest endpoint), but:
- It blanket-allows any read/write capability we add in the future — including ones that might be sensitive
- Per-IP rate limiting becomes essential against spam, which the server doesn't apply to broad-scope keys today
- Revoking the key takes down all your server-side integrations along with the embed
- Secret scanners can't tell the difference between "this is safe in browser" vs "this should never be public" without the prefix distinction
We'd rather take the short-term inconvenience of Option 1/2 than ship a permanent footgun. Publishable keys will resolve this properly.