Back to list
mgd34msu

nextjs

by mgd34msu

Plug in, Receive good vibes.

2🍴 0📅 Jan 25, 2026

SKILL.md


name: nextjs description: Builds full-stack React applications with Next.js App Router, Server Components, Server Actions, and edge deployment. Use when creating Next.js projects, implementing routing, data fetching, caching, authentication, or deploying to Vercel.

Next.js

Full-stack React framework with App Router, Server Components, Server Actions, and optimized deployment patterns.

Quick Start

Create new project:

npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app && npm run dev

Essential file structure:

src/
  app/
    layout.tsx      # Root layout (required)
    page.tsx        # Home page
    globals.css     # Global styles
    api/            # Route handlers
  components/       # React components
  lib/              # Utilities

App Router Fundamentals

File Conventions

FilePurpose
page.tsxRoute UI
layout.tsxShared UI wrapper
loading.tsxLoading UI (Suspense)
error.tsxError boundary
not-found.tsx404 UI
route.tsAPI endpoint

Routing Patterns

app/
  page.tsx                    # /
  blog/
    page.tsx                  # /blog
    [slug]/
      page.tsx                # /blog/:slug
  (marketing)/                # Route group (no URL segment)
    about/page.tsx            # /about
  @modal/                     # Parallel route (slot)
    (.)photo/[id]/page.tsx    # Intercepting route

Dynamic segments:

// app/blog/[slug]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  return <Article slug={slug} />
}

Catch-all segments:

// app/docs/[...slug]/page.tsx - matches /docs/a, /docs/a/b, etc.
// app/docs/[[...slug]]/page.tsx - also matches /docs

Layouts

// app/layout.tsx - Root layout (required)
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Nested layout:

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  )
}

Server Components

Default behavior - all components are Server Components unless marked with 'use client'.

// Server Component (default)
async function Posts() {
  const posts = await db.posts.findMany() // Direct DB access
  return (
    <ul>
      {posts.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  )
}

When to use Client Components:

  • Event handlers (onClick, onChange)
  • State and lifecycle (useState, useEffect)
  • Browser APIs
  • Custom hooks with state
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Data Fetching

// Direct fetch in Server Component
async function BlogPosts() {
  const res = await fetch('https://api.example.com/posts')
  const posts = await res.json()
  return <PostList posts={posts} />
}

// With ORM
async function Users() {
  const users = await prisma.user.findMany()
  return <UserList users={users} />
}

Parallel Data Fetching

export default async function Page() {
  // Start both requests simultaneously
  const postsPromise = getPosts()
  const usersPromise = getUsers()

  // Await both
  const [posts, users] = await Promise.all([postsPromise, usersPromise])

  return (
    <>
      <PostList posts={posts} />
      <UserList users={users} />
    </>
  )
}

Streaming with Suspense

import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

Server Actions

Basic Form

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.posts.create({ data: { title, content } })

  revalidatePath('/posts')
  redirect('/posts')
}
// app/posts/new/page.tsx
import { createPost } from '@/app/actions'

export default function NewPostPage() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  )
}

With Validation

'use server'

import { z } from 'zod'

const PostSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
})

export async function createPost(formData: FormData) {
  const validated = PostSchema.safeParse({
    title: formData.get('title'),
    content: formData.get('content'),
  })

  if (!validated.success) {
    return { error: validated.error.flatten().fieldErrors }
  }

  await db.posts.create({ data: validated.data })
  revalidatePath('/posts')
  redirect('/posts')
}

With useActionState

'use client'

import { useActionState } from 'react'
import { createPost } from '@/app/actions'

export function CreatePostForm() {
  const [state, action, pending] = useActionState(createPost, null)

  return (
    <form action={action}>
      <input name="title" />
      {state?.error?.title && <p>{state.error.title}</p>}
      <button disabled={pending}>
        {pending ? 'Creating...' : 'Create'}
      </button>
    </form>
  )
}

Caching

Data Cache

// Cached by default (static)
const data = await fetch('https://api.example.com/data')

// Opt out of caching
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store'
})

// Time-based revalidation
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 } // 1 hour
})

// Tag-based revalidation
const data = await fetch('https://api.example.com/data', {
  next: { tags: ['posts'] }
})

Revalidation

'use server'

import { revalidatePath, revalidateTag } from 'next/cache'

export async function updatePost() {
  // Revalidate specific path
  revalidatePath('/posts')

  // Revalidate by tag
  revalidateTag('posts')

  // Revalidate layout
  revalidatePath('/posts', 'layout')
}

Route Segment Config

// Force dynamic rendering
export const dynamic = 'force-dynamic'

// Revalidate every 60 seconds
export const revalidate = 60

// Generate static params
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

API Routes (Route Handlers)

// app/api/posts/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  const posts = await db.posts.findMany()
  return NextResponse.json(posts)
}

export async function POST(request: Request) {
  const body = await request.json()
  const post = await db.posts.create({ data: body })
  return NextResponse.json(post, { status: 201 })
}

Dynamic route handler:

// app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params
  const post = await db.posts.findUnique({ where: { id } })

  if (!post) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 })
  }

  return NextResponse.json(post)
}

Middleware

// middleware.ts (root level)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Check auth
  const token = request.cookies.get('token')

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // Add headers
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')

  return response
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

Metadata & SEO

// Static metadata
export const metadata = {
  title: 'My App',
  description: 'App description',
  openGraph: {
    title: 'My App',
    description: 'App description',
    images: ['/og.png'],
  },
}

// Dynamic metadata
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.excerpt,
  }
}

Image Optimization

import Image from 'next/image'

export function Avatar() {
  return (
    <Image
      src="/avatar.png"
      alt="Avatar"
      width={64}
      height={64}
      priority // Above the fold
    />
  )
}

// Remote images (configure in next.config.js)
<Image
  src="https://example.com/image.jpg"
  alt="Remote image"
  width={800}
  height={600}
/>

Environment Variables

# .env.local (git ignored, local dev)
DATABASE_URL=postgresql://...
SECRET_KEY=abc123

# Public (exposed to browser)
NEXT_PUBLIC_API_URL=https://api.example.com
// Server only
const dbUrl = process.env.DATABASE_URL

// Client accessible
const apiUrl = process.env.NEXT_PUBLIC_API_URL

Common Patterns

Authentication Check

import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export default async function ProtectedPage() {
  const cookieStore = await cookies()
  const session = cookieStore.get('session')

  if (!session) {
    redirect('/login')
  }

  return <Dashboard />
}

Error Handling

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

Loading States

// app/dashboard/loading.tsx
export default function Loading() {
  return <DashboardSkeleton />
}

Best Practices

  1. Default to Server Components - Only use Client Components when needed
  2. Colocate data fetching - Fetch data in the component that needs it
  3. Use Server Actions for mutations - Not API routes
  4. Implement proper loading states - Use Suspense and loading.tsx
  5. Configure caching appropriately - Don't over-cache dynamic content
  6. Use generateStaticParams - For static generation of dynamic routes

Common Mistakes

MistakeFix
Using 'use client' everywhereOnly use for interactivity
Fetching in layout for child dataFetch in the page/component that needs it
Not awaiting params/searchParamsThese are now Promises in Next.js 15
Using API routes for mutationsUse Server Actions instead
Forgetting to revalidate cacheCall revalidatePath/revalidateTag

Reference Files

Templates

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon