Self-hosting on Cloudflare
A License lets you run your own copy of Supersaas Analytics on your own Cloudflare account. The copy has no Trial, no billing, no Quota and no limits on Sites or Members, because you pay for the infrastructure yourself.
The License key is checked offline when the Worker starts. Your copy never calls our servers.
What you need
- Your License key, from the License email or from My licenses on the account you bought it with.
- Access to the private repository. Accept the GitHub invitation that Polar sends after the purchase.
- A Cloudflare account with Workers. The free plan is enough to start, but D1 and Workers have daily limits on it.
- Node 22 or later, and pnpm.
- A Resend account and a verified sending domain, so people can verify their email and sign in.
1. Get the code
git clone git@github.com:<owner>/supersaas-data.git
cd supersaas-data
pnpm install
npx wrangler login
2. Create the resources
npx wrangler d1 create supersaas-data-db
npx wrangler kv namespace create supersaas-data-kv
npx wrangler kv namespace create supersaas-data-cache
npx wrangler r2 bucket create supersaas-data
Put the ids these commands print into wrangler.jsonc:
d1_databases[0].database_id. Leavemigrations_tableandmigrations_diras they are.kv_namespaces: the ids forKVandCACHE.r2_buckets[0].bucket_name, if you named the bucket differently.
The KV namespaces and the R2 bucket are not used by any feature yet, but the Worker expects their bindings.
3. The cron
triggers.crons in wrangler.jsonc wakes the Worker every day at 00:10 UTC to roll up the previous day, at 00:40 UTC to
write and email the Digests, and hourly for the search sync. Leave it as it is: it must match nitro.scheduledTasks in
nuxt.config.ts, and without it, past days show no data.
4. Settings
Add these to vars in wrangler.jsonc:
"vars": {
"DEPLOYMENT_MODE": "selfhosted",
"NUXT_AUTH_URL": "https://analytics.example.com",
"NUXT_EMAIL_FROM": "Analytics <analytics@example.com>",
"NUXT_PUBLIC_TRACKER_ORIGIN": ""
}
Then add the secrets:
npx wrangler secret put LICENSE_KEY
npx wrangler secret put NUXT_AUTH_SECRET # openssl rand -base64 32
npx wrangler secret put RESEND_API_TOKEN
npx wrangler secret put NUXT_ANTHROPIC_API_KEY # optional, see below
DEPLOYMENT_MODEmust beselfhosted. Without it, the copy behaves as our Hosted service, with Trials and billing.LICENSE_KEYis your License key. Without a valid key, the copy shows only a setup page, and its API and/collectanswer503.NUXT_AUTH_SECRETsigns sessions. Changing it signs everyone out.NUXT_AUTH_URLis the copy's public origin. Email links (Digests included) and OAuth callbacks use it.RESEND_API_TOKENandNUXT_EMAIL_FROMsend email. Without them, emails are only printed to the Worker logs, so nobody can verify their email.NUXT_ANTHROPIC_API_KEYis your own Anthropic API key. The AI features use it (Ask AI, and the summary on top of each Digest), and they are hidden when it is not set. Its usage is billed to your Anthropic account.- Google and GitHub sign-in are optional: see
NUXT_AUTH_GOOGLE_*andNUXT_AUTH_GITHUB_*in.env.examplein the repository.
5. Deploy
pnpm run deploy
Use pnpm run deploy, not pnpm deploy, which is a pnpm built-in. The script builds the Worker, applies new
migrations to D1, then deploys. The first deploy creates every table.
The Worker has no workers.dev URL ("workers_dev": false), so before the first deploy uncomment routes in
wrangler.jsonc and set the pattern to your domain, whose zone must be on the same Cloudflare account.
Open your domain and sign up. The first account is yours; the admin area of our Hosted service does not exist on a Self-hosted copy.
To check the copy:
curl -i https://analytics.example.com/api/deploymentshows"license":"valid".curl -i https://analytics.example.com/sd.jsanswers200.
Search Console (optional)
Search Console data is read with OAuth on behalf of the person who connects.
Your copy needs a Google OAuth client
with the read-only https://www.googleapis.com/auth/webmasters.readonly scope.
- In the Google Cloud console, choose or create the project. The one that holds the "Sign in with Google" client works.
- Enable the Google Search Console API under APIs & Services → Library.
- Under Google Auth Platform → Data access, add the scope
.../auth/webmasters.readonly. - Under Clients, create a Web application client, or open the sign-in one, and add the redirect URI
<origin>/api/search-connections/google/callback. - Set
NUXT_SEARCH_CONSOLE_GOOGLE_CLIENT_IDandNUXT_SEARCH_CONSOLE_GOOGLE_CLIENT_SECRET. Left empty, the sign-in client (NUXT_AUTH_GOOGLE_CLIENT_IDandNUXT_AUTH_GOOGLE_CLIENT_SECRET) is used.
webmasters.readonly is a sensitive scope, so submit the app for verification early: it takes days to weeks.
Until Google verifies it, only the test users listed under Audience can connect, and they see an "unverified app"
warning.
Refresh tokens are stored encrypted with a key derived from NUXT_AUTH_SECRET. Rotating that secret means every Site
has to connect Search Console again.
Updating
Updates arrive in the private repository. Keep your wrangler.jsonc changes in a commit of their own, so they merge
cleanly:
git pull
pnpm install
pnpm run deploy
Migrations are applied before the new Worker goes live. Read the release notes before a large update.
Troubleshooting
- Only the setup page shows:
LICENSE_KEYis missing or was not copied whole. The setup page says which. Set the secret again;wrangler secret putredeploys the Worker. - Trials or billing show:
DEPLOYMENT_MODEis notselfhosted. Checkvarsand deploy again. - Past days are empty but today has data: the cron has not run. Check Workers → supersaas-data → Settings → Triggers.
- No verification email arrives:
RESEND_API_TOKENis not set, orNUXT_EMAIL_FROMis not on a domain verified in Resend.