Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:46:46 +08:00
commit c47723919d
6 changed files with 1339 additions and 0 deletions

View File

@@ -0,0 +1,526 @@
# Server Actions vs API Routes Decision Matrix
Comprehensive guide for choosing between Server Actions and API routes in Next.js applications.
## Quick Decision Tree
```
Is it a form submission?
├─ YES → Use Server Action (unless external API involved)
└─ NO
├─ Is it a webhook or external callback?
│ └─ YES → Use API Route
└─ NO
├─ Does it need non-POST methods (GET, PUT, DELETE)?
│ └─ YES → Use API Route
└─ NO
├─ Does it proxy an external API?
│ └─ YES → Use API Route
└─ NO
├─ Does it need revalidatePath/revalidateTag?
│ └─ YES → Use Server Action
└─ NO → Either works (prefer Server Action for simplicity)
```
## Detailed Decision Matrix
| Criterion | Server Action | API Route | Notes |
|-----------|--------------|-----------|-------|
| **Form submission** | [OK] Strongly recommended | - | Progressive enhancement, CSRF protection |
| **Data mutation** | [OK] Recommended | ○ Works | Server Actions simpler for mutations |
| **Revalidation needed** | [OK] Built-in | - Requires manual | `revalidatePath()`, `revalidateTag()` |
| **External API proxy** | - Not ideal | [OK] Recommended | Hide API keys, rate limiting |
| **Webhooks** | - Cannot use | [OK] Required | Needs public URL endpoint |
| **OAuth callbacks** | - Cannot use | [OK] Required | Third-party redirects |
| **GET requests** | - POST only | [OK] Required | Server Actions are POST-only |
| **Multiple HTTP methods** | - POST only | [OK] Required | REST API with GET/POST/PUT/DELETE |
| **External client access** | - Internal only | [OK] Required | Mobile apps, third-party integrations |
| **Custom response headers** | - Limited | [OK] Full control | CORS, caching, content-type |
| **Streaming responses** | ○ Possible | [OK] Easier | SSE, custom streams |
| **File uploads** | [OK] FormData | ○ Works | Server Actions handle FormData natively |
| **Type safety** | [OK] Full | ○ Manual | Server Actions fully type-safe |
| **CSRF protection** | [OK] Automatic | - Manual | Server Actions have built-in protection |
| **Progressive enhancement** | [OK] Works without JS | - Requires JS | Forms work even if JS fails |
| **Authentication in RSC** | [OK] Direct access | - Via middleware | Server Actions can access auth directly |
**Legend**: [OK] = Strongly recommended, ○ = Works but not ideal, - = Not suitable/possible
## Use Case Patterns
### 1. Form Submissions
**Scenario**: User submits a form to create/update data
**Recommended**: Server Action
**Reasoning**:
- Built-in form handling with `action` attribute
- Automatic CSRF protection
- Progressive enhancement (works without JavaScript)
- Simpler code than fetch + API route
- Type-safe with TypeScript
**Example**:
```typescript
// Server Action (recommended)
'use server';
export async function createEntity(formData: FormData) {
const name = formData.get('name') as string;
await db.entity.create({ name });
revalidatePath('/entities');
return { success: true };
}
// Usage in component
<form action={createEntity}>
<input name="name" required />
<button type="submit">Create</button>
</form>
```
### 2. External API Proxying
**Scenario**: Client needs to call external API, but API keys must be hidden
**Recommended**: API Route
**Reasoning**:
- Hides API keys on server
- Enables rate limiting
- Allows response transformation
- Can cache responses
- Standard REST pattern
**Example**:
```typescript
// API Route (recommended)
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const query = searchParams.get('query');
const response = await fetch(
`https://api.external.com?key=${process.env.API_KEY}&q=${query}`,
{ next: { revalidate: 3600 } } // Cache for 1 hour
);
return Response.json(await response.json());
}
```
### 3. Webhooks
**Scenario**: External service (Stripe, GitHub, etc.) sends POST requests
**Recommended**: API Route
**Reasoning**:
- Needs public, stable URL
- External service cannot call Server Actions
- Requires signature verification
- May need custom response codes/headers
**Example**:
```typescript
// API Route (required)
export async function POST(request: Request) {
const signature = request.headers.get('stripe-signature');
const body = await request.text();
// Verify webhook signature
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.WEBHOOK_SECRET
);
// Process event
await handleWebhookEvent(event);
return Response.json({ received: true });
}
```
### 4. Data Mutations with Revalidation
**Scenario**: Update data and refresh cache
**Recommended**: Server Action
**Reasoning**:
- Built-in `revalidatePath()` and `revalidateTag()`
- Simpler than manual cache invalidation
- Type-safe
- Direct server access
**Example**:
```typescript
// Server Action (recommended)
'use server';
export async function updateEntity(id: string, data: any) {
await db.entity.update({ where: { id }, data });
revalidatePath('/entities');
revalidateTag(`entity-${id}`);
return { success: true };
}
```
### 5. OAuth Callbacks
**Scenario**: Third-party service redirects back to your app
**Recommended**: API Route
**Reasoning**:
- Needs stable public URL
- External redirect target
- May need to set cookies/headers
- Standard OAuth flow pattern
**Example**:
```typescript
// API Route (required)
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
// Exchange code for token
const token = await exchangeCodeForToken(code);
// Set cookie and redirect
const response = NextResponse.redirect('/dashboard');
response.cookies.set('auth_token', token, { httpOnly: true });
return response;
}
```
### 6. Public REST API
**Scenario**: Expose API endpoints for mobile apps or third-party integrations
**Recommended**: API Route
**Reasoning**:
- External client access
- Standard REST conventions
- Multiple HTTP methods (GET, POST, PUT, DELETE)
- Custom authentication (API keys, tokens)
- Can document with OpenAPI/Swagger
**Example**:
```typescript
// API Route (required)
export async function GET(request: Request) {
const token = request.headers.get('authorization');
if (!validateApiKey(token)) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const entities = await db.entity.findMany();
return Response.json({ entities });
}
```
### 7. File Uploads
**Scenario**: User uploads files
**Recommended**: Server Action
**Reasoning**:
- Native FormData handling
- Simpler than API route with multipart parsing
- Can stream large files
- Progressive enhancement
**Example**:
```typescript
// Server Action (recommended)
'use server';
export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File;
// Process file
const buffer = Buffer.from(await file.arrayBuffer());
await saveFile(buffer, file.name);
revalidatePath('/files');
return { success: true, filename: file.name };
}
```
### 8. Optimistic Updates
**Scenario**: Update UI immediately, validate on server
**Recommended**: Server Action
**Reasoning**:
- Works with `useOptimistic` hook
- Automatic rollback on error
- Type-safe
- Simpler state management
**Example**:
```typescript
'use client';
import { useOptimistic } from 'react';
function Component() {
const [optimisticData, addOptimistic] = useOptimistic(data);
async function handleUpdate(newData) {
addOptimistic(newData); // Update UI immediately
await serverAction(newData); // Validate on server
}
return <UI data={optimisticData} onUpdate={handleUpdate} />;
}
```
### 9. Server-Sent Events (SSE)
**Scenario**: Push real-time updates to client
**Recommended**: API Route
**Reasoning**:
- Needs streaming response
- Custom headers (Content-Type: text/event-stream)
- Long-lived connection
- Standard SSE protocol
**Example**:
```typescript
// API Route (recommended)
export async function GET() {
const stream = new ReadableStream({
start(controller) {
const interval = setInterval(() => {
controller.enqueue(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
return () => clearInterval(interval);
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
```
### 10. Complex Authentication Flows
**Scenario**: Multi-step authentication with redirects
**Recommended**: API Route
**Reasoning**:
- Full control over response
- Custom headers and cookies
- Complex redirect logic
- Standard auth patterns
**Example**:
```typescript
// API Route (recommended)
export async function POST(request: Request) {
const { email, password } = await request.json();
const user = await authenticateUser(email, password);
if (!user) {
return Response.json({ error: 'Invalid credentials' }, { status: 401 });
}
const response = NextResponse.json({ user });
response.cookies.set('session', user.sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 days
});
return response;
}
```
## Performance Considerations
### Server Actions
**Pros**:
- Automatic request deduplication
- Built-in caching with React
- No additional route handler overhead
- Smaller client bundle (no fetch logic)
**Cons**:
- POST-only (cannot use GET with browser caching)
- No HTTP-level caching (no Cache-Control headers)
### API Routes
**Pros**:
- Full HTTP caching support (Cache-Control, ETag)
- GET requests can leverage browser cache
- Can use CDN caching
- Standard HTTP optimizations
**Cons**:
- Additional route handler overhead
- Requires client-side fetch logic
- No automatic deduplication
## Security Considerations
### Server Actions
**Pros**:
- Built-in CSRF protection
- No CORS issues (same-origin)
- Automatic request validation
- Type-safe by default
**Cons**:
- Cannot restrict by IP or API key
- Limited rate limiting options
### API Routes
**Pros**:
- Full control over authentication
- API key/token validation
- IP-based restrictions
- Custom rate limiting
- CORS configuration
**Cons**:
- Must implement CSRF protection manually
- More attack surface (public endpoints)
## Developer Experience
### Server Actions
**Pros**:
- Simpler code (no fetch boilerplate)
- Fully type-safe
- Better error handling with React
- Progressive enhancement
- Automatic form reset
**Cons**:
- Less familiar to backend developers
- Debugging can be harder (no network tab)
- Limited to Next.js ecosystem
### API Routes
**Pros**:
- Standard REST patterns
- Familiar to all developers
- Easy to test (curl, Postman)
- Visible in network tab
- Framework-agnostic clients
**Cons**:
- More boilerplate code
- Manual type safety
- Requires client-side error handling
- CSRF protection needed
## Migration Strategies
### API Route → Server Action
**When to migrate**:
- POST-only endpoint
- Used for form submissions
- Needs revalidation
- Only called from your app
**Steps**:
1. Create Server Action with same logic
2. Remove fetch call from client
3. Update form to use `action` prop
4. Add revalidation if needed
5. Remove API route
6. Test thoroughly
**Complexity**: Low to Medium
### Server Action → API Route
**When to migrate**:
- Need external client access
- Require GET/PUT/DELETE methods
- Need custom headers/cookies
- Webhook target
**Steps**:
1. Create API route with same logic
2. Add authentication/authorization
3. Remove revalidation (use cache instead)
4. Update clients to use fetch
5. Remove Server Action
6. Test with external clients
**Complexity**: Medium to High
## Worldbuilding App Examples
### Entity Management
| Operation | Recommended | Reason |
|-----------|------------|--------|
| Create entity form | Server Action | Form submission, revalidation |
| Update entity form | Server Action | Form submission, revalidation |
| Delete entity | Server Action | Simple mutation, revalidation |
| Get entity list | API Route (GET) | External dashboard, caching |
| Bulk import | Server Action | FormData, file upload |
| Export entities | API Route (GET) | Streaming, custom headers |
### Relationships
| Operation | Recommended | Reason |
|-----------|------------|--------|
| Add relationship | Server Action | Simple mutation, revalidation |
| Remove relationship | Server Action | Simple mutation, revalidation |
| Get relationship graph | API Route (GET) | Complex data, caching |
| Bulk relationship update | Server Action | Transaction, revalidation |
### Timeline Events
| Operation | Recommended | Reason |
|-----------|------------|--------|
| Create event | Server Action | Form submission, revalidation |
| Update event | Server Action | Form submission, revalidation |
| Timeline feed | API Route (GET) | Caching, pagination |
| Event search | API Route (GET) | Complex query, caching |
### Search and Filtering
| Operation | Recommended | Reason |
|-----------|------------|--------|
| Search within app | Server Action | With revalidation, type-safe |
| Public search API | API Route (GET) | External access, rate limiting |
| Save search filter | Server Action | Form submission, revalidation |
## Best Practices Summary
1. **Default to Server Actions for forms** - Simpler, more secure, better UX
2. **Use API Routes for external integration** - Webhooks, proxies, third-party APIs
3. **Consider HTTP methods** - Non-POST operations need API routes
4. **Think about clients** - External clients require API routes
5. **Evaluate caching needs** - Complex caching may favor API routes
6. **Prioritize type safety** - Server Actions provide better type safety
7. **Plan for scale** - API routes better for public APIs with rate limiting
8. **Progressive enhancement** - Forms with Server Actions work without JS
9. **Monitor performance** - Both have trade-offs, measure and optimize
10. **Be consistent** - Choose patterns and stick to them across your codebase