| Layer | Technology | Rationale |
|---|---|---|
| Framework | Next.js 15.2.3+ (App Router) | Full-stack React, great DX, security patched |
| Language | TypeScript | Type safety, better maintainability |
| Styling | Tailwind CSS | Rapid UI development, consistent design system |
| UI Components | shadcn/ui | Accessible, customizable, copy-paste components |
| Database | Firestore | NoSQL, real-time sync, serverless, Firebase ecosystem |
| Authentication | JWT with private key | Stateless auth, integrates with existing RDS auth |
| State Management | React Query (TanStack Query) | Server state management, caching |
| Deployment | Vercel or Docker | Easy CI/CD, preview deployments |
| Framework | Pros | Cons | Verdict |
|---|---|---|---|
| Next.js | Mature ecosystem, SSR/SSG, API routes, great docs | Vercel-centric, can be complex | Selected |
| Remix | Great data loading, progressive enhancement | Smaller ecosystem, less community adoption | Good alternative |
| SvelteKit | Fast, lightweight, great DX | Smaller talent pool, less mature | Not ideal for team |
| Nuxt (Vue) | Good full-stack Vue option | Team may prefer React ecosystem | Depends on team |
| T3 Stack | Type-safe end-to-end with tRPC | More complex setup, learning curve | Consider for v2 |
-
Full-Stack Capability
- API Routes for backend logic
- Server Components for efficient data fetching
- No need for separate backend service initially
-
React Ecosystem
- Large community and talent pool
- Extensive component libraries
- Easy to find solutions to problems
-
RDS Alignment
- Other RDS projects likely use React
- Consistent tech stack across organization
- Easier for contributors to onboard
-
Performance
- Server-side rendering for fast initial loads
- Automatic code splitting
- Built-in image optimization
-
Developer Experience
- Hot module reloading
- Great TypeScript support
- Excellent error messages
Why Firestore:
- Real-time sync out of the box (great for live dashboards)
- Serverless - no database management needed
- Scales automatically
- Firebase ecosystem (Auth, Hosting, Functions if needed)
- Generous free tier (Spark plan)
- Flexible NoSQL schema
Collections Structure:
├── users/
│ └── {userId}
│ ├── githubId: string
│ ├── username: string
│ ├── role: "MEMBER" | "ADMIN" | "SUPER_ADMIN"
│ └── createdAt: timestamp
│
├── oooRecords/
│ └── {recordId}
│ ├── userId: string
│ ├── startDate: timestamp
│ ├── endDate: timestamp
│ ├── reason: string (optional)
│ └── createdAt: timestamp
│
├── tasks/
│ └── {taskId}
│ ├── externalId: string (optional)
│ ├── title: string
│ ├── status: "TODO" | "IN_PROGRESS" | "BLOCKED" | "UNDER_REVIEW" | "COMPLETED"
│ ├── assigneeId: string
│ └── lastUpdated: timestamp
│
└── taskUpdates/
└── {updateId}
├── taskId: string
├── content: string
└── createdAt: timestamp
TypeScript Types:
interface User {
id: string;
githubId: string;
username: string;
role: 'MEMBER' | 'ADMIN' | 'SUPER_ADMIN';
createdAt: Timestamp;
}
interface OOORecord {
id: string;
userId: string;
startDate: Timestamp;
endDate: Timestamp;
reason?: string;
createdAt: Timestamp;
}
interface Task {
id: string;
externalId?: string;
title: string;
status: 'TODO' | 'IN_PROGRESS' | 'BLOCKED' | 'UNDER_REVIEW' | 'COMPLETED';
assigneeId: string;
lastUpdated: Timestamp;
}
interface TaskUpdate {
id: string;
taskId: string;
content: string;
createdAt: Timestamp;
}Why JWT:
- Stateless authentication - no session storage needed
- Integrates with existing RDS authentication system
- Private key verification ensures token integrity
- Works seamlessly with Next.js middleware
Implementation:
// lib/auth.ts
import { jwtVerify } from 'jose';
const privateKey = new TextEncoder().encode(process.env.JWT_PRIVATE_KEY);
export async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, privateKey);
return payload;
} catch {
return null;
}
}
// Types for JWT payload
interface JWTPayload {
userId: string;
username: string;
role: 'MEMBER' | 'ADMIN' | 'SUPER_ADMIN';
exp: number;
}Middleware for Protected Routes:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from './lib/auth';
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
|| request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
const payload = await verifyToken(token);
if (!payload) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};Configuration:
- JWT token passed via cookie or Authorization header
- Private key stored in environment variables
- Role-based access control from token claims
Why Tailwind:
- Rapid prototyping
- Consistent spacing and colors
- Great for responsive design
- Small production bundle (purged CSS)
Why shadcn/ui:
- Not a component library (copy-paste, fully customizable)
- Accessible by default (Radix UI primitives)
- Matches Tailwind workflow
- Components for: Tables, Calendars, Forms, Modals, etc.
pulse-app/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Auth routes (login, callback)
│ │ ├── (dashboard)/ # Protected dashboard routes
│ │ │ ├── page.tsx # Dashboard home
│ │ │ ├── ooo/ # OOO management
│ │ │ ├── tasks/ # Task tracking
│ │ │ └── admin/ # Admin features
│ │ ├── api/ # API routes
│ │ │ ├── auth/ # NextAuth handlers
│ │ │ ├── ooo/ # OOO CRUD
│ │ │ └── tasks/ # Task operations
│ │ ├── layout.tsx
│ │ └── page.tsx # Landing/redirect
│ ├── components/
│ │ ├── ui/ # shadcn/ui components
│ │ ├── dashboard/ # Dashboard-specific components
│ │ └── shared/ # Shared components
│ ├── lib/
│ │ ├── auth.ts # NextAuth config
│ │ ├── firebase.ts # Firebase/Firestore client
│ │ └── utils.ts # Utility functions
│ ├── hooks/ # Custom React hooks
│ └── types/ # TypeScript types
├── firestore.rules # Firestore security rules
├── firestore.indexes.json # Firestore indexes
├── public/
├── .env.example
├── firebase.json # Firebase config
├── package.json
├── tailwind.config.js
└── tsconfig.json
- Node.js 18+
- Firebase project (create at console.firebase.google.com)
- JWT private key for token verification
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Add Firebase config and JWT private key
# Start dev server
pnpm dev
# Deploy Firestore rules (optional)
firebase deploy --only firestore:rules- Zero-config deployment
- Preview deployments for PRs
- Edge functions support
- Free tier generous
- Full control
- Works with AWS, GCP, DigitalOcean
- Good for self-hosting
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["npm", "start"]- Consider NestJS or Express for dedicated API
- Keep Next.js as frontend only
- Socket.io or Pusher for live updates
- Server-Sent Events for simpler use cases
- End-to-end type safety
- Can migrate to T3 stack later
| Decision | Choice | Confidence |
|---|---|---|
| Framework | Next.js 15.2.3+ | High |
| Language | TypeScript | High |
| Database | Firestore | High |
| Auth | JWT (private key) | High |
| Styling | Tailwind + shadcn/ui | High |
| Deployment | Vercel (MVP) | Medium |
This stack provides a solid foundation that can scale with the project's needs while maintaining excellent developer experience.