I haven't seen if Astro, tRPC and Cloudflare Workers go well together. I found myself asking: must tRPC be in a separate package/deployment (e.g. packages/
in a monorepo, or separate Cloudflare Worker). I'm pleased to say it's relatively straight forward - there are 2 options.
Background:
If you haven't heard of Cloudflare Workers, tRPC or Astro, read this. Otherwise, skip to 2 options.
- Cloudflare Workers: cheap but fast serverless platform that integrates Cloudflare's other services: SQLite on the edge (D1), S3-alternative with zero egress fees (R2) and WebSockets (durable objects, also used by DriftDB).
- tRPC lets you call your backend like you call functions. It's similar to gRPC, but without the code generation headache that comes with it. When using gRPC/protobufs, I've found the codegen to be a very complex area:
- There are multiple code generation tools for each language, with different features and bugs. They're not consistent. Not all features are implemented for each language.
- gRPC/protobuf code generation tools seem to be neglected.
- Code generation can be configured in different ways. This means the generated code is different and incompatible. Switching codegen tools to get their features (e.g.
google-protobuf/grpc-web
toprotobuf-ts
, or in Python: frombetterproto
togrpcio_tools
) are major breaking changes. - Warning: Should tRPC even be used in Cloudflare Workers? It might have cold-start issues. ![[workers tRPC.png]]
- I don't have these issues, and another discord user mentioned:
Cold starts definitely play a part but it just sounds like the codes own initialisation
- Astro is a framework for developing web applications with any Javascript/Typescript framework. You can make static or server-side rendered (SSG) websites. You can write code that executes in the browser and in your backend, in the same file (when using SSG). It's like NextJS but it supports React, Svelte, Vue, Solid and more, in the same app (and UI component).
---
import Layout from "../layouts/Layout.astro";
import { trpcClient } from "../ts/trpcClient";
// Server-side: Call rest of backend APIs, e.g. createUser
const userId = await trpcClient.createUser.mutate({
name: "benbutterworth",
bio: "gardener",
});
const user = await trpcClient.getUserById.query(userId);
// Logged server-side (in cloudflare)
console.log({ user });
---
<Layout title="Welcome to Astro.">
<main>
<p>Generated server-side</p>
<h1>Welcome, {user.name}</h1>
<h2>User ID: {userId}</h2>
<h2>User bio: {user.bio}</h2>
<p>Generated client-side</p>
<h1 class="user2"></h1>
<h2 class="user2-id"></h2>
<h2 class="user2-bio"></h2>
</main>
<script>
// Client-side: Call backend APIs, e.g. createUser
import { trpcClient } from "../ts/trpcClient";
const userId = await trpcClient.createUser.mutate({
name: "Anonymous",
bio: "Interwebs",
});
const user = await trpcClient.getUserById.query(userId)
// Logged client-side (browser console).
console.log({ user });
document.querySelector("h1.user2")!.textContent = `Hello, ${user.name}`;
document.querySelector("h2.user2-id")!.textContent = userId;
document.querySelector("h2.user2-bio")!.textContent = user.bio;
</script>
</Layout>
- Zapp.run: is a cool online Flutter IDE that makes use of Cloudflare Workers and Astro, and judging by Astro + tRPC v10, perhaps they use tRPC as well.
2 options:
- Simpler option: Cloudflare Pages
- Complex option: Cloudflare Pages + Cloudflare Worker
Simpler option: Cloudflare Pages
- Both the backend code (tRPC, database access, R2 access) lives in the same "package" as the frontend.
- Example
- Positives:
- Less configuration and code
- Single command to deploy
- Hot-restart works well. Saving a file leads to the backend rebuilding, and the frontend refreshing. In the example, run
pnpm dev
to start the dev server.
- Negatives:
- Old backends lingering: Because preview environments are not deleted, the backend code shipped in your client will still exist, and can be called at any time in the future by anyone* (restrict it). They run old code: which might have bugs or compatibility issues. For example, older preview environments might be:
- be incompatible with the current database schema
- corrupt the database
- deletes files unintentionally
- lack authorization checks
- Old backends lingering: Because preview environments are not deleted, the backend code shipped in your client will still exist, and can be called at any time in the future by anyone* (restrict it). They run old code: which might have bugs or compatibility issues. For example, older preview environments might be:
- Uses Cloudflare Pages Functions, which is relatively new compared to Cloudflare Workers, so it has some issues. Deployed using Cloudflare Pages:
wrangler pages publish
(internally uses Cloudflare Workers)
Complex option: Cloudflare Pages + Backend
- This is approach is probably good if you've already got a backend. This backend can be Cloudflare Workers running tRPC, Hono, Express, any combination of these, or anything else (e.g. NodeJS). Consider tRPC's Express.js adapter or Hono adapter (discussion). This allows you to avoid Cloudflare Worker platform for server-side rendering.
- Positives:
- Deploy frontend and backend separately. Choose different services for frontend and backend.
- Separation: the code will not get entangled, because they're in separate packages. Forces you to be more strict.
- Cloudflare Workers separate from frontend: Cloudflare Pages (which uses Workers internally)
- This separation means they are separate workers:
- more control over worker / features
- This separation means they are separate workers:
- Negatives:
- More complex maintenance. e.g. You need to setup monorepo tool to build backend code before frontend is built. When deploying a preview environment, you need to deploy the backend. The frontend needs to be pointed to the correct backend. Preview environments are not self-contained.
- Deployed as 1 Cloudflare Pages deployment, and 1 Cloudflare worker:
wrangler publish
Summary 📒
I'm currently going with option 1: on tRPC on Cloudflare Pages. Let me know if you have any questions. 🤓
Other resources
- tRPC Pages Plugin: for both options, you can use this plugin for some convenience (to get Cloudflare bindings to D1, R2, etc.).