Build local-first apps with Evolu and Next.js for offline-first operation, end-to-end encryption, and cross-device sync. Covers branded types, reactive queries, CRUD, and mnemonic recovery...
Enable TypeScript strict mode and exactOptionalPropertyTypes. Define schema with branded types using id() and NonEmptyString. Create Evolu instance and wrap app with EvoluProvider. Use useQuery for reactive data and create/update for mutations.
Wait for response before proceeding.
Packages: @evolu/common, @evolu/react, @evolu/react-web
Quick install:
npm install @evolu/common @evolu/react @evolu/react-web
See: [references/installation.md] for platform variants (React Native, Expo, Svelte) and full TypeScript configuration.
Key patterns:
id("TableName") - branded ID types prevent mixing IDs across tablesNonEmptyString + maxLength() - validated stringsnullOr() - optional fields (Evolu uses null, not undefined)SqliteBoolean - booleans stored as 0/1See: [references/schema-definition.md] for complete examples and automatic system columns.
Key files:
lib/evolu.ts - Schema, instance creation, typed hooksapp/providers.tsx - EvoluProvider + Suspense wrapperapp/layout.tsx - Import providersImportant: All Evolu code must be in Client Components ("use client").
See: [references/instance-setup.md] for complete setup code.
Key patterns:
const todosQuery = evolu.createQuery((db) =>
db.selectFrom("todo")
.select(["id", "title", "isCompleted"])
.where("isDeleted", "is not", evolu.sqliteTrue)
.orderBy("createdAt", "desc")
);
const { rows } = useQuery(todosQuery);
Always filter: .where("isDeleted", "is not", evolu.sqliteTrue)
See: [references/queries.md] for relationships, conditional queries, and useQueries for parallel loading.
Key rules:
create(tableName, data) - ID auto-generatedupdate(tableName, { id, ...changes }) - ID requiredisDeleted: evolu.sqliteTrue.from() before mutationsSee: [references/mutations.md] for complete CRUD examples and batch operations.
Key operations:
getMnemonic() - Get 12-word recovery phrase (handle securely!)restoreAppOwner(mnemonic) - Restore on new deviceresetAppOwner() - Delete all local data (irreversible)exportDatabase() - Export for backupSecurity: Mnemonic = master key to ALL encrypted data. Never log in production.
See: [references/owner-management.md] for complete examples and security checklist.
Architecture:
next/dynamic with ssr: false if hydration issues occurSee: [references/nextjs-integration.md] for complete setup and hydration troubleshooting.
See: [references/troubleshooting.md] for detailed solutions.
npx tsc --noEmit.from() before mutationsisDeleted rows"use client"useQuery have boundarynpm run build