Ship faster with a
modern foundation
Stop wasting time on boilerplate. Start with authentication, database, file storage, and a scalable architecture — all pre-configured and ready to deploy.
Everything you need, nothing you don't
Authentication that just works
Sign up, sign in, password reset — handled. Reactive state with
useSyncExternalStore keeps your UI in sync
instantly, while secure cookies enable seamless server-side
access.
Architecture that scales
Built on Feature-Sliced Design principles. Clear boundaries between features, widgets, and shared code mean your codebase stays organized as your team and product grow.
Forms without the pain
Zod schemas + React Hook Form = type-safe validation from frontend to backend. Define once, validate everywhere. No more runtime surprises.
Own your infrastructure
Deploy anywhere with Docker Compose. Optimized for Coolify, but works with any container platform. Your data, your servers, your rules.
File uploads, simplified
Pre-configured Cloudflare R2 client for S3-compatible storage. Upload files, generate presigned URLs, and manage assets without vendor lock-in.
Backend you can extend
PocketBase gives you a real-time database, auth, and file storage in a single binary. Add custom endpoints and hooks with JavaScript when you need more.
Powered by tools you already love
Up and running in 2 minutes
Clone the repository
Use the GitHub template or clone directly to get started.
cd my-app
Install dependencies & start services
Bun handles package installation. Docker Compose starts PocketBase.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# PocketBase is now running at http://localhost:8080
Configure & launch
Copy the environment template and start the dev server.
bun dev
# Open http://localhost:3000
See how it works
Client-side Authentication
The useAuth hook gives you reactive access to the
current user. No prop drilling, no context boilerplate — just
import and use.
- Automatic hydration handling prevents UI flicker
- Real-time updates when auth state changes
- Type-safe user object from PocketBase
import { useAuth } from "@/shared/providers/auth-provider"; function Dashboard() { const { user, isAuthenticated, isLoading } = useAuth(); if (isLoading) return <Skeleton />; if (!isAuthenticated) return <SignInPrompt />; return ( <div> Welcome back, {user.name}! </div> ); }
Sign In & Sign Out
Authentication actions are simple async functions. Call them from anywhere — form handlers, buttons, or effects.
- Cookie sync keeps server and client in sync
- Automatic error handling with typed responses
- Works with Server Components out of the box
import { signIn, signOut, syncAuthCookie } from "@/shared/auth/client"; // Sign in with email and password async function handleSignIn(email: string, password: string) { await signIn(email, password); syncAuthCookie(); // Sync to cookie for SSR } // Sign out clears everything function handleSignOut() { signOut(); // Clears auth store + cookie }
Server-side Auth Access
Server Components can read auth state from cookies. Perfect for protecting pages, fetching user-specific data, or rendering personalized content.
- Zero client JavaScript for auth checks
- Secure — tokens never exposed to client
- Works with Next.js middleware
import { cookies } from "next/headers"; import PocketBase from "pocketbase"; export default async function ProtectedPage() { const cookieStore = await cookies(); const pb = new PocketBase(process.env.POCKETBASE_URL); const authCookie = cookieStore.get("pb_auth"); if (authCookie?.value) { pb.authStore.loadFromCookie(`pb_auth=${authCookie.value}`); } const user = pb.authStore.isValid ? pb.authStore.record : null; // Now use `user` to fetch data or render UI }
Type-safe Form Validation
Define your schema once with Zod, and get type inference everywhere. React Hook Form handles the rest — validation, errors, and submission.
- Compile-time type checking
- Automatic error messages
- Reuse schemas on server for API validation
import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; const schema = z.object({ email: z.string().email("Invalid email"), password: z.string().min(8, "Min 8 characters"), }); type FormData = z.infer<typeof schema>; function SignInForm() { const { register, handleSubmit, formState } = useForm<FormData>({ resolver: zodResolver(schema), }); // formState.errors is fully typed! }
Organized from day one
Feature-Sliced Design
A battle-tested architecture that keeps your code organized as complexity grows. Each layer has clear responsibilities and import rules.
-
features/User interactions and business logic (auth forms, etc.) -
widgets/Composite UI blocks that combine features (user menu, etc.) -
shared/Reusable utilities, UI components, and API clients
src/ ├── features/ # User interactions │ └── auth/ │ ├── sign-in-form.tsx │ ├── sign-up-form.tsx │ └── schemas.ts ├── widgets/ # Composite blocks │ ├── user-menu/ │ └── health-status/ └── shared/ # Shared code ├── ui/ # shadcn components ├── db/ # PocketBase clients ├── auth/ # Auth helpers ├── storage/ # R2 client ├── providers/ # React providers └── lib/ # Utilities
Deploy anywhere you want
This starter is designed for self-hosting, but works great with any platform. Choose what fits your needs — no vendor lock-in.
Frequently asked questions
Why PocketBase instead of a traditional database?
PocketBase gives you a database, authentication, file storage, and real-time subscriptions in a single 15MB binary. No separate services to manage, no complex setup. And don't let anyone tell you SQLite doesn't scale — it handles thousands of concurrent users without breaking a sweat. PocketBase is production-ready for the vast majority of applications, not just MVPs.
Can I use this with a different database?
Technically yes, but that's not the point of this starter. If you want Prisma, Drizzle, or another ORM, there are plenty of excellent starters built around those. This one is specifically designed around PocketBase's strengths — use it if that's what you want.
Is Feature-Sliced Design overkill for small projects?
We don't follow FSD to the letter — just the parts that make
sense. Three folders (features,widgets
, shared) with simple import rules. No layers, no
segments, no over-engineering. It's lightweight structure that
grows with you, not a rigid framework.
Why Bun instead of Node.js?
Bun is significantly faster for installing dependencies and
running scripts. It's also a drop-in replacement for Node.js in
most cases. If you prefer Node, just replace bun
with npm or pnpm in the scripts —
everything else works the same.
How do I add OAuth providers (Google, GitHub, etc.)?
PocketBase has built-in OAuth2 support. Enable providers in the
PocketBase admin UI at /_/, add your client ID and
secret, and use the
pb.collection('users').authWithOAuth2() method. The
auth provider in this starter will pick up the session
automatically.
Can I use this for a SaaS with multiple tenants?
Yes, but you'll need to add tenant isolation. PocketBase supports collection rules that can filter data by tenant ID. For more complex multi-tenancy (separate databases per tenant), you might want to look at a different architecture, but this starter is a solid foundation for single-tenant or simple multi-tenant apps.
Is Cloudflare R2 required for file storage?
No. PocketBase has built-in file storage that works great for most use cases. R2 is pre-configured as an option for when you need S3-compatible storage, CDN distribution, or want to separate file storage from your database server.
How do I customize the UI components?
All UI components are from
shadcn/ui, which means they live in your codebase (not node_modules).
Edit them directly in src/shared/ui/. They're built
with Radix primitives and Tailwind, so customization is
straightforward.
Ready to build something great?
Join developers who've stopped reinventing the wheel and started shipping faster.