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,281 @@
---
name: server-actions-vs-api-optimizer
description: Analyze routes and recommend whether to use Server Actions or API routes based on use case patterns including authentication, revalidation, external API calls, and client requirements. Use this skill when deciding between Server Actions and API routes, optimizing Next.js data fetching, refactoring routes, analyzing route architecture, or choosing the right data mutation pattern. Trigger terms include Server Actions, API routes, route handler, data mutation, revalidation, authentication flow, external API, client-side fetch, route optimization, Next.js patterns.
---
# Server Actions vs API Routes Optimizer
Analyze existing routes and recommend whether to use Server Actions or traditional API routes based on specific use case patterns, including authentication flows, data revalidation, external API calls, and client requirements.
## Core Capabilities
### 1. Analyze Existing Routes
To analyze current route architecture:
- Scan app directory for route handlers and Server Actions
- Identify patterns in request/response handling
- Detect authentication, revalidation, and external API usage
- Use `scripts/analyze_routes.py` for automated analysis
### 2. Provide Recommendations
Based on analysis, provide recommendations using the decision matrix from `references/decision_matrix.md`:
- **Server Actions**: Form submissions, mutations with revalidation, simple data updates
- **API Routes**: External API proxying, webhooks, third-party integrations, non-form mutations
- **Hybrid Approach**: Complex flows requiring both patterns
### 3. Generate Migration Plans
When refactoring is recommended:
- Identify specific routes to convert
- Provide step-by-step migration instructions
- Show before/after code examples
- Highlight breaking changes and client updates needed
## When to Use Server Actions
Prefer Server Actions for:
1. **Form Submissions**: Direct form action handling with progressive enhancement
2. **Data Mutations with Revalidation**: Operations that need `revalidatePath()` or `revalidateTag()`
3. **Simple CRUD Operations**: Direct database mutations from components
4. **Authentication in RSC**: Auth checks in Server Components
5. **File Uploads**: Handling FormData directly
6. **Optimistic Updates**: Client-side optimistic UI with server validation
**Benefits**:
- Automatic POST request handling
- Built-in CSRF protection
- Type-safe with TypeScript
- Progressive enhancement (works without JS)
- Direct access to server-side resources
- Simpler code for common patterns
## When to Use API Routes
Prefer API routes for:
1. **External API Proxying**: Hiding API keys, rate limiting, response transformation
2. **Webhooks**: Third-party service callbacks (Stripe, GitHub, etc.)
3. **Non-POST Operations**: GET, PUT, DELETE, PATCH endpoints
4. **Third-Party Integrations**: OAuth callbacks, external service authentication
5. **Public APIs**: Endpoints called by external clients
6. **Complex Response Headers**: Custom headers, cookies, redirects
7. **Non-Form Client Requests**: fetch() calls from client components
8. **SSE/Streaming**: Server-sent events or custom streaming
**Benefits**:
- Full HTTP method support
- Custom response handling
- External accessibility
- Middleware support
- Standard REST API patterns
## Analysis Workflow
### 1. Run Automated Analysis
Use the analysis script to scan your codebase:
```bash
python scripts/analyze_routes.py --path /path/to/app --output analysis-report.md
```
The script identifies:
- Existing API routes and their patterns
- Server Actions usage
- Authentication patterns
- Revalidation calls
- External API integrations
- Potential optimization opportunities
### 2. Review Decision Matrix
Consult `references/decision_matrix.md` for detailed decision criteria:
- Use case patterns
- Trade-offs analysis
- Performance considerations
- Security implications
- Developer experience factors
### 3. Generate Recommendations
For each route, determine:
- Current implementation pattern
- Recommended pattern (Server Action or API route)
- Reasoning based on use case
- Migration complexity (low/medium/high)
- Potential benefits of refactoring
### 4. Create Migration Plan
For routes requiring changes:
- Prioritize high-impact, low-complexity migrations
- Document breaking changes
- Provide code transformation examples
- Update client-side code if needed
## Common Patterns and Recommendations
### Pattern: Form Submission with DB Update
**Current**: API route with fetch from client
**Recommended**: Server Action
**Reason**: Simpler, built-in CSRF protection, progressive enhancement
```typescript
// Before (API Route)
// app/api/entities/route.ts
export async function POST(request: Request) {
const data = await request.json();
await db.entity.create(data);
return Response.json({ success: true });
}
// After (Server Action)
// app/actions.ts
'use server';
export async function createEntity(formData: FormData) {
await db.entity.create(Object.fromEntries(formData));
revalidatePath('/entities');
}
```
### Pattern: External API Proxy
**Current**: Client-side fetch to external API (exposes keys)
**Recommended**: API route
**Reason**: Hide API keys, rate limiting, response transformation
```typescript
// Recommended: API Route
// app/api/external-service/route.ts
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}`);
const data = await response.json();
return Response.json(data);
}
```
### Pattern: Webhook Handler
**Current**: None (new feature)
**Recommended**: API route
**Reason**: External service calls, needs public URL
```typescript
// Recommended: API Route
// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
const signature = request.headers.get('stripe-signature');
const body = await request.text();
// Verify webhook signature
// Process webhook event
return Response.json({ received: true });
}
```
### Pattern: Data Mutation with Revalidation
**Current**: API route with manual cache invalidation
**Recommended**: Server Action
**Reason**: Built-in revalidation, simpler code
```typescript
// Before (API Route)
// app/api/entities/[id]/route.ts
export async function PATCH(request: Request, { params }) {
const data = await request.json();
await db.entity.update({ where: { id: params.id }, data });
revalidatePath('/entities');
return Response.json({ success: true });
}
// After (Server Action)
// app/actions.ts
'use server';
export async function updateEntity(id: string, data: any) {
await db.entity.update({ where: { id }, data });
revalidatePath('/entities');
revalidateTag(`entity-${id}`);
}
```
### Pattern: Authentication Check
**Current**: API middleware
**Recommended**: Server Action for mutations, API route for public endpoints
**Reason**: Simpler auth in Server Components
```typescript
// Server Action (for mutations)
'use server';
export async function protectedAction() {
const session = await auth();
if (!session) throw new Error('Unauthorized');
// Perform action
}
// API Route (for public/external access)
export async function POST(request: Request) {
const token = request.headers.get('authorization');
if (!validateToken(token)) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// Process request
}
```
## Resource Files
### scripts/analyze_routes.py
Automated route analysis tool that scans your Next.js app directory to identify route patterns, Server Actions, authentication usage, revalidation calls, and external API integrations. Generates a detailed report with recommendations.
### references/decision_matrix.md
Comprehensive decision matrix with detailed criteria for choosing between Server Actions and API routes. Includes use case patterns, trade-offs, performance considerations, security implications, and real-world examples.
## Best Practices
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 Progressive Enhancement**: Server Actions work without JavaScript
4. **Optimize for Revalidation**: Server Actions have built-in revalidation
5. **Evaluate Client Requirements**: If external clients need access, use API routes
6. **Think About Method Requirements**: Non-POST operations need API routes
7. **Consider Type Safety**: Server Actions are fully type-safe
8. **Plan for Migration**: Start with new features, gradually refactor existing
## Integration with Worldbuilding App
Common patterns in worldbuilding applications:
### Entity CRUD Operations
- **Create/Update/Delete entities**: Server Actions (form submissions with revalidation)
- **Get entity list for external dashboard**: API route (external client access)
### Relationship Management
- **Add/remove relationships**: Server Actions (mutations with revalidation)
- **Export relationship graph**: API route (complex response, streaming)
### Timeline Operations
- **Create/edit timeline events**: Server Actions (form submissions)
- **Timeline data feed for visualization**: API route (GET requests, caching)
### Search and Filtering
- **Filter entities in app**: Server Actions (with revalidation)
- **Public search API**: API route (external access, rate limiting)
### Import/Export
- **Import data files**: Server Action (FormData handling)
- **Export to external format**: API route (custom headers, streaming)
Consult `references/decision_matrix.md` for detailed analysis of each pattern.

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

View File

@@ -0,0 +1,464 @@
#!/usr/bin/env python3
"""
Analyze Next.js routes and Server Actions to provide optimization recommendations.
Usage:
python analyze_routes.py --path /path/to/app --output analysis-report.md
python analyze_routes.py --path ./app --format json
"""
import os
import re
import json
import argparse
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple
from dataclasses import dataclass, asdict
@dataclass
class RouteInfo:
"""Information about a route or Server Action."""
path: str
type: str # 'api_route' or 'server_action'
methods: List[str] # HTTP methods for API routes
has_auth: bool
has_revalidation: bool
has_external_api: bool
has_form_data: bool
has_streaming: bool
has_cookies: bool
has_headers: bool
recommendation: str
reason: str
migration_complexity: str # 'low', 'medium', 'high'
class RouteAnalyzer:
"""Analyze Next.js routes and Server Actions."""
def __init__(self, app_path: str):
self.app_path = Path(app_path)
self.routes: List[RouteInfo] = []
def analyze(self) -> List[RouteInfo]:
"""Analyze all routes in the app directory."""
# Find all route.ts/js files (API routes)
for route_file in self.app_path.rglob('route.*'):
if route_file.suffix in ['.ts', '.tsx', '.js', '.jsx']:
self._analyze_api_route(route_file)
# Find Server Actions (files with 'use server')
for file in self.app_path.rglob('*.*'):
if file.suffix in ['.ts', '.tsx', '.js', '.jsx']:
if self._has_server_directive(file):
self._analyze_server_actions(file)
return self.routes
def _has_server_directive(self, file_path: Path) -> bool:
"""Check if file has 'use server' directive."""
try:
content = file_path.read_text(encoding='utf-8')
return "'use server'" in content or '"use server"' in content
except:
return False
def _analyze_api_route(self, file_path: Path):
"""Analyze an API route file."""
try:
content = file_path.read_text(encoding='utf-8')
except:
return
# Detect HTTP methods
methods = []
for method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
if re.search(rf'export\s+async\s+function\s+{method}', content):
methods.append(method)
if not methods:
return
# Analyze patterns
has_auth = self._detect_auth(content)
has_revalidation = self._detect_revalidation(content)
has_external_api = self._detect_external_api(content)
has_form_data = self._detect_form_data(content)
has_streaming = self._detect_streaming(content)
has_cookies = self._detect_cookies(content)
has_headers = self._detect_custom_headers(content)
# Generate recommendation
recommendation, reason, complexity = self._recommend_for_api_route(
methods=methods,
has_auth=has_auth,
has_revalidation=has_revalidation,
has_external_api=has_external_api,
has_form_data=has_form_data,
has_streaming=has_streaming,
has_cookies=has_cookies,
has_headers=has_headers,
)
route_info = RouteInfo(
path=str(file_path.relative_to(self.app_path)),
type='api_route',
methods=methods,
has_auth=has_auth,
has_revalidation=has_revalidation,
has_external_api=has_external_api,
has_form_data=has_form_data,
has_streaming=has_streaming,
has_cookies=has_cookies,
has_headers=has_headers,
recommendation=recommendation,
reason=reason,
migration_complexity=complexity,
)
self.routes.append(route_info)
def _analyze_server_actions(self, file_path: Path):
"""Analyze Server Actions in a file."""
try:
content = file_path.read_text(encoding='utf-8')
except:
return
# Find exported async functions
function_pattern = re.compile(
r'export\s+(?:async\s+)?function\s+(\w+)',
re.MULTILINE
)
for match in function_pattern.finditer(content):
function_name = match.group(1)
# Try to extract function body (basic approach)
start = match.start()
# Find the opening brace
brace_pos = content.find('{', start)
if brace_pos == -1:
continue
# Simple brace matching (not perfect but works for most cases)
brace_count = 1
pos = brace_pos + 1
while pos < len(content) and brace_count > 0:
if content[pos] == '{':
brace_count += 1
elif content[pos] == '}':
brace_count -= 1
pos += 1
function_body = content[brace_pos:pos]
# Analyze patterns in function body
has_auth = self._detect_auth(function_body)
has_revalidation = self._detect_revalidation(function_body)
has_external_api = self._detect_external_api(function_body)
has_form_data = 'FormData' in function_body
# Generate recommendation
recommendation, reason, complexity = self._recommend_for_server_action(
function_name=function_name,
has_auth=has_auth,
has_revalidation=has_revalidation,
has_external_api=has_external_api,
has_form_data=has_form_data,
)
route_info = RouteInfo(
path=f"{file_path.relative_to(self.app_path)}::{function_name}",
type='server_action',
methods=['POST'], # Server Actions are POST-only
has_auth=has_auth,
has_revalidation=has_revalidation,
has_external_api=has_external_api,
has_form_data=has_form_data,
has_streaming=False,
has_cookies=False,
has_headers=False,
recommendation=recommendation,
reason=reason,
migration_complexity=complexity,
)
self.routes.append(route_info)
def _detect_auth(self, content: str) -> bool:
"""Detect authentication patterns."""
patterns = [
r'auth\(',
r'getServerSession',
r'getSession',
r'session',
r'currentUser',
r'verifyToken',
r'authorization',
r'Authorization',
]
return any(re.search(pattern, content, re.IGNORECASE) for pattern in patterns)
def _detect_revalidation(self, content: str) -> bool:
"""Detect revalidation calls."""
return 'revalidatePath' in content or 'revalidateTag' in content
def _detect_external_api(self, content: str) -> bool:
"""Detect external API calls."""
patterns = [
r'fetch\([\'"]https?://',
r'axios\.',
r'\.get\([\'"]https?://',
r'\.post\([\'"]https?://',
]
return any(re.search(pattern, content) for pattern in patterns)
def _detect_form_data(self, content: str) -> bool:
"""Detect FormData handling."""
return 'FormData' in content or 'formData' in content
def _detect_streaming(self, content: str) -> bool:
"""Detect streaming responses."""
return 'ReadableStream' in content or 'TransformStream' in content or 'stream' in content.lower()
def _detect_cookies(self, content: str) -> bool:
"""Detect cookie operations."""
return 'cookies()' in content or 'setCookie' in content
def _detect_custom_headers(self, content: str) -> bool:
"""Detect custom header operations."""
patterns = [
r'headers\(\)',
r'\.headers\.',
r'setHeader',
r'Response\.json\([^,]+,\s*\{[^}]*headers',
]
return any(re.search(pattern, content) for pattern in patterns)
def _recommend_for_api_route(
self,
methods: List[str],
has_auth: bool,
has_revalidation: bool,
has_external_api: bool,
has_form_data: bool,
has_streaming: bool,
has_cookies: bool,
has_headers: bool,
) -> Tuple[str, str, str]:
"""Generate recommendation for an API route."""
reasons = []
# Strong indicators to keep as API route
if has_external_api:
reasons.append("proxies external API")
if has_streaming:
reasons.append("uses streaming responses")
if has_custom_headers or has_cookies:
reasons.append("requires custom headers/cookies")
if 'GET' in methods or len(methods) > 1:
reasons.append(f"uses multiple HTTP methods ({', '.join(methods)})")
# Strong indicators to convert to Server Action
if methods == ['POST'] and has_revalidation and not has_external_api:
if has_form_data:
return (
'Convert to Server Action',
'Simple POST with form data and revalidation - ideal for Server Action',
'low'
)
else:
return (
'Convert to Server Action',
'POST-only mutation with revalidation - better as Server Action',
'low'
)
# Keep as API route
if reasons:
return (
'Keep as API Route',
'Better as API route: ' + ', '.join(reasons),
'n/a'
)
# Neutral case - could go either way
if methods == ['POST']:
return (
'Consider Server Action',
'Simple POST endpoint could be simplified as Server Action',
'low'
)
return (
'Keep as API Route',
'Current implementation is appropriate',
'n/a'
)
def _recommend_for_server_action(
self,
function_name: str,
has_auth: bool,
has_revalidation: bool,
has_external_api: bool,
has_form_data: bool,
) -> Tuple[str, str, str]:
"""Generate recommendation for a Server Action."""
# Check if Server Action might be better as API route
if has_external_api and not has_revalidation:
return (
'Consider API Route',
'External API calls without revalidation might be better as API route for reusability',
'medium'
)
# Good use of Server Action
if has_revalidation or has_form_data:
return (
'Keep as Server Action',
'Good use of Server Action - leverages revalidation and/or form handling',
'n/a'
)
# Neutral
return (
'Keep as Server Action',
'Current implementation is appropriate',
'n/a'
)
def generate_report(self, format: str = 'markdown') -> str:
"""Generate analysis report."""
if format == 'json':
return json.dumps([asdict(route) for route in self.routes], indent=2)
# Markdown report
lines = [
'# Next.js Route Analysis Report',
'',
f'Analyzed {len(self.routes)} routes/actions',
'',
]
# Summary statistics
api_routes = [r for r in self.routes if r.type == 'api_route']
server_actions = [r for r in self.routes if r.type == 'server_action']
needs_migration = [r for r in self.routes if 'Convert' in r.recommendation or 'Consider' in r.recommendation]
lines.extend([
'## Summary',
'',
f'- **API Routes**: {len(api_routes)}',
f'- **Server Actions**: {len(server_actions)}',
f'- **Recommended Migrations**: {len(needs_migration)}',
'',
])
# Recommendations for migration
if needs_migration:
lines.extend([
'## Recommended Migrations',
'',
])
for route in needs_migration:
lines.extend([
f'### {route.path}',
'',
f'**Current**: {route.type.replace("_", " ").title()}',
f'**Recommendation**: {route.recommendation}',
f'**Reason**: {route.reason}',
f'**Complexity**: {route.migration_complexity}',
'',
])
# Detailed analysis
lines.extend([
'## Detailed Analysis',
'',
])
for route in self.routes:
lines.extend([
f'### {route.path}',
'',
f'- **Type**: {route.type.replace("_", " ").title()}',
f'- **Methods**: {", ".join(route.methods)}',
f'- **Authentication**: {"Yes" if route.has_auth else "No"}',
f'- **Revalidation**: {"Yes" if route.has_revalidation else "No"}',
f'- **External API**: {"Yes" if route.has_external_api else "No"}',
f'- **Form Data**: {"Yes" if route.has_form_data else "No"}',
])
if route.type == 'api_route':
lines.extend([
f'- **Streaming**: {"Yes" if route.has_streaming else "No"}',
f'- **Custom Headers/Cookies**: {"Yes" if (route.has_cookies or route.has_headers) else "No"}',
])
lines.extend([
'',
f'**Recommendation**: {route.recommendation}',
f'**Reason**: {route.reason}',
'',
])
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(
description='Analyze Next.js routes and provide optimization recommendations'
)
parser.add_argument(
'--path',
required=True,
help='Path to the Next.js app directory'
)
parser.add_argument(
'--output',
help='Output file path for the report'
)
parser.add_argument(
'--format',
choices=['markdown', 'json'],
default='markdown',
help='Output format (default: markdown)'
)
args = parser.parse_args()
# Validate path
app_path = Path(args.path)
if not app_path.exists():
print(f"Error: Path does not exist: {args.path}")
return 1
# Analyze routes
analyzer = RouteAnalyzer(args.path)
routes = analyzer.analyze()
if not routes:
print("No routes or Server Actions found.")
return 0
# Generate report
report = analyzer.generate_report(format=args.format)
# Write output
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(report, encoding='utf-8')
print(f"Report written to: {args.output}")
else:
print(report)
return 0
if __name__ == '__main__':
exit(main())