From fullstack-agents
Generate production-ready Next.js pages with SSR initial load, flexible client-side data management, and server-response-based updates.
npx claudepluginhub adelabdelgawad/fullstack-agents --plugin fullstack-agentsThis skill uses the workspace's default tool permissions.
Generate production-ready Next.js pages with SSR initial load, flexible client-side data management, and server-response-based updates.
examples.mdreferences/api-route-pattern.mdreferences/context-pattern.mdreferences/data-fetching-strategy.mdreferences/fetch-pattern.mdreferences/page-pattern.mdreferences/select-components.mdreferences/simple-fetching-pattern.mdreferences/swr-fetching-pattern.mdreferences/table-pattern.mdscripts/helper.pySearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Generate production-ready Next.js pages with SSR initial load, flexible client-side data management, and server-response-based updates.
Use this skill when asked to:
┌─────────────────────────────────────────────────────────────┐
│ Browser Request │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ page.tsx (Server Component) │
│ • auth() check │
│ • await searchParams (Next.js 15+) │
│ • Fetch initial data via server actions │
│ • Pass initialData to client component │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ *-table.tsx (Client Component) │
│ • useSWR with fallbackData: initialData │
│ • Context Provider for actions │
│ • updateItems() uses server response (NOT optimistic) │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ API Routes (app/api/...) │
│ • withAuth() wrapper │
│ • Proxy to FastAPI backend │
│ • backendFetch() direct calls to FastAPI backend │
└─────────────────────────────────────────────────────────────┘
app/(pages)/setting/{entity}/
├── page.tsx # SSR page (server component)
├── context/
│ └── {entity}-actions-context.tsx # Actions provider
└── _components/
├── actions/
│ ├── add-{entity}-button.tsx
│ └── {entity}-actions-menu.tsx
├── modal/
│ ├── add-{entity}-sheet.tsx
│ └── edit-{entity}-sheet.tsx
├── sidebar/
│ └── status-panel.tsx
└── table/
├── {entity}-table.tsx # Main client component
├── {entity}-table-body.tsx
└── {entity}-table-columns.tsx
app/api/setting/{entity}/
├── route.ts # GET (list), POST (create)
└── [{entityId}]/
├── route.ts # GET, PUT, DELETE
└── status/
└── route.ts # PUT (toggle status)
lib/
├── actions/
│ └── {entity}.actions.ts # Server actions
└── fetch/
├── client.ts # Client-side fetch (api.get/post/put/delete)
├── server.ts # Server action fetch (serverGet/Post/Put/Delete)
├── backend.ts # backendFetch() for API routes
└── api-route-helper.ts # withAuth() wrapper for API routes
lib/
├── types/
│ └── api/
│ └── {entity}.ts # TypeScript types
Choose the appropriate pattern based on requirements. See references/data-fetching-strategy.md for the decision framework.
Server Component (page.tsx) - Same for both strategies:
// NO "use client" - this is a server component
import { auth } from "@/lib/auth/server-auth";
import { getItems } from "@/lib/actions/items.actions";
import { redirect } from "next/navigation";
import ItemsTable from "./_components/table/items-table";
export default async function ItemsPage({
searchParams,
}: {
searchParams: Promise<{ page?: string; limit?: string; search?: string }>;
}) {
const session = await auth();
if (!session?.accessToken) redirect("/login");
const params = await searchParams; // Next.js 15+ requires await
const page = Number(params.page) || 1;
const limit = Number(params.limit) || 10;
const items = await getItems(limit, (page - 1) * limit, params);
return <ItemsTable initialData={items} />;
}
Strategy A: Simple Fetching (Default)
"use client";
import { useState } from "react";
import { api } from "@/lib/fetch/client";
function ItemsTable({ initialData }) {
const [data, setData] = useState(initialData);
// Update from server response
const updateItems = (serverResponse: Item[]) => {
setData(current => ({
...current,
items: current.items.map(item => {
const updated = serverResponse.find(i => i.id === item.id);
return updated ?? item;
}),
}));
};
// ...
}
See references/simple-fetching-pattern.md.
Strategy B: SWR Fetching (When Justified)
"use client";
import useSWR from "swr";
import { api } from "@/lib/fetch/client";
const fetcher = (url: string) => api.get(url);
function ItemsTable({ initialData }) {
const searchParams = useSearchParams();
const page = Number(searchParams?.get("page") || "1");
/**
* SWR JUSTIFICATION:
* - Reason: [Document why revalidation needed]
* - Trigger: [Interval / Focus / Manual]
*/
const { data, mutate, isLoading } = useSWR(
`/api/setting/items?page=${page}`,
fetcher,
{
fallbackData: initialData,
keepPreviousData: true,
revalidateOnMount: false,
revalidateOnFocus: false, // Set true only if justified
}
);
// ...
}
See references/swr-fetching-pattern.md.
// ✅ CORRECT: Use server response to update cache
const updateItems = async (serverResponse: Item[]) => {
const currentData = data;
if (!currentData) return;
const responseMap = new Map(serverResponse.map(i => [i.id, i]));
const updatedList = currentData.items.map(item =>
responseMap.has(item.id) ? responseMap.get(item.id)! : item
);
await mutate(
{ ...currentData, items: updatedList },
{ revalidate: false }
);
};
// In action handler:
const updatedItem = await api.put<Item>(`/setting/items/${id}`, payload);
await updateItems([updatedItem]); // Use server response!
// ❌ WRONG: Optimistic update
const updatedList = currentData.items.map(item =>
item.id === id ? { ...item, ...localChanges } : item // Don't do this!
);
// context/{entity}-actions-context.tsx
"use client";
import { createContext, useContext, ReactNode } from "react";
interface ActionsContextType {
onToggleStatus: (id: string, isActive: boolean) => Promise<ActionResult>;
onUpdate: (id: string, data: UpdateData) => Promise<ActionResult>;
updateItems: (items: Item[]) => Promise<void>;
onRefresh: () => Promise<void>;
}
const ActionsContext = createContext<ActionsContextType | null>(null);
export function ActionsProvider({ children, actions }: Props) {
return (
<ActionsContext.Provider value={actions}>
{children}
</ActionsContext.Provider>
);
}
export function useTableActions() {
const context = useContext(ActionsContext);
if (!context) {
throw new Error("useTableActions must be used within ActionsProvider");
}
return context;
}
// app/api/setting/items/route.ts
import { NextRequest } from "next/server";
import { withAuth } from "@/lib/fetch/api-route-helper";
import { backendFetch } from "@/lib/fetch/backend";
export async function GET(request: NextRequest) {
const params = request.nextUrl.searchParams.toString();
return withAuth((token) => backendFetch(`/setting/items/?${params}`, token));
}
export async function POST(request: NextRequest) {
const body = await request.json();
return withAuth((token, headers) =>
backendFetch('/setting/items/', token, { method: 'POST', body, headers })
);
}
When creating a new entity page, generate files in this order:
useState + server response updatesuseSWR with documented justificationfallbackData - SSR data for instant renderkeepPreviousData: true - Smooth paginationrevalidateOnMount: false - Trust SSR datarevalidateOnFocus: false - Set true only if justifiedlib/fetch/client.ts - Client-side API calls (api.get/post/put/delete, auto-prefixes /api/)lib/fetch/server.ts - Server action fetch (serverGet/Post/Put/Delete with /backend/... URLs)lib/fetch/backend.ts - backendFetch() for API route handlerslib/fetch/api-route-helper.ts - withAuth() wrapper for API routesSee the references/ directory for detailed patterns:
data-fetching-strategy.md - Decision framework for choosing strategysimple-fetching-pattern.md - Strategy A implementationswr-fetching-pattern.md - Strategy B implementationpage-pattern.md - SSR page structuretable-pattern.md - Table component patternscontext-pattern.md - Actions contextapi-route-pattern.md - API routesfetch-pattern.md - Fetch utilitiesselect-components.md - SingleSelect and MultiSelect component source and usage