Tech Stack
Complete overview of technologies used and why we chose them
Overview
This project uses a carefully selected set of modern technologies that work together to provide a robust, type-safe, and performant full-stack application.
Core Technologies
Framework: Next.js 16+
Next.js is our primary framework, using the App Router for server-first architecture.
Why Next.js?
- Server Components - Improved performance and reduced client bundle size
- Built-in optimization - Automatic code splitting, image optimization, font optimization
- Flexible rendering - Static, dynamic, and incremental static regeneration
- File-based routing - Intuitive project structure
- API routes - Backend and frontend in one codebase
- Strong ecosystem - Extensive tooling and community support
Key Features Used:
- App Router for modern routing patterns
- Server Actions for form submissions (optional)
- Streaming for progressive loading
unstable_cachefor server-side caching- Middleware for request interception
// Example: Server Component with data fetching
import { api } from "@/trpc/server";
export default async function UsersPage() {
const users = await api.users.list.query({ limit: 10 });
return (
<div>
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}Language: TypeScript
TypeScript provides static typing for JavaScript, catching errors at compile-time.
Why TypeScript?
- Type safety - Catch errors before runtime
- Better IDE support - Autocomplete, refactoring, navigation
- Self-documenting code - Types serve as inline documentation
- Refactoring confidence - Know exactly what breaks when you change code
- Team scaling - Enforces contracts between code modules
Configuration:
- Strict mode enabled (
strict: true) - Path aliases configured (
@/forsrc/) - Module resolution set to
bundler
// Example: Type-safe function
type User = {
id: string;
name: string;
email: string;
};
export function formatUserName(user: User): string {
return user.name.toUpperCase();
// TypeScript ensures 'user' has 'name' property
}Database: PostgreSQL + Drizzle ORM
PostgreSQL is our relational database, accessed through Drizzle ORM.
Why PostgreSQL?
- Reliability - ACID compliance, proven track record
- Feature-rich - JSON support, full-text search, GIS extensions
- Performance - Efficient indexing and query optimization
- Scalability - Handles large datasets and concurrent connections
- Open source - No licensing costs
Why Drizzle ORM?
- Type-safe queries - Full TypeScript integration
- Zero runtime overhead - No query builders at runtime
- SQL-like syntax - Easy to learn if you know SQL
- Migration support - Version control for your database schema
- Edge compatible - Works with serverless databases like Neon
// Example: Type-safe query with Drizzle
import { db } from "@/db";
import { users } from "@/db/tables/users";
import { eq } from "drizzle-orm";
const user = await db.query.users.findFirst({
where: eq(users.email, "john@example.com"),
with: {
organizations: true,
},
});See the Database guide for more details.
API: tRPC
tRPC provides end-to-end type safety for our API layer without code generation.
Why tRPC?
- Type inference - Share types between client and server automatically
- No code generation - Types flow naturally from implementation
- Developer experience - Autocomplete everywhere, catch errors instantly
- React Query integration - Powerful data fetching and caching
- Lightweight - Small bundle size, minimal overhead
Alternative to REST/GraphQL:
- No need to write OpenAPI specs or GraphQL schemas
- No need to sync types between frontend and backend
- Catch breaking changes at compile-time, not runtime
// Server-side router
export const userRouter = createTRPCRouter({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await userOperations.getById(input.id);
}),
});
// Client-side usage - fully typed!
const { data } = api.users.getById.useQuery({ id: "123" });
// ^? User | undefinedSee the tRPC guide for more details.
Authentication: Better-Auth
Better-Auth handles user authentication and session management.
Why Better-Auth?
- Type-safe - Full TypeScript support
- Flexible - Email/password, OAuth, magic links, passkeys
- Secure - HTTP-only cookies, CSRF protection, rate limiting
- Modern - Built for React Server Components
- Customizable - Extensible with plugins and adapters
Supported Auth Methods:
- Email/Password
- OAuth (Google, GitHub, and more)
- Magic Links (passwordless)
- Passkeys (WebAuthn)
// Server Component
import { getAuth } from "@/lib/auth/server";
const auth = await getAuth();
if (!auth.user) {
redirect("/login");
}
// Client Component
"use client";
import { useAuth } from "@/lib/auth/client";
const { user, signIn, signOut } = useAuth();See the Authentication guide for more details.
Styling & UI
Tailwind CSS
Tailwind CSS is our utility-first CSS framework.
Why Tailwind CSS?
- Rapid development - Build UIs quickly with utility classes
- Consistency - Design system baked into the framework
- Responsive design - Mobile-first breakpoints built-in
- Customization - Easily theme and extend
- Tree-shaking - Only CSS you use ends up in production
- No naming fatigue - No need to invent class names
// Example: Tailwind classes
<button className="rounded-lg bg-primary px-4 py-2 text-white hover:bg-primary/90">
Click Me
</button>Shadcn UI
Shadcn UI provides accessible, customizable UI components.
Why Shadcn UI?
- Copy-paste components - Own your code, not imported from npm
- Radix UI primitives - Accessibility built-in
- Customizable - Modify components to fit your design
- Tailwind-based - Consistent with our styling approach
- No runtime overhead - Just React components
Available Components:
- Forms: Input, Select, Checkbox, Radio, Textarea
- Overlays: Dialog, Popover, Tooltip, Sheet
- Navigation: Tabs, Dropdown Menu, Command
- Feedback: Toast, Alert, Progress
- And many more...
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
<Button variant="default" size="lg">
Submit
</Button>See the UI Components guide for more details.
Forms & Validation
React Hook Form
React Hook Form manages form state and validation.
Why React Hook Form?
- Performance - Minimal re-renders
- Easy integration - Works with UI libraries
- Validation - Built-in or custom validation
- TypeScript support - Fully typed
- Small bundle size - ~9kb gzipped
Zod
Zod is our schema validation library.
Why Zod?
- Type inference - Generate TypeScript types from schemas
- Composable - Build complex validations from simple ones
- Error messages - Customizable and translatable
- Universal - Same schemas for client and server
- No dependencies - Pure TypeScript
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 address"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
type FormData = z.infer<typeof schema>;
const form = useForm<FormData>({
resolver: zodResolver(schema),
});State Management
React Query (via tRPC)
React Query manages server state through tRPC.
Why React Query?
- Automatic caching - Reduce network requests
- Background updates - Keep data fresh
- Optimistic updates - Instant UI feedback
- Request deduplication - Avoid duplicate requests
- Offline support - Handle network failures gracefully
Built into tRPC:
- All tRPC hooks use React Query under the hood
- Automatic type inference for cache keys
- Integrated with tRPC's error handling
const { data, isLoading, error } = api.users.list.useQuery();
const mutation = api.users.create.useMutation({
onSuccess: () => {
// Invalidate cache to refetch
utils.users.list.invalidate();
},
});Internationalization
next-intl
next-intl handles internationalization.
Why next-intl?
- Type-safe translations - Catch missing keys at compile-time
- Server Components support - Works with App Router
- ICU message format - Rich formatting (plurals, dates, numbers)
- Small bundle size - Only load needed translations
- Routing integration - Locale prefixes handled automatically
import { useTranslations } from "next-intl";
function WelcomeMessage() {
const t = useTranslations("common");
return <h1>{t("welcome", { name: "John" })}</h1>;
// Output: "Welcome, John!"
}See the Internationalization guide for more details.
React Email
React Email lets us build emails with React components.
Why React Email?
- Component-based - Reuse React knowledge
- Type-safe - TypeScript support
- Preview - See emails in development
- Responsive - Mobile-friendly by default
- Client compatibility - Works across email clients
import { Button, Html, Text } from "@react-email/components";
export function WelcomeEmail({ name }: { name: string }) {
return (
<Html>
<Text>Welcome, {name}!</Text>
<Button href="https://example.com">Get Started</Button>
</Html>
);
}File Storage
Firebase Storage / AWS S3
Firebase Storage or AWS S3 for file uploads.
Why Firebase Storage?
- Easy setup - Quick to get started
- SDKs - Official libraries for web and mobile
- Generous free tier - Good for small projects
- CDN included - Fast file delivery
Why AWS S3?
- Scalability - Handle any amount of data
- Reliability - 99.999999999% durability
- Ecosystem - Integrates with other AWS services
- Cost-effective - Pay only for what you use
Configuration is environment-based. See src/config/storage.ts for setup.
Development Tools
TypeScript ESLint
typescript-eslint for code linting.
- Catch potential bugs
- Enforce code style
- Best practices
Prettier
Prettier for code formatting.
- Consistent formatting
- No debates about style
- Automatic on save
Drizzle Kit
Drizzle Kit for database migrations.
- Generate migrations from schema changes
- Apply migrations to database
- Introspect existing databases
Monitoring & Error Tracking
Sentry
Sentry for error tracking and performance monitoring.
Features:
- Error tracking
- Performance monitoring
- Release tracking
- User feedback
Configuration files:
sentry.server.config.ts- Server-side configsentry.edge.config.ts- Edge runtime config
Deployment
Vercel
Optimized for Vercel deployment.
Why Vercel?
- Zero config - Deploy with one click
- Edge network - Fast global delivery
- Preview deployments - Test before merging
- Environment variables - Easy configuration
- Analytics - Built-in performance tracking
Alternative platforms supported:
- AWS (via SST or Amplify)
- Google Cloud Run
- Docker containers
- Any Node.js host
Summary
This tech stack provides:
- ✅ Type safety - From database to UI
- ✅ Developer experience - Fast feedback loops
- ✅ Performance - Server-first, optimized builds
- ✅ Scalability - Proven technologies
- ✅ Maintainability - Clear patterns and structure
Each technology was chosen to work seamlessly with the others, creating a cohesive and productive development experience.