A structured approach to developing features in the SaaS boilerplate, covering development lifecycle, testing strategies, and optimization practices.
# Clone and install dependencies
pnpm install
# Start development server
pnpm dev
# Build packages first for dependencies
pnpm build:packagesPlanning Phase
Implementation Phase
src/db/schemasrc/db/repositoriessrc/servicesIntegration Phase
Test individual functions and components:
# Run unit tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverageTesting Patterns:
// Example test utilities
import { createTestUser, createTestOrganization } from '@/lib/test-utils'
describe('User Management', () => {
beforeEach(async () => {
// Clean test database
await cleanTestDatabase()
})
it('should create user with organization', async () => {
const user = await createTestUser({
email: 'test@example.com',
role: 'admin'
})
const org = await createTestOrganization({
ownerId: user.id,
name: 'Test Organization'
})
expect(org.members).toContain(user.id)
})
})# Run linting
pnpm lint
# Fix linting issues automatically
pnpm lint:fix
# Lint specific directories
pnpm lint src/componentsKey Rules:
Branch Strategy: Use feature branches with descriptive names like feature/user-authentication or fix/payment-webhook-handling.
# Create feature branch
git checkout -b feature/new-feature-name
# Regular commits with descriptive messages
git add .
git commit -m "feat: add user role management system"
# Push and create pull request
git push origin feature/new-feature-name// Prefer Server Components for data fetching
export default async function UserDashboard() {
const user = await getCurrentUser()
const subscription = await getUserSubscription(user.id)
return (
<div>
<UserProfile user={user} />
<SubscriptionStatus subscription={subscription} />
</div>
)
}
// Use Client Components only when needed
'use client'
export function InteractiveChart({ data }: { data: ChartData }) {
const [selectedPeriod, setSelectedPeriod] = useState('month')
return (
<Chart
data={data}
period={selectedPeriod}
onPeriodChange={setSelectedPeriod}
/>
)
}// Use select for specific fields
const users = await db
.select({
id: usersTable.id,
email: usersTable.email,
name: usersTable.name,
})
.from(usersTable)
.where(eq(usersTable.organizationId, orgId))
// Use joins instead of multiple queries
const usersWithSubscriptions = await db
.select()
.from(usersTable)
.leftJoin(subscriptionsTable, eq(usersTable.id, subscriptionsTable.userId))
.where(eq(usersTable.organizationId, orgId))Always verify these items before deploying:
pnpm test)pnpm lint)pnpm type-check)pnpm db:generate)# Production build
pnpm build
# Verify build output
ls -la .next/
# Run database migrations (production)
pnpm db:migrate
# Deploy to hosting platform
pnpm deployNODE_ENV=development
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=postgresql://localhost:5432/saas_dev
STRIPE_SECRET_KEY=sk_test_...
// Error boundary for React components
import { ErrorBoundary } from 'next/dist/client/components/error-boundary'
export function FeatureErrorBoundary({ children }: { children: React.ReactNode }) {
return (
<ErrorBoundary
errorComponent={({ error, reset }) => (
<div className="p-4 border border-red-200 rounded">
<h3>Something went wrong!</h3>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
>
{children}
</ErrorBoundary>
)
}
// Server-side error logging
export function logError(error: Error, context: string) {
console.error(`[${context}] ${error.message}`, {
stack: error.stack,
timestamp: new Date().toISOString(),
})
// Send to monitoring service in production
if (process.env.NODE_ENV === 'production') {
// sendToMonitoring(error, context)
}
}// Monitor API response times
export async function withTiming<T>(
operation: () => Promise<T>,
label: string
): Promise<T> {
const start = Date.now()
try {
const result = await operation()
const duration = Date.now() - start
console.log(`${label} completed in ${duration}ms`)
return result
} catch (error) {
const duration = Date.now() - start
console.error(`${label} failed after ${duration}ms:`, error)
throw error
}
}
// Usage in API routes
export async function GET() {
return withTiming(async () => {
const users = await getAllUsers()
return NextResponse.json(users)
}, 'GET /api/users')
}# Monitor database performance
pnpm db:studio
# Check slow queries
# Access your database directly and run:
SELECT query, mean_time, calls
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;Development Tips: