Loading...
Loading...
Use when adding 'use client' directives, splitting mixed components into server + client parts, or fixing RSC boundary errors like "useState only works in a Client Component." Also for migrating interactive UI (forms, modals, event handlers) to App Router.
npx skill4agent add blazity/next-migration-skills component-migration'use client'nextjs-migration-toolkitTOOLKIT_DIR="$(cd "$(dirname "$SKILL_PATH")/../nextjs-migration-toolkit" && pwd)"
if [ ! -f "$TOOLKIT_DIR/package.json" ]; then
echo "ERROR: nextjs-migration-toolkit is not installed." >&2
echo "Run: npx skills add blazity/next-migration-skills -s nextjs-migration-toolkit" >&2
echo "Then retry this skill." >&2
exit 1
fi
bash "$TOOLKIT_DIR/scripts/setup.sh" >/dev/null.migration/target-version.txtSKILL_DIR="$(cd "$(dirname "$SKILL_PATH")" && pwd)"
cat "$SKILL_DIR/../version-patterns/nextjs-<version>.md"paramssearchParamsparamssearchParamsawaituse()npx tsx "$TOOLKIT_DIR/src/bin/ast-tool.ts" analyze components <srcDir>'use client';import { useState } from 'react';
import { useRouter } from 'next/router';
export default function ProductPage({ product }) {
const router = useRouter();
const [qty, setQty] = useState(1);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<input type="number" value={qty} onChange={(e) => setQty(Number(e.target.value))} />
<button onClick={() => alert(`Added ${qty}`)}>Add to Cart</button>
<button onClick={() => router.back()}>Back</button>
</div>
);
}app/products/[id]/page.tsximport { Metadata } from 'next';
import { ProductActions } from './product-actions';
export async function generateMetadata({ params }): Promise<Metadata> {
const product = await fetch(`https://api.example.com/products/${params.id}`,
{ cache: 'no-store' }).then(r => r.json());
return { title: product.name };
}
export default async function ProductPage({ params }) {
const product = await fetch(`https://api.example.com/products/${params.id}`,
{ cache: 'no-store' }).then(r => r.json());
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<ProductActions />
</div>
);
}app/products/[id]/product-actions.tsx'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export function ProductActions() {
const router = useRouter();
const [qty, setQty] = useState(1);
return (
<>
<input type="number" value={qty} onChange={(e) => setQty(Number(e.target.value))} />
<button onClick={() => alert(`Added ${qty}`)}>Add to Cart</button>
<button onClick={() => router.back()}>Back</button>
</>
);
}useRouternext/routernext/navigationnpx tsx "$TOOLKIT_DIR/src/bin/ast-tool.ts" analyze props <componentFile>npx tsx "$TOOLKIT_DIR/src/bin/ast-tool.ts" validate <appDir>'use client'next/routernext/navigationIt only works in a Client Component but none of its parents are marked with "use client"'use client''use client''use client''use client''use client'Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server"'use server'// layout.tsx (server component)
import { ClientShell } from './client-shell';
import { ServerContent } from './server-content';
export default function Layout() {
return (
<ClientShell>
<ServerContent /> {/* Server component passed as children */}
</ClientShell>
);
}childrengetServerSideProps'use client'useEffectuseState// WRONG — "use client" page with useEffect
'use client';
import { useState, useEffect } from 'react';
export default function AnalyticsPage() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/analytics').then(r => r.json()).then(setData);
}, []);
if (!data) return <p>Loading...</p>;
return <div>{data.pageViews}</div>;
}// app/analytics/page.tsx (server component — NO 'use client')
import { getAnalyticsData } from '@/lib/analytics';
import { AnalyticsView } from './analytics-view';
export default async function AnalyticsPage() {
const data = getAnalyticsData();
return <AnalyticsView data={data} />;
}// app/analytics/analytics-view.tsx (client component)
'use client';
import { useState } from 'react';
export function AnalyticsView({ data }: { data: AnalyticsData }) {
const [view, setView] = useState<'overview' | 'pages'>('overview');
return (
<div>
<button onClick={() => setView('overview')}>Overview</button>
<button onClick={() => setView('pages')}>Top Pages</button>
{view === 'overview' ? <p>{data.pageViews}</p> : <p>...</p>}
</div>
);
}getServerSidePropsgetStaticPropsuseEffect