スキル一覧に戻る
levanminhduc

nextjs

by levanminhduc

0🍴 0📅 2025年12月25日
GitHubで見るManusで実行

SKILL.md


name: nextjs description: Guide for implementing Next.js - a React framework for production with server-side rendering, static generation, and modern web features. Use when building Next.js applications, implementing App Router, working with server components, data fetching, routing, or optimizing performance. license: MIT version: 1.0.0

Next.js Skill

Next.js is a React framework for building full-stack web applications with server-side rendering, static generation, and powerful optimization features built-in.

Reference

https://nextjs.org/docs/llms.txt

When to Use This Skill

Use this skill when:

  • Building new Next.js applications (v15+)
  • Implementing App Router architecture
  • Working with Server Components and Client Components
  • Setting up routing, layouts, and navigation
  • Implementing data fetching patterns
  • Optimizing images, fonts, and performance
  • Configuring metadata and SEO
  • Setting up API routes and route handlers
  • Migrating from Pages Router to App Router
  • Deploying Next.js applications

Core Concepts

App Router vs Pages Router

App Router (Recommended for v13+):

  • Modern architecture with React Server Components
  • File-system based routing in app/ directory
  • Layouts, loading states, and error boundaries
  • Streaming and Suspense support
  • Nested routing with layouts

Pages Router (Legacy):

  • Traditional page-based routing in pages/ directory
  • Uses getStaticProps, getServerSideProps, getInitialProps
  • Still supported for existing projects

Key Architectural Principles

  1. Server Components by Default: Components in app/ are Server Components unless marked with 'use client'
  2. File-based Routing: File system defines application routes
  3. Nested Layouts: Share UI across routes with layouts
  4. Progressive Enhancement: Works without JavaScript when possible
  5. Automatic Optimization: Images, fonts, scripts auto-optimized

Installation & Setup

Create New Project

npx create-next-app@latest my-app
# or
yarn create next-app my-app
# or
pnpm create next-app my-app
# or
bun create next-app my-app

Interactive Setup Prompts:

  • TypeScript? (Yes recommended)
  • ESLint? (Yes recommended)
  • Tailwind CSS? (Optional)
  • src/ directory? (Optional)
  • App Router? (Yes for new projects)
  • Import alias? (Default: @/*)

Manual Setup

npm install next@latest react@latest react-dom@latest

package.json scripts:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

Project Structure

my-app/
├── app/                    # App Router (v13+)
│   ├── layout.tsx         # Root layout
│   ├── page.tsx           # Home page
│   ├── loading.tsx        # Loading UI
│   ├── error.tsx          # Error UI
│   ├── not-found.tsx      # 404 page
│   ├── global.css         # Global styles
│   └── [folder]/          # Route segments
├── public/                # Static assets
├── components/            # React components
├── lib/                   # Utility functions
├── next.config.js         # Next.js configuration
├── package.json
└── tsconfig.json

Routing

File Conventions

  • page.tsx - Page UI for route
  • layout.tsx - Shared UI for segment and children
  • loading.tsx - Loading UI (wraps page in Suspense)
  • error.tsx - Error UI (wraps page in Error Boundary)
  • not-found.tsx - 404 UI
  • route.ts - API endpoint (Route Handler)
  • template.tsx - Re-rendered layout UI
  • default.tsx - Parallel route fallback

Basic Routing

Static Route:

app/
├── page.tsx              → /
├── about/
│   └── page.tsx         → /about
└── blog/
    └── page.tsx         → /blog

Dynamic Route:

// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Post: {params.slug}</h1>;
}

Catch-all Route:

// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
  return <h1>Category: {params.slug.join("/")}</h1>;
}

Optional Catch-all:

// app/docs/[[...slug]]/page.tsx
// Matches /docs, /docs/a, /docs/a/b, etc.

Route Groups

Organize routes without affecting URL:

app/
├── (marketing)/          # Group without URL segment
│   ├── about/page.tsx   → /about
│   └── blog/page.tsx    → /blog
└── (shop)/
    ├── products/page.tsx → /products
    └── cart/page.tsx     → /cart

Parallel Routes

Render multiple pages in same layout:

app/
├── @team/               # Slot
│   └── page.tsx
├── @analytics/          # Slot
│   └── page.tsx
└── layout.tsx           # Uses both slots
// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  );
}

Intercepting Routes

Intercept routes to show in modal:

app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx
└── (..)photo/           # Intercepts /photo/[id]
    └── [id]/
        └── page.tsx

Layouts

Root Layout (Required)

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

Nested Layouts

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <section>
      <nav>Dashboard Nav</nav>
      {children}
    </section>
  );
}

Layouts are:

  • Shared across multiple pages
  • Preserve state on navigation
  • Do not re-render on navigation
  • Can fetch data

Server and Client Components

Server Components (Default)

Components in app/ are Server Components by default:

// app/page.tsx (Server Component)
async function getData() {
  const res = await fetch("https://api.example.com/data");
  return res.json();
}

export default async function Page() {
  const data = await getData();
  return <div>{data.title}</div>;
}

Benefits:

  • Fetch data on server
  • Access backend resources directly
  • Keep sensitive data on server
  • Reduce client-side JavaScript
  • Improve initial page load

Limitations:

  • Cannot use hooks (useState, useEffect)
  • Cannot use browser APIs
  • Cannot add event listeners

Client Components

Mark components with 'use client' directive:

// components/counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

Use Client Components for:

  • Interactive UI (onClick, onChange)
  • State management (useState, useReducer)
  • Effects (useEffect, useLayoutEffect)
  • Browser APIs (localStorage, navigator)
  • Custom hooks
  • React class components

Composition Pattern

// app/page.tsx (Server Component)
import { ClientComponent } from "./client-component";

export default function Page() {
  return (
    <div>
      <h1>Server-rendered content</h1>
      <ClientComponent />
    </div>
  );
}

Data Fetching

Server Component Data Fetching

// app/posts/page.tsx
async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { revalidate: 3600 }, // Revalidate every hour
  });

  if (!res.ok) throw new Error("Failed to fetch");

  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

Caching Strategies

Force Cache (Default):

fetch("https://api.example.com/data", { cache: "force-cache" });

No Store (Dynamic):

fetch("https://api.example.com/data", { cache: "no-store" });

Revalidate:

fetch("https://api.example.com/data", {
  next: { revalidate: 3600 }, // Seconds
});

Tag-based Revalidation:

fetch("https://api.example.com/data", {
  next: { tags: ["posts"] },
});

// Revalidate elsewhere:
import { revalidateTag } from "next/cache";
revalidateTag("posts");

Parallel Data Fetching

async function getData() {
  const [posts, users] = await Promise.all([
    fetch("https://api.example.com/posts").then((r) => r.json()),
    fetch("https://api.example.com/users").then((r) => r.json()),
  ]);

  return { posts, users };
}

Sequential Data Fetching

async function getData() {
  const post = await fetch(`https://api.example.com/posts/${id}`).then((r) =>
    r.json(),
  );
  const author = await fetch(
    `https://api.example.com/users/${post.authorId}`,
  ).then((r) => r.json());

  return { post, author };
}

Route Handlers (API Routes)

Basic Route Handler

// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ message: "Hello" });
}

export async function POST(request: Request) {
  const body = await request.json();
  return Response.json({ received: body });
}

Dynamic Route Handler

// app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } },
) {
  const post = await getPost(params.id);
  return Response.json(post);
}

export async function DELETE(
  request: Request,
  { params }: { params: { id: string } },
) {
  await deletePost(params.id);
  return new Response(null, { status: 204 });
}

Request Helpers

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get("id");

  const cookies = request.headers.get("cookie");

  return Response.json({ id });
}

Response Types

// JSON
return Response.json({ data: "value" });

// Text
return new Response("Hello", { headers: { "Content-Type": "text/plain" } });

// Redirect
return Response.redirect("https://example.com");

// Status codes
return new Response("Not Found", { status: 404 });
import Link from "next/link";

export default function Page() {
  return (
    <>
      <Link href="/about">About</Link>
      <Link href="/blog/post-1">Post 1</Link>
      <Link href={{ pathname: "/blog/[slug]", query: { slug: "post-1" } }}>
        Post 1 (alternative)
      </Link>
    </>
  );
}

useRouter Hook (Client)

"use client";

import { useRouter } from "next/navigation";

export function NavigateButton() {
  const router = useRouter();

  return <button onClick={() => router.push("/dashboard")}>Dashboard</button>;
}

Router Methods:

  • router.push(href) - Navigate to route
  • router.replace(href) - Replace current history
  • router.refresh() - Refresh current route
  • router.back() - Navigate back
  • router.forward() - Navigate forward
  • router.prefetch(href) - Prefetch route

Programmatic Navigation (Server)

import { redirect } from "next/navigation";

export default async function Page() {
  const session = await getSession();

  if (!session) {
    redirect("/login");
  }

  return <div>Protected content</div>;
}

Metadata & SEO

Static Metadata

// app/page.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "My Page",
  description: "Page description",
  keywords: ["nextjs", "react"],
  openGraph: {
    title: "My Page",
    description: "Page description",
    images: ["/og-image.jpg"],
  },
  twitter: {
    card: "summary_large_image",
    title: "My Page",
    description: "Page description",
    images: ["/twitter-image.jpg"],
  },
};

export default function Page() {
  return <div>Content</div>;
}

Dynamic Metadata

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}

Metadata Files

  • favicon.ico, icon.png, apple-icon.png - Favicons
  • opengraph-image.png, twitter-image.png - Social images
  • robots.txt - Robots file
  • sitemap.xml - Sitemap

Image Optimization

Image Component

import Image from "next/image";

export default function Page() {
  return (
    <>
      {/* Local image */}
      <Image src="/profile.png" alt="Profile" width={500} height={500} />

      {/* Remote image */}
      <Image
        src="https://example.com/image.jpg"
        alt="Remote"
        width={500}
        height={500}
      />

      {/* Responsive fill */}
      <div style={{ position: "relative", width: "100%", height: "400px" }}>
        <Image src="/hero.jpg" alt="Hero" fill style={{ objectFit: "cover" }} />
      </div>

      {/* Priority loading */}
      <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
    </>
  );
}

Image Props:

  • src - Image path (local or URL)
  • alt - Alt text (required)
  • width, height - Dimensions (required unless fill)
  • fill - Fill parent container
  • sizes - Responsive sizes
  • quality - 1-100 (default 75)
  • priority - Preload image
  • placeholder - 'blur' | 'empty'
  • blurDataURL - Data URL for blur

Remote Image Configuration

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "example.com",
        pathname: "/images/**",
      },
    ],
  },
};

Font Optimization

Google Fonts

// app/layout.tsx
import { Inter, Roboto_Mono } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
});

const robotoMono = Roboto_Mono({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-roboto-mono",
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Local Fonts

import localFont from "next/font/local";

const myFont = localFont({
  src: "./fonts/my-font.woff2",
  display: "swap",
  variable: "--font-my-font",
});

Loading States

Loading File

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

Streaming with Suspense

// app/page.tsx
import { Suspense } from "react";

async function Posts() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map((p) => (
        <li key={p.id}>{p.title}</li>
      ))}
    </ul>
  );
}

export default function Page() {
  return (
    <div>
      <h1>My Posts</h1>
      <Suspense fallback={<div>Loading posts...</div>}>
        <Posts />
      </Suspense>
    </div>
  );
}

Error Handling

Error File

// app/error.tsx
"use client";

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

Global Error

// app/global-error.tsx
"use client";

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

Not Found

// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>404 - Not Found</h2>
      <p>Could not find requested resource</p>
    </div>
  );
}

// Trigger programmatically
import { notFound } from "next/navigation";

export default async function Page({ params }) {
  const post = await getPost(params.id);

  if (!post) {
    notFound();
  }

  return <div>{post.title}</div>;
}

Middleware

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Authentication check
  const token = request.cookies.get("token");

  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

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

  return response;
}

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

Environment Variables

# .env.local
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
// Server-side only
const dbUrl = process.env.DATABASE_URL;

// Client and server (NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;

Configuration

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  // React strict mode
  reactStrictMode: true,

  // Image domains
  images: {
    remotePatterns: [{ protocol: "https", hostname: "example.com" }],
  },

  // Redirects
  async redirects() {
    return [
      {
        source: "/old-page",
        destination: "/new-page",
        permanent: true,
      },
    ];
  },

  // Rewrites
  async rewrites() {
    return [
      {
        source: "/api/:path*",
        destination: "https://api.example.com/:path*",
      },
    ];
  },

  // Headers
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [{ key: "X-Frame-Options", value: "DENY" }],
      },
    ];
  },

  // Environment variables
  env: {
    CUSTOM_KEY: "value",
  },
};

module.exports = nextConfig;

Best Practices

  1. Use Server Components: Default to Server Components, use Client Components only when needed
  2. Optimize Images: Always use next/image for automatic optimization
  3. Metadata: Set proper metadata for SEO
  4. Loading States: Provide loading UI with Suspense
  5. Error Handling: Implement error boundaries
  6. Route Handlers: Use for API endpoints instead of separate backend
  7. Caching: Leverage built-in caching strategies
  8. Layouts: Use nested layouts to share UI
  9. TypeScript: Enable TypeScript for type safety
  10. Performance: Use priority for above-fold images, lazy load below-fold

Common Patterns

Protected Routes

// app/dashboard/layout.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";

export default async function DashboardLayout({ children }) {
  const session = await getSession();

  if (!session) {
    redirect("/login");
  }

  return <>{children}</>;
}

Data Mutations (Server Actions)

// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
  const title = formData.get("title");

  await db.post.create({ data: { title } });

  revalidatePath("/posts");
}

// app/posts/new/page.tsx
import { createPost } from "@/app/actions";

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" type="text" required />
      <button type="submit">Create</button>
    </form>
  );
}

Static Generation

// Generate static params for dynamic routes
export async function generateStaticParams() {
  const posts = await getPosts();

  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function Post({ params }) {
  const post = await getPost(params.slug);
  return <article>{post.content}</article>;
}

Deployment

# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

Self-Hosting

# Build
npm run build

# Start production server
npm start

Requirements:

  • Node.js 18.17 or later
  • output: 'standalone' in next.config.js (optional, reduces size)

Docker

FROM node:18-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

Troubleshooting

Common Issues

  1. Hydration errors

    • Ensure server and client render same content
    • Check for browser-only code in Server Components
    • Verify no conditional rendering based on browser APIs
  2. Images not loading

    • Add remote domains to next.config.js
    • Check image paths (use leading / for public)
    • Verify width/height provided
  3. API route 404

    • Check file is named route.ts/js not index.ts
    • Verify export named GET/POST not default export
    • Ensure in app/api/ directory
  4. "use client" errors

    • Add 'use client' to components using hooks
    • Import Client Components in Server Components, not vice versa
    • Check event handlers have 'use client'
  5. Metadata not updating

    • Clear browser cache
    • Check metadata export is named correctly
    • Verify async generateMetadata returns Promise

Resources

Implementation Checklist

When building with Next.js:

  • Create project with create-next-app
  • Configure TypeScript and ESLint
  • Set up root layout with metadata
  • Implement routing structure
  • Add loading and error states
  • Configure image optimization
  • Set up font optimization
  • Implement data fetching patterns
  • Add API routes as needed
  • Configure environment variables
  • Set up middleware if needed
  • Optimize for production build
  • Test in production mode
  • Configure deployment platform
  • Set up monitoring and analytics

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

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

+20
LICENSE

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

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

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

0/5
Issue管理

オープンIssueが50未満

+5
言語

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

+5
タグ

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

0/5

レビュー

💬

レビュー機能は近日公開予定です