Generate production-ready Next.js pages with SSR initial load, SWR client-side data management, and server-response-based cache updates.
Generates production-ready Next.js pages with SSR, SWR data management, and server-response-based cache updates.
/plugin marketplace add adelabdelgawad/fullstack-agents/plugin install adelabdelgawad-fullstack-agents-plugins-fullstack-agents@adelabdelgawad/fullstack-agentsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples.mdreferences/api-route-pattern.mdreferences/context-pattern.mdreferences/fetch-pattern.mdreferences/page-pattern.mdreferences/table-pattern.mdscripts/helper.pyGenerate production-ready Next.js pages with SSR initial load, SWR client-side data management, and server-response-based cache 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 │
│ • backendGet/Post/Put/Delete helpers │
└─────────────────────────────────────────────────────────────┘
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
├── server.ts # Server-side fetch
└── api-route-helper.ts # API route helpers
types/
└── {entity}.d.ts # TypeScript types
Server Component (page.tsx):
// 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} />;
}
Client Component (items-table.tsx):
"use client";
import useSWR from "swr";
import { fetchClient } from "@/lib/fetch/client";
const fetcher = (url: string) => fetchClient.get(url).then(r => r.data);
function ItemsTable({ initialData }) {
const searchParams = useSearchParams();
const page = Number(searchParams?.get("page") || "1");
const { data, mutate, isLoading } = useSWR(
`/api/setting/items?page=${page}`,
fetcher,
{
fallbackData: initialData, // SSR data as initial cache
keepPreviousData: true, // Smooth transitions
revalidateOnMount: false, // Don't refetch on mount
revalidateOnFocus: false, // Don't refetch on focus
}
);
// ...
}
// ✅ 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 { data: updatedItem } = await fetchClient.put(`/api/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, backendGet, backendPost } from "@/lib/fetch/api-route-helper";
export async function GET(request: NextRequest) {
const params = request.nextUrl.searchParams.toString();
return withAuth(token => backendGet(`/setting/items/?${params}`, token));
}
export async function POST(request: NextRequest) {
const body = await request.json();
return withAuth(token => backendPost('/setting/items/', token, body));
}
When creating a new entity page, generate files in this order:
fallbackData - SSR data for instant renderkeepPreviousData: true - Smooth paginationrevalidateOnMount: false - Trust SSR datarevalidateOnFocus: false - Reduce API callslib/fetch/client.ts - Client-side API callslib/fetch/server.ts - Server action API callslib/fetch/api-route-helper.ts - API route wrappersSee the references/ directory for detailed patterns:
page-pattern.md - SSR page structuretable-pattern.md - SWR table componentcontext-pattern.md - Actions contextapi-route-pattern.md - API routesfetch-pattern.md - Fetch utilitiesThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.