Integrations
Astro
Render request-time rows and hydrate an island for live database updates.
On this page
Render tasks in an Astro page, then keep the list live with a React island and @hulla/db-react. The server supplies the first rows; the island subscribes to later changes.
Complete the shared task setup first. This example uses Astro’s React integration. Other island frameworks use their matching database binding.
Use a compatible server adapter. For a Node standalone deployment:
// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import react from '@astrojs/react'
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [react()],
vite: { ssr: { external: ['@hulla/db'] } },
})Install the framework’s @astrojs/node and @astrojs/react integrations alongside
React and the database packages. The server-rendered page
runs its frontmatter per request; static prerendered pages instead capture data
at build time and must not contain a private user’s result.
Read the selected rows in frontmatter, then use client:load to enable browser
observation without losing the server-rendered HTML:
---
// src/pages/index.astro
import { db } from '../../db/_generated/server'
import { readTasks } from '../../db/task-data'
import { Tasks } from '../components/Tasks'
const initialTasks = await readTasks(db)
---
<html lang="en">
<head>
<title>Live tasks</title>
</head>
<body>
<Tasks initialTasks={initialTasks} client:load />
</body>
</html>The React component shares the same hooks as other React applications:
// src/components/Tasks.tsx
import type { Output } from '@hulla/db'
import { useMutation, useQuery } from '@hulla/db-react'
import { create, list } from '../../db/_generated/client/tasks'
export function Tasks({ initialTasks }: { initialTasks: Output<typeof list> }) {
const tasks = useQuery(list, { initialData: initialTasks })
const add = useMutation(create)
return (
<>
{tasks.status === 'error' && <p>Could not load tasks.</p>}
{add.state.status === 'error' && <p>Could not save the task.</p>}
<ul>
{tasks.data?.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
<button
onClick={() => {
void add({ title: 'New task' })
}}>
Add task
</button>
</>
)
}Without a client hydration directive,
the island remains HTML and never subscribes. client:only skips its server render;
use client:load for this SSR handoff. Other hydration timings delay when live
observation begins. Multiple React islands can observe the same generated query
and share the database cache without sharing a React context.
Verify Astro.request.headers through your provider before a private read, or use
a verified identity installed by application middleware in Astro.locals. Pass
the resulting account key as initialAuthKey to the binding along with its rows.
Do not prerender or publicly cache private pages. Query/mutation declarations
still need their own checks; see the shared auth setup.
Astro actions and API endpoints can use direct db operations and transactions;
their writes refresh live islands. The Astro page/frontmatter does not itself
become live. Keep native assets with the server output, use a host permitting
subprocesses, and deploy the persistent streaming service separately. See
deployment requirements.