docs/docs/00100-intro/00200-quickstarts/00155-nuxt.md
import { InstallCardLink } from "@site/src/components/InstallCardLink"; import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps";
Get a SpacetimeDB Nuxt app running in under 5 minutes.
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Nuxt development server.
</StepText>
<StepCode>
spacetime dev --template nuxt-ts
</StepCode>
The template includes a basic Nuxt app connected to SpacetimeDB.
</StepText>
Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `components/AppContent.vue` to build your UI, and `app.vue` to configure the SpacetimeDB connection.
</StepText>
<StepCode>
my-spacetime-app/
├── spacetimedb/ # Your SpacetimeDB module
│ └── src/
│ └── index.ts # SpacetimeDB module logic
├── app.vue # Root component with provider
├── components/
│ └── AppContent.vue # Main UI component
├── server/
│ └── api/
│ └── people.get.ts # Server-side data fetching
├── module_bindings/ # Auto-generated types
├── nuxt.config.ts # Nuxt configuration
└── package.json
</StepCode>
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
</StepText>
<StepCode>
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
person: table(
{ public: true },
{
name: t.string(),
}
),
});
export default spacetimedb;
export const add = spacetimedb.reducer(
{ name: t.string() },
(ctx, { name }) => {
ctx.db.person.insert({ name });
}
);
export const sayHello = spacetimedb.reducer(ctx => {
for (const person of ctx.db.person.iter()) {
console.info(`Hello, ${person.name}!`);
}
console.info('Hello, World!');
});
</StepCode>
spacetime call add Alice
"Alice"
spacetime call say_hello
spacetime logs 2025-01-13T12:00:00.000000Z INFO: Hello, Alice! 2025-01-13T12:00:00.000000Z INFO: Hello, World!
</StepCode>
</Step>
<Step title="Understand server-side rendering">
<StepText>
The SpacetimeDB SDK works both server-side and client-side. The template uses a hybrid approach:
- **Server API route** (`server/api/people.get.ts`): Fetches initial data during SSR for fast page loads
- **Client composables**: Maintain a real-time WebSocket connection for live updates
The server API route connects to SpacetimeDB, subscribes, fetches data, and disconnects.
</StepText>
<StepCode>
```typescript
// server/api/people.get.ts
import { DbConnection, tables } from '../../module_bindings';
export default defineEventHandler(async () => {
return new Promise((resolve, reject) => {
DbConnection.builder()
.withUri(process.env.SPACETIMEDB_HOST!)
.withDatabaseName(process.env.SPACETIMEDB_DB_NAME!)
.onConnect((conn) => {
conn.subscriptionBuilder()
.onApplied(() => {
const people = Array.from(conn.db.person.iter());
conn.disconnect();
resolve(people);
})
.subscribe(tables.person);
})
.build();
});
});
</StepCode>
</StepCode>
</Step>
<Step title="Use composables and SSR data together">
<StepText>
Use `useFetch` to load initial data server-side, then Vue composables for real-time updates on the client. The component displays server-fetched data immediately while the WebSocket connection establishes.
</StepText>
<StepCode>
```vue
<!-- components/AppContent.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue';
import { tables, reducers } from '../module_bindings';
// Fetch initial data server-side for SSR
const { data: initialPeople } = await useFetch('/api/people');
// On the client, use real-time composables
let conn, people, addReducer;
if (import.meta.client) {
const { useSpacetimeDB, useTable, useReducer } = await import('spacetimedb/vue');
conn = useSpacetimeDB();
[people] = useTable(tables.person);
addReducer = useReducer(reducers.add);
}
// Use real-time data once connected, fall back to SSR data
const displayPeople = computed(() => {
if (conn?.isActive && people?.value) return people.value;
return initialPeople.value ?? [];
});
</script>
</StepCode>