React Postgres Components
An experiment on deploying remote functions that run inside Postgres using v8, run React SSR, and are easily defined in a rpc/
directory:
rpc/hello-world.tsx
export default function helloWorld () => { const [{version}] = sql`SELECT version()`; // no `await` needed! return <h1>Hello from <em>inside</em> Postgres: {version}</h1>; }
And then using them in frontend SSR like this:
app/page.tsx
import HelloWorld from "@/rpc/hello-world"; export default function Page() { return <Suspense fallback={"Loading…"}> <HelloWorld /> </Suspense>; }
How does it work?
Using esbuild and PLV8 (a Postgres extension that embeds V8), the functions in the rpc/
folder are bundled and inserted into Postgres as part of the Vercel deployment process.
While experimental, this example is a good illustration of Framework-defined Infrastructure. In local dev, the functions are executed in the Node.js runtime and exist in a unified codebase. Upon git push
, specialized infrastructure (in this case PLV8 functions) is created.
The function source is extended with a minimalist yet useful runtime:
- - A
sql
template tag literal that wrapsplv8.execute
- - A
TextEncoder
polyfill fittingly namedfastestsmallesttextencoderdecoder
, required for React 18+ SSR[1]. - - A
console
polyfill that buffers logs and returns them as part of the rpc protocol so that they end up in the app logging context.
This resulting bundle is inserted into Postgres as follows:
CREATE OR REPLACE FUNCTION "rpc_hello-world"()
RETURNS text AS $$
${functionSource}
$$ LANGUAGE plv8 IMMUTABLE STRICT;
Local development
While Node.js and PLV8 are both based in V8, a good local dev experience needed to account for important differences:
- - Different runtime APIs
- - Sync vs async I/O
Both of these were solved by leveraging the isolated-vm
project transparently during local dev.
For each rpc/
function, a V8 Isolate is created without access to Node.js APIs. Our runtime is loaded on top (like sql
and TextEncoder
).
To preserve the synchronous plv8.execute
API semantics, we use applySyncPromise
which pauses the isolate until the promise that dispatches the query is resolved outside of it.
Production
To invoke our functions in production, <HelloWorld />
is issuing a SELECT helloWorld() query to Postgres, which is then streamed to the client via React Server Components.
This makes it such that the Postgres functions are not exposed automatically, and gives us more control and integration with the frontend server side rendering lifecycle.
FAQ
Getting it
The source code is available on rauchg/react-postgres-components and released under the MIT license. Elephant icon by Lima Studio.
To deploy it, you'll need a Vercel Postgres or Neon database linked to the project.