一键导入
add-api-route
Create Next.js API routes for backend proxies, server-side aggregation, or third-party API integrations with caching and error handling.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create Next.js API routes for backend proxies, server-side aggregation, or third-party API integrations with caching and error handling.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Scaffold a new dashboard chart component with registry, types, and proper theme integration.
Create a new customizable dashboard with its own chart registry, provider, and page. Use when adding dashboards like DRep or SPO dashboard.
Context window conservation rules. Invoke when approaching context limits or before large tasks.
Deep reflection on the skill learning system itself. Analyzes what's working, what's stale, and proposes structural improvements. The meta-skill.
End-of-session automation. Creates a journey and evolves skills based on session learnings.
Run the build and intelligently fix TypeScript errors with guardrails. Stops if fixes introduce more errors or the same error persists after 3 attempts.
| name | add-api-route |
| description | Create Next.js API routes for backend proxies, server-side aggregation, or third-party API integrations with caching and error handling. |
Create a new Next.js API route that proxies to the backend with proper authentication and error handling.
$0 - Route path relative to pages/api (e.g., voters/stats creates pages/api/voters/stats.ts)$1 - Backend endpoint path (e.g., /api/voters/statistics)Create file at src/pages/api/${$0}.ts (or src/pages/api/${$0}/index.ts for directory routes):
import type { NextApiRequest, NextApiResponse } from "next";
import { callApi } from "@/utils/apiHelper";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
try {
const response = await callApi({
endpoint: "${$1}",
method: "GET",
});
const data = await response.json();
res.setHeader("Cache-Control", "public, s-maxage=60, stale-while-revalidate=300");
return res.status(response.status).json(data);
} catch (error) {
console.error("${$0} API error:", error);
return res.status(500).json({ error: "Failed to fetch data" });
}
}
If the route has parameters (e.g., voters/[id].ts):
import type { NextApiRequest, NextApiResponse } from "next";
import { callApi } from "@/utils/apiHelper";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { id } = req.query;
if (!id || typeof id !== "string") {
return res.status(400).json({ error: "Invalid ID parameter" });
}
try {
const response = await callApi({
endpoint: `/api/voters/${id}`,
method: "GET",
});
const data = await response.json();
res.setHeader("Cache-Control", "public, s-maxage=60, stale-while-revalidate=300");
return res.status(response.status).json(data);
} catch (error) {
console.error("Voter detail API error:", error);
return res.status(500).json({ error: "Failed to fetch voter data" });
}
}
If this data will be used by components, add a service function in src/services/api.ts:
export async function fetch${PascalCaseName}(): Promise<${TypeName}> {
return fetchApi<${TypeName}>("/api/${$0}");
}
Adjust s-maxage based on data freshness needs:
stale-while-revalidate=86400| Route | Creates |
|---|---|
voters | src/pages/api/voters.ts |
voters/stats | src/pages/api/voters/stats.ts |
voters/[id] | src/pages/api/voters/[id].ts |
proposals/[hash]/votes | src/pages/api/proposals/[hash]/votes.ts |
curl http://localhost:3000/api/${$0}When a frontend chart needs data from N+1 API calls (list all items, then fetch detail for each), move the aggregation server-side. This prevents hundreds of client-side calls and lets you cache the result for all users.
When to use: Any chart that would otherwise make O(N) detail fetches from the browser.
import type { NextApiRequest, NextApiResponse } from "next";
import { callApi } from "@/utils/apiHelper";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "GET") return res.status(405).json({ error: "Method not allowed" });
try {
// 1. Fetch all list pages
const pageSize = 100;
const firstRes = await callApi({ endpoint: `/items?page=1&pageSize=${pageSize}`, method: "GET" });
const firstData = await firstRes.json();
const allItems = [...firstData.items];
if (firstData.pagination.totalPages > 1) {
const remaining = Array.from({ length: firstData.pagination.totalPages - 1 }, (_, i) => i + 2);
const pages = await Promise.all(
remaining.map(async (pg) => {
const r = await callApi({ endpoint: `/items?page=${pg}&pageSize=${pageSize}`, method: "GET" });
return (await r.json()).items;
})
);
for (const page of pages) allItems.push(...page);
}
// 2. Fetch details in parallel batches (prevent overwhelming backend)
const batchSize = 20;
const results = [];
for (let i = 0; i < allItems.length; i += batchSize) {
const batch = allItems.slice(i, i + batchSize);
const details = await Promise.all(
batch.map(async (item) => {
try {
const r = await callApi({ endpoint: `/items/${item.id}`, method: "GET" });
const d = await r.json();
return { id: item.id, /* extract needed fields */ };
} catch {
return { id: item.id, /* fallback values */ };
}
})
);
results.push(...details);
}
// 3. Cache aggressively — this is an expensive endpoint
res.setHeader("Cache-Control", "public, s-maxage=300, stale-while-revalidate=600");
return res.status(200).json({ items: results });
} catch (error) {
console.error("Aggregation API error:", error);
return res.status(500).json({ error: "Failed to aggregate data" });
}
}
// In hooks file
export function useAggregatedData() {
const { data, error, isLoading, mutate } = useSWR(
"/api/items/aggregated", fetcher,
{ revalidateOnFocus: false, dedupingInterval: 300000 } // match server cache
);
return { items: data?.items || [], isLoading, error: error?.message || null, refresh: () => mutate() };
}
| Data Type | s-maxage | stale-while-revalidate |
|---|---|---|
| Expensive aggregation (100+ backend calls) | 300s | 600s |
| Moderate aggregation (10-50 calls) | 120s | 300s |
| Simple proxy | 60s | 300s |
For routes that call external APIs (not the backend), use this pattern with server-side caching:
import type { NextApiRequest, NextApiResponse } from "next";
const EXTERNAL_API_URL = "https://api.example.com/v1/endpoint";
// Server-side cache (shared across all users on same instance)
const serverCache = new Map<string, string>();
// Track in-flight requests to prevent duplicate API calls
const inFlightRequests = new Map<string, Promise<string>>();
// Simple hash for cache keys
function hashKey(input: string): string {
let hash = 0;
for (let i = 0; i < input.length; i++) {
hash = ((hash << 5) - hash) + input.charCodeAt(i);
hash = hash & hash;
}
return String(Math.abs(hash));
}
// Limit cache size to prevent memory issues
function pruneCache() {
if (serverCache.size > 1000) {
const keysToDelete = Array.from(serverCache.keys()).slice(0, 200);
keysToDelete.forEach(key => serverCache.delete(key));
}
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const apiKey = process.env.EXTERNAL_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: "Service not configured" });
}
const { input } = req.body;
const cacheKey = hashKey(input);
// Check server cache first
const cached = serverCache.get(cacheKey);
if (cached) {
return res.status(200).json({ result: cached, cached: true });
}
// Check if there's already an in-flight request for this input
const inFlight = inFlightRequests.get(cacheKey);
if (inFlight) {
try {
const result = await inFlight;
return res.status(200).json({ result, cached: true });
} catch {
return res.status(500).json({ error: "Request failed" });
}
}
// Create API call promise and track it
const apiPromise = callExternalAPI(input, apiKey);
inFlightRequests.set(cacheKey, apiPromise);
try {
const result = await apiPromise;
// Cache the result
serverCache.set(cacheKey, result);
pruneCache();
// Clean up in-flight tracker
inFlightRequests.delete(cacheKey);
return res.status(200).json({ result });
} catch (error) {
inFlightRequests.delete(cacheKey);
console.error("External API error:", error);
return res.status(500).json({ error: "Service error" });
}
}
async function callExternalAPI(input: string, apiKey: string): Promise<string> {
const response = await fetch(EXTERNAL_API_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input }),
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
return data.result;
}
| Feature | Benefit |
|---|---|
| Server-side cache | All users share same cache (1 API call per unique input) |
| In-flight deduplication | Parallel requests for same input share one API call |
| Cache pruning | Prevents memory issues on long-running servers |
| Env var for API key | Keeps secrets server-side |