Skip to main content

nextjs-anti-patterns

Identify and fix common Next.js App Router anti-patterns and mistakes. Use when reviewing code for Next.js best practices, debugging performance issues, migrating from Pages Router patterns, or preventing common pitfalls. Activates for code review, performance optimization, or detecting inappropriate useEffect/useState usage. CRITICAL: For browser detection, keep the logic in the user-facing component (or a composed helper that it renders) rather than isolating it in unused files.

소스 정보

저장소
wsimmonds/claude-nextjs-skills
최근 소스 활동
2025년 10월 23일 23:29
감지된 SKILL.md 언어
영어
스타
108
포크
22

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
nextjs-anti-patterns
description
Identify and fix common Next.js App Router anti-patterns and mistakes. Use when reviewing code for Next.js best practices, debugging performance issues, migrating from Pages Router patterns, or preventing common pitfalls. Activates for code review, performance optimization, or detecting inappropriate useEffect/useState usage. CRITICAL: For browser detection, keep the logic in the user-facing component (or a composed helper that it renders) rather than isolating it in unused files.
# Next.js Anti-Patterns ## Overview Identify and correct common anti-patterns in Next.js App Router applications, focusing on misuse of useEffect, improper data fetching, unnecessary client-side state, and incorrect component boundaries. ## TypeScript: NEVER Use `any` Type **CRITICAL RULE:** This codebase has `@typescript-eslint/no-explicit-any` enabled. Using `any` will cause build failures. **❌ WRONG:** ```typescript function handleSubmit(e: any) { ... } const data: any[] = []; ``` **✅ CORRECT:** ```typescript function handleSubmit(e: React.FormEvent<HTMLFormElement>) { ... } const data: string[] = []; ``` ### Common Next.js Type Patterns ```typescript // Page props function Page({ params }: { params: { slug: string } }) { ... } function Page({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined } }) { ... } // Form events const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { ... } const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... } // Server actions async function myAction(formData: FormData) { ... } ``` ## When to Use This Skill Use this skill when: - Reviewing Next.js code for best practices - Debugging performance issues in App Router apps - Migrating from Pages Router with legacy patterns - Code shows excessive client-side JavaScript - Components are using useEffect unnecessarily - Detecting waterfall data fetching patterns - Identifying incorrect Server/Client component usage ## Rendering Responsibilities When requirements call for a page or component to present specific UI (e.g., display a banner or guard message), place that rendering responsibility in the exported component that callers actually use. Helper components are fine, but make sure they are composed so the main entry point still outputs the expected elements. ### Recommended Pattern ```typescript // page.tsx 'use client'; import { BrowserGuard } from './BrowserGuard'; export default function Page() { return <BrowserGuard />; } // BrowserGuard.tsx 'use client'; export function BrowserGuard() { const isSafari = typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); if (isSafari) { return <h1>Unsupported Browser</h1>; } return <h1>Welcome</h1>; } ``` This keeps the logic modular while ensuring the visible component still renders the appropriate message. Apply the same principle to other requirements—compose helpers, but never leave the primary component without the mandated UI. ## Anti-Pattern Categories ### Category 1: Inappropriate useEffect Usage #### Anti-Pattern 1.1: Using useEffect for Browser Detection **❌ WRONG - useEffect with useState:** ```typescript 'use client'; import { useEffect, useState } from 'react'; export default function BrowserCheck() { const [isSafari, setIsSafari] = useState(false); useEffect(() => { setIsSafari(/Safari/.test(navigator.userAgent)); }, []); return <div>{isSafari ? 'Unsupported' : 'Welcome'}</div>; } ``` **Why it's wrong:** - Causes flash of wrong content (renders default, then updates) - Unnecessary client-side state management - Can be detected directly in component body - Creates hydration mismatch **✅ CORRECT - Direct browser detection in component body:** ```typescript 'use client'; export default function BrowserCheck() { // Direct detection without useState or useEffect const isSafari = typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); const isFirefox = typeof navigator !== 'undefined' && /Firefox/.test(navigator.userAgent); if (isSafari || isFirefox) { return <h1>Unsupported Browser</h1>; } return <h1>Welcome</h1>; } ``` **Key points:** - Use `typeof navigator !== 'undefined'` for SSR safety - Direct detection in component body (no hooks) - Use conditional rendering, not useState - Safari detection should exclude Chrome (`!/Chrome/.test(...)`) - Implement directly in the component OR create a helper and import it into the exported entry point - Ensure the logic is accessible from the component that consumers will render **Alternative: Use CSS media queries for responsive:** ```typescript export default function ResponsiveComponent() { return ( <div> <div className="block md:hidden"><MobileView /></div> <div className="hidden md:block"><DesktopView /></div> </div> ); } ``` #### Anti-Pattern 1.2: Using useEffect for Data Fetching **Wrong:** ```typescript 'use client'; import { useEffect, useState } from 'react'; export default function BlogPosts() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch('/api/posts') .then(res => res.json()) .then(data => { setPosts(data); setLoading(false); }) .catch(err => { setError(err); setLoading(false); }); }, []); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } ``` **Why it's wrong:** - Adds unnecessary client-side JavaScript - Creates loading states that could be avoided - Slower initial render (client-side fetch) - Complicates error handling - Breaks without JavaScript enabled **Correct:** ```typescript // Server Component (no 'use client') export default async function BlogPosts() { const response = await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } // Cache for 1 hour }); if (!response.ok) { throw new Error('Failed to fetch posts'); } const posts = await response.json(); return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> ); } ``` **Benefits:** - Zero client-side JavaScript for this component - No loading state needed - Faster initial render (server-side) - Automatic error boundary support - Works without JavaScript #### Anti-Pattern 1.3: Using useEffect for URL Detection **Wrong:** ```typescript 'use client'; import { useEffect, useState } from 'react'; export default function ShareButton() { const [url, setUrl] = useState(''); useEffect(() => { setUrl(window.location.href); }, []); const handleShare = () => { navigator.share({ url }); }; return <button onClick={handleShare}>Share</button>; } ``` **Correct:** ```typescript 'use client'; export default function ShareButton() { const handleShare = () => { // Access URL directly when needed const url = window.location.href; navigator.share({ url }); }; return <button onClick={handleShare}>Share</button>; } ``` ### Category 2: Inappropriate useState Usage #### Anti-Pattern 2.1: Using useState for Server Data **Wrong:** ```typescript 'use client'; import { useState, useEffect } from 'react'; export default function UserProfile({ userId }: { userId: string }) { const [user, setUser] = useState(null); useEffect(() => { fetch(`/api/users/${userId}`) .then(r => r.json()) .then(setUser); }, [userId]); if (!user) return <div>Loading...</div>; return <div>{user.name}</div>; } ``` **Correct:** ```typescript // Server Component export default async function UserProfile({ userId }: { userId: string }) { const response = await fetch(`https://api.example.com/users/${userId}`); const user = await response.json(); return <div>{user.name}</div>; } ``` #### Anti-Pattern 2.2: Using useState for Derived Values **Wrong:** ```typescript 'use client'; import { useState, useEffect } from 'react'; export default function ProductList({ products }: { products: Product[] }) { const [total, setTotal] = useState(0); useEffect(() => { setTotal(products.reduce((sum, p) => sum + p.price, 0)); }, [products]); return <div>Total: ${total}</div>; } ``` **Correct:** ```typescript 'use client'; export default function ProductList({ products }: { products: Product[] }) { // Calculate directly - no state needed const total = products.reduce((sum, p) => sum + p.price, 0); return <div>Total: ${total}</div>; } ``` **Or, if truly expensive calculation:** ```typescript 'use client'; import { useMemo } from 'react'; export default function ProductList({ products }: { products: Product[] }) { const total = useMemo( () => products.reduce((sum, p) => sum + p.price, 0), [products] ); return <div>Total: ${total}</div>; } ``` ### Category 3: Pages Router Patterns in App Router #### Anti-Pattern 3.1: Using getServerSideProps **Wrong:** ```typescript // This doesn't work in App Router! export async function getServerSideProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } export default function Page({ data }) { return <div>{data.title}</div>; } ``` **Correct:** ```typescript // App Router: Server Component with async export default async function Page() { const res = await fetch('https://api.example.com/data', { cache: 'no-store' // Equivalent to getServerSideProps }); const data = await res.json(); return <div>{data.title}</div>; } ``` #### Anti-Pattern 3.2: Using getStaticProps **Wrong:** ```typescript // This doesn't work in App Router! export async function getStaticProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data }, revalidate: 60 }; } ``` **Correct:** ```typescript // App Router: Server Component with revalidation export default async function Page() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } // Revalidate every 60 seconds }); const data = await res.json(); return <div>{data.title}</div>; } ``` #### Anti-Pattern 3.3: Using next/head in App Router **Wrong:** ```typescript import Head from 'next/head'; export default function Page() { return ( <> <Head> <title>My Page</title> <meta name="description" content="Description" /> </Head> <main>Content</main> </>
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기