The "Tab-Switching" Hell

If you run a SaaS, your morning routine probably looks like this:

  1. Open Google Analytics (Traffic). Wait for it to load.
  2. Open Stripe Dashboard (Revenue).
  3. Open Cloudflare Dashboard (Server load).
  4. Open CRM (New leads).

You have 4 tabs open, trying to mentally correlate "Did that traffic spike cause the server load?" or "Did the new blog post bring any leads?"

We wanted a single "God View". One screen. Real-time.

We looked at Tableau and Looker. They are expensive, slow, and overkill. We looked at Geckoboard. It costs $100+/mo for decent features.

So we decided to build our own. In one day.

 

The Stack

We are all-in on the Edge, so the stack was obvious:

  • Backend: Cloudflare Workers (Hono).
  • Database: Cloudflare D1 (for our internal stats).
  • Frontend: SvelteKit + ApexCharts.

The Problem: Google Analytics 4 on the Edge

Fetching data from D1 is easy. Fetching data from Stripe is easy (REST API). Fetching data from GA4 on Cloudflare Workers? Nightmare.

The official googleapis Node.js library relies on fs, net, and other Node-specific modules that simply do not exist in the Workers runtime.

You can't just npm install googleapis.

We had two choices:

  1. Spin up a slow, cold-starting Node.js container just for this. (No.)
  2. Implement the OAuth2 JWT signing flow manually using Web Crypto API. (Yes.)

 

The Solution: Manual JWT Signing

To authenticate with Google APIs as a Service Account without a library, you need to:

  1. Create a JWT.
  2. Sign it with your Private Key (RSA-SHA256).
  3. Exchange it for an Access Token.

Here is the code that actually works on Cloudflare Workers (copy-paste this, it took us 4 hours to debug):

// src/lib/google-auth.ts

function pemToBinary(pem: string) {
  const base64 = pem
    .replace(/-----BEGIN PRIVATE KEY-----/g, "")
    .replace(/-----END PRIVATE KEY-----/g, "")
    .replace(/\s/g, "");
  return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
}

async function importPrivateKey(pem: string) {
  const binaryDer = pemToBinary(pem);
  return await crypto.subtle.importKey(
    "pkcs8",
    binaryDer,
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    false,
    ["sign"],
  );
}

export async function getAccessToken(clientEmail: string, privateKey: string) {
  const header = { alg: "RS256", typ: "JWT" };
  const now = Math.floor(Date.now() / 1000);
  const claim = {
    iss: clientEmail,
    scope: "https://www.googleapis.com/auth/analytics.readonly",
    aud: "https://oauth2.googleapis.com/token",
    exp: now + 3600,
    iat: now,
  };

  const encodedHeader = btoa(JSON.stringify(header));
  const encodedClaim = btoa(JSON.stringify(claim));
  const key = await importPrivateKey(privateKey);

  const signature = await crypto.subtle.sign(
    "RSASSA-PKCS1-v1_5",
    key,
    new TextEncoder().encode(`${encodedHeader}.${encodedClaim}`),
  );

  const signedJwt = `${encodedHeader}.${encodedClaim}.${btoa(
    String.fromCharCode(...new Uint8Array(signature)),
  )
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "")}`;

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${signedJwt}`,
  });

  const data = await response.json();
  return data.access_token;
}

Why this is cool

This code has zero dependencies. It uses the native crypto API built into the browser and Cloudflare Workers. It's blazing fast (sub-10ms to sign).

 

The Result: The "God View"

We built a simple Svelte page that calls our Worker. The Worker:

  1. Fetches active users from GA4 (using the token above).
  2. Fetches "Workflows Run Today" from D1.
  3. Fetches "New Leads" from our CRM API.
  4. Returns a single JSON object.

Total Latency: ~200ms. Cost: $0/mo (Cloudflare Free Tier allows 100k requests/day).

The Dashboard

(Imagine a sleek dark-mode dashboard here with a sparkline chart showing traffic up 37% and a big number "12" for new leads)

We used ApexCharts for the visuals. It's lightweight and looks premium out of the box.

<script>
  import Chart from "./Chart.svelte";
  export let data; // Loaded from our Worker
</script>

<div class="grid grid-cols-3 gap-4">
  <Card title="Real-time Users" value={data.ga4.activeUsers} trend="+12%" />
  <Card title="Workflows Run" value={data.d1.workflowsCount} trend="+5%" />
  <Card title="Revenue (MRR)" value={data.stripe.mrr} trend="+2%" />
</div>

 

Brutal Honesty: The Limitations

Is this a replacement for Tableau? No.

  1. It's Read-Only: You can't "drill down" unless you code it.
  2. Maintenance: If Google changes their API, we have to fix it.
  3. No Historical Data: We only fetch "Today" and "Last 30 Days". For deep analysis, we still use the native tools.

But for that "first coffee of the morning" check? It's unbeatable.

Conclusion

You don't need to pay $500/mo for a BI tool just to see 3 numbers. You don't need a heavy Node.js server to talk to Google.

With a little understanding of cryptography and the Edge, you can build exactly what you need, for free.

Next Step: We are packaging this GoogleAuth helper into an open-source npm package for Cloudflare Workers. Stay tuned.


Show, Don't Tell (I Owe You a Screenshot)

I know what you're thinking: "Where's the dashboard screenshot?"

You caught me. I wrote "Imagine a sleek dark-mode dashboard" because I was too lazy to take a screenshot when I published this.

But here's the deal: The code above works. I use it every morning. If you implement it, you'll have a dashboard in 20 minutes.


You're juggling 5 analytics dashboards. Your competitors see everything in one place.

We built a unified dashboard that combines GA4, Semrush, Ahrefs, and Cloudflare in real-time.

Start with WRIO Analytics (Free Tier) →

What you get:

  • ✅ Unified dashboard (GA4 + Semrush + Ahrefs + Cloudflare)
  • ✅ Real-time sync (no manual exports)
  • ✅ Custom KPIs and alerts
  • ✅ Self-hosted on Cloudflare Workers

Prefer DIY? Use the code examples in this post. We respect that.

WRIO is Edge-first infrastructure for developers who hate maintenance.

What you get:

  • Pre-built connectors for GA4, Stripe, Cloudflare, and 50+ APIs
  • Deploy on your Workers (you own the code, we just provide the SDK)
  • JWT/OAuth handled (we update when Google changes APIs, you don't)
  • Unified dashboard out of the box (yes, with actual UI this time)

The economics:

  • DIY: 6 hours to build + 2 hours/month maintenance
  • WRIO: 0 hours to build, $49/month (or self-host for free)

Want the actual dashboard?
👉 Sign Up as Developer — Early access to Edge SDK

Prefer to build it yourself?
Cool. Fork the code above. When you get tired of debugging OAuth flows, we'll be here.

 

Frequently Asked Questions

How much does this cost to run?

The entire stack runs on Cloudflare's free tier (100,000 requests/day) and Google Analytics 4 (free). The only cost is your time to set it up (4-6 hours). There are no monthly fees unless you exceed Cloudflare's free limits.

Can I use this without Cloudflare Workers?

No, this specific implementation relies on Cloudflare Workers' Web Crypto API for JWT signing. However, you could adapt the concept to other serverless platforms (Vercel Edge Functions, AWS Lambda@Edge) with similar crypto capabilities.

Is this approach secure?

Yes. The JWT signing uses RSA-SHA256 with your Google Service Account's private key, which is the same method used by Google's official libraries. As long as you keep your private key secret (store it as a Cloudflare secret, never commit to git), the authentication is secure.

How often does the data refresh?

The dashboard fetches data in real-time on every page load. GA4 data typically has a 24-48 hour delay for historical metrics, but real-time active users are updated within seconds. D1 and Stripe data are always current.

What are the limitations compared to Tableau or Looker?

This is a read-only dashboard with fixed queries. You can't drill down into data or create custom reports without writing code. For deep analysis, you'll still need the native tools (GA4, Stripe Dashboard). This dashboard is best for daily "health check" monitoring, not exploratory analytics.