Next.js App Router — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Next.js App Router (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Default to Server Components; reach for Client Components only for interactivity, state, or browser APIs. The two costly mistakes this skill prevents are the accidental client tree - one 'use client' placed high that drags the whole page into the browser bundle - and accidental caching, where fetch's default caching silently serves stale data and the team "fixes" it by sprinkling force-dynamic everywhere, losing all caching.
Collect before writing a route (label guesses as guesses):
'use client' only at the smallest component that needs it. Everything imported by a client component becomes client code, so a high boundary silently ships the subtree to the browser.children - composition keeps the inner tree on the server.Bad:
'use client' // whole page is now client code
export default function Page() {
const [open, setOpen] = useState(false)
// 40 KB of product listing logic dragged into the bundle for one toggle
}Good:
// page.tsx - server component, fetches and renders
export default async function Page() {
const products = await getProducts()
return <ProductList products={products} action={<FilterToggle />} />
}
// FilterToggle.tsx - 'use client', only the toggle ships to the browserasync/await. No useEffect data fetching unless the data is genuinely client-driven (e.g. depends on live client state).fetch is cached by default - every fetch call must make a deliberate choice:{ next: { revalidate: N } }. Pick N from acceptable staleness in Step 1 - 60 s for dashboards-ish freshness, 3600 s for marketing/content.{ cache: 'no-store' }, or export const dynamic = 'force-dynamic' when the whole route is per-request.next: { tags: ['products'] }) when a mutation will need to invalidate it precisely.cookies() or headers() makes the route dynamic - expect it, don't fight it.fetch with identical args is memoized per request, so fetch where the data is used rather than prop-drilling from the top.loading.tsx for every route with async work - the shell should render immediately while data streams in.<Suspense> with their own fallbacks so one slow query does not hold the whole page. If any single data dependency takes over ~300 ms, it is a streaming candidate; never let one slow fetch block first paint past 1 s when the rest of the page is ready.error.tsx for route-level error boundaries; not-found.tsx plus notFound() for 404s.revalidatePath or revalidateTag for exactly the data that changed - tags from Step 3 make this precise instead of nuking whole routes.Produce the route implementation plus a one-table caching manifest: for each data source on the page - cached or dynamic, revalidate interval or no-store, tags, and which mutation invalidates it. Every choice explicit, none accidental.
'use client' on a page or layout to fix one interactive widget - extract the widget instead; the directive is transitive to every import.useEffect what a server component could fetch - it adds a client-server waterfall and a loading spinner where streamed HTML would do.force-dynamic route-wide when one fetch needed no-store - you lose caching for everything else on the page.'use client' directive is at a leaf and justified by interactivity, state, or a browser API.fetch has an explicit caching decision; grep for bare fetches with none.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.