DevToolBox無料
ブログ

Next.js ディープダイブガイド 2026:App Router、Server Components、本番パターン

16分by DevToolBox

TL;DR — Key Summary

Next.js 15 App Router is the recommended approach for new projects. Use Server Components by default, add "use client" only when you need interactivity. Server Actions replace API routes for mutations. ISR with revalidatePath gives you the best of static and dynamic.

Key Takeaways

  • App Router uses React Server Components by default, reducing client JS and improving performance
  • File-based routing: layout.tsx, loading.tsx, error.tsx handle different UI states declaratively
  • Data fetching uses async/await directly in components with fetch options to control caching
  • Server Actions simplify form handling and mutations without writing API routes manually
  • next/image and next/font automatically optimize media assets for better Core Web Vitals
  • Middleware runs on the Edge Runtime and is ideal for authentication and A/B testing
  • Standalone output mode enables Docker-based self-hosted deployment

1. App Router vs Pages Router: When to Use Each

Next.js 13 introduced the App Router, a new routing paradigm built on React Server Components. It coexists with the original Pages Router, enabling incremental migration. Understanding their differences is critical for making the right architectural decisions.

FeatureApp Router (Recommended)Pages Router (Legacy)
Default renderingReact Server ComponentsClient Components
Data fetchingasync/await + fetch cachegetStaticProps / getServerSideProps
Layoutslayout.tsx (嵌套)_app.tsx (全局)
Loading statesloading.tsx (Suspense)Manual implementation
Error handlingerror.tsx (Error Boundary)Manual implementation
Server ActionsNative supportRequires API routes
StreamingBuilt-inNot supported
Best forAll new projectsMaintaining existing apps

Migration Path: Pages Router to App Router

Next.js supports using both routers in the same project. The migration strategy is to proceed page by page, starting with non-critical routes.

// Directory structure showing coexistence
my-app/
  app/                    // App Router
    layout.tsx            // Root layout
    page.tsx              // Home page (migrated)
    dashboard/
      layout.tsx          // Dashboard layout
      page.tsx            // Dashboard page (migrated)
  pages/                  // Pages Router (still active)
    _app.tsx              // Global wrapper
    old-feature.tsx       // Not yet migrated
    api/
      legacy-endpoint.ts  // API route (still works)

// Migration checklist:
// 1. Move page.tsx from pages/ to app/
// 2. Replace getStaticProps with async component + fetch
// 3. Replace getServerSideProps with async Server Component
// 4. Add "use client" to components with hooks/events
// 5. Create layout.tsx for shared UI
// 6. Replace _document.tsx with root layout metadata

2. Server Components vs Client Components

React Server Components (RSC) are the cornerstone of the App Router. Understanding the boundary between Server and Client Components is essential for writing high-performance Next.js applications.

Server Components (Default)

Server Components render on the server and send zero JavaScript to the client. They can directly access databases, file systems, and private APIs.

// app/dashboard/page.tsx - Server Component (no "use client")
import { db } from "@/lib/db";
import { cache } from "react";

// cache() deduplicates requests across the render tree
const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

export default async function DashboardPage() {
  // Direct database access - secure, no API needed
  const user = await getUser("user_123");
  const stats = await db.analytics.findMany({
    where: { userId: user.id },
    orderBy: { date: "desc" },
    take: 30,
  });

  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      {/* Client component for interactivity */}
      <StatsChart data={stats} />
    </div>
  );
}

Client Components ("use client")

The "use client" directive marks a component and its subtree as client-rendered. Use it only when you genuinely need browser capabilities.

"use client";

// Must use "use client" when:
// - Using useState, useEffect, useRef, useContext
// - Adding event listeners (onClick, onChange, onSubmit)
// - Using browser APIs (window, localStorage, navigator)
// - Using third-party client-side libraries

import { useState, useEffect } from "react";

interface StatsChartProps {
  data: { date: string; value: number }[];
}

export function StatsChart({ data }: StatsChartProps) {
  const [activeIndex, setActiveIndex] = useState(0);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    // Animate on mount - requires browser
    setIsVisible(true);
  }, []);

  return (
    <div
      style={{ opacity: isVisible ? 1 : 0, transition: "opacity 0.3s" }}
    >
      {data.map((point, i) => (
        <div
          key={i}
          onClick={() => setActiveIndex(i)}
          style={{
            background: i === activeIndex ? "#6366f1" : "#e2e8f0",
            cursor: "pointer",
          }}
        >
          {point.value}
        </div>
      ))}
    </div>
  );
}

Component Boundary Rule

"use client" creates a boundary: everything in that file and everything it imports becomes client code. Keep client components at the leaves of the component tree, and let Server Components handle data fetching.

3. File-Based Routing: Layouts, Templates, and Special Files

The App Router defines routes through file and folder names inside the app directory. Special filename conventions control behavior at each route segment.

app/
  layout.tsx          // Required: root layout, wraps all pages
  page.tsx            // Route: /
  loading.tsx         // Automatic Suspense boundary
  error.tsx           // Error boundary for this segment
  not-found.tsx       // 404 for this segment
  template.tsx        // Like layout but re-mounts on nav
  global-error.tsx    // Root error boundary
  blog/
    layout.tsx        // Shared blog layout
    page.tsx          // Route: /blog
    [slug]/
      page.tsx        // Route: /blog/:slug
      opengraph-image.tsx  // OG image generation
  (marketing)/        // Route group - no URL segment
    about/
      page.tsx        // Route: /about
    pricing/
      page.tsx        // Route: /pricing
  @modal/             // Parallel route slot
    (.)photo/[id]/    // Intercepted route
      page.tsx

What the Root Layout Must Include

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";

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

export const metadata: Metadata = {
  title: {
    template: "%s | My App",
    default: "My App",
  },
  description: "My awesome application",
  openGraph: {
    type: "website",
    siteName: "My App",
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

loading.tsx and Suspense Streaming

loading.tsx automatically wraps the page in a React Suspense boundary. The server immediately streams the HTML shell, then streams content chunks as they resolve.

// app/dashboard/loading.tsx
// Shown instantly while dashboard/page.tsx loads
export default function DashboardLoading() {
  return (
    <div style={{ padding: "2rem" }}>
      <div style={{
        height: "2rem",
        width: "40%",
        background: "#e2e8f0",
        borderRadius: "0.25rem",
        animation: "pulse 2s infinite"
      }} />
    </div>
  );
}

// Fine-grained streaming with Suspense
// app/dashboard/page.tsx
import { Suspense } from "react";

export default function DashboardPage() {
  return (
    <div>
      {/* Renders immediately */}
      <DashboardHeader />
      
      {/* Streams when data resolves */}
      <Suspense fallback={<RevenueChartSkeleton />}>
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<LatestInvoicesSkeleton />}>
        <LatestInvoices />
      </Suspense>
    </div>
  );
}

4. Data Fetching: fetch, ISR, and Dynamic Routes

Data fetching in the App Router is done in Server Components using an extended fetch API. Next.js extends the native fetch with fine-grained caching and revalidation control.

fetch Caching Strategies

// Next.js 15: fetch is NOT cached by default

// 1. Static data (CDN cached indefinitely)
const data = await fetch("https://api.example.com/static", {
  cache: "force-cache",
});

// 2. Time-based revalidation (ISR)
const posts = await fetch("https://api.example.com/posts", {
  next: { revalidate: 3600 }, // Revalidate every hour
});

// 3. Tag-based revalidation
const product = await fetch("https://api.example.com/products/1", {
  next: { tags: ["product", "product-1"] },
});

// 4. Dynamic data (never cached)
const userProfile = await fetch("https://api.example.com/me", {
  cache: "no-store",
  headers: { Authorization: "Bearer " + getToken() },
});

// Trigger on-demand revalidation from a Server Action
import { revalidateTag, revalidatePath } from "next/cache";

async function updateProduct(id: string, data: Partial<Product>) {
  "use server";
  await db.product.update({ where: { id }, data });
  revalidateTag("product-" + id); // Invalidate cached product data
  revalidatePath("/products");    // Revalidate product list page
}

generateStaticParams: SSG with Dynamic Routes

// app/blog/[slug]/page.tsx

// Called at build time to pre-render pages
export async function generateStaticParams() {
  const posts = await fetch("https://api.example.com/posts").then(
    (res) => res.json()
  );

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

// Optionally control what happens for slugs not in generateStaticParams
// "blocking": generate on-demand and cache (default)
// false: 404 for unknown slugs
export const dynamicParams = true;

// Metadata generation
export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      images: [{ url: post.coverImage }],
    },
  };
}

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

5. Server Actions: Form Handling and Mutations

Server Actions are async functions marked with "use server" that run directly on the server. They eliminate the need to manually create API routes for simple CRUD operations.

Basic Server Action with a Form

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

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";

const CreatePostSchema = z.object({
  title: z.string().min(3).max(100),
  content: z.string().min(10),
});

export type ActionState = {
  errors?: { title?: string[]; content?: string[] };
  message?: string;
};

export async function createPost(
  prevState: ActionState,
  formData: FormData
): Promise<ActionState> {
  const validated = CreatePostSchema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
  });

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

  try {
    await db.post.create({ data: validated.data });
  } catch (error) {
    return { message: "Database error: Failed to create post." };
  }

  revalidatePath("/blog");
  redirect("/blog");
}

Using useFormState in Client Components

"use client";

import { useFormState, useFormStatus } from "react-dom";
import { createPost, type ActionState } from "@/app/actions";

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Creating..." : "Create Post"}
    </button>
  );
}

const initialState: ActionState = {};

export function CreatePostForm() {
  const [state, dispatch] = useFormState(createPost, initialState);

  return (
    <form action={dispatch}>
      <div>
        <label htmlFor="title">Title</label>
        <input id="title" name="title" type="text" />
        {state.errors?.title && (
          <p style={{ color: "red" }}>{state.errors.title[0]}</p>
        )}
      </div>
      <div>
        <label htmlFor="content">Content</label>
        <textarea id="content" name="content" />
        {state.errors?.content && (
          <p style={{ color: "red" }}>{state.errors.content[0]}</p>
        )}
      </div>
      {state.message && (
        <p style={{ color: "red" }}>{state.message}</p>
      )}
      <SubmitButton />
    </form>
  );
}

Best Practice: Server Actions vs API Routes

Use Server Actions for form submissions and mutations triggered from within the Next.js app. Continue using Route Handlers (app/api/route.ts) for endpoints that need to be called by external systems like mobile apps, third-party services, or webhooks.

6. Image and Font Optimization

next/image and next/font are two of the most powerful built-in optimization tools in Next.js, with a direct impact on Core Web Vitals scores.

next/image: Automatic Format Conversion and Lazy Loading

import Image from "next/image";

// Local image: import for automatic size detection
import heroImage from "@/public/hero.jpg";

export function HeroSection() {
  return (
    <div style={{ position: "relative", height: "80vh" }}>
      {/* LCP image: priority loads eagerly (no lazy) */}
      <Image
        src={heroImage}
        alt="Hero illustration"
        fill
        priority         // Removes loading="lazy" for LCP
        sizes="100vw"   // Responsive sizes hint
        style={{ objectFit: "cover" }}
        quality={85}    // Default: 75 (AVIF/WebP auto-selected)
      />
    </div>
  );
}

// Remote image: requires explicit dimensions
export function ProductCard({ product }: { product: Product }) {
  return (
    <Image
      src={product.imageUrl}
      alt={product.name}
      width={400}
      height={300}
      loading="lazy"   // Default for non-priority images
      placeholder="blur"
      blurDataURL={product.blurHash}
    />
  );
}

Configuring Remote Domains

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cdn.example.com",
        port: "",
        pathname: "/images/**",
      },
      {
        protocol: "https",
        hostname: "**.cloudinary.com",
      },
    ],
    formats: ["image/avif", "image/webp"], // Preferred order
    minimumCacheTTL: 60 * 60 * 24 * 30,   // 30 days
  },
};

export default nextConfig;

next/font: Zero-CLS Font Loading

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

const inter = Inter({
  subsets: ["latin"],
  display: "swap",       // Font display strategy
  variable: "--font-inter",  // CSS variable for use in styles
});

const firaCode = Fira_Code({
  subsets: ["latin"],
  weight: ["400", "500"],
  variable: "--font-fira-code",
});

// Self-hosted font
const brandFont = localFont({
  src: [
    { path: "./fonts/Brand-Regular.woff2", weight: "400" },
    { path: "./fonts/Brand-Bold.woff2", weight: "700" },
  ],
  variable: "--font-brand",
});

// Result: fonts are self-hosted by Next.js server,
// no external requests = no CLS, no privacy concerns
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html
      lang="en"
      className={inter.variable + " " + firaCode.variable + " " + brandFont.variable}
    >
      <body style={{ fontFamily: "var(--font-inter)" }}>{children}</body>
    </html>
  );
}

7. Middleware and Edge Runtime

Next.js Middleware runs on the Edge Runtime (Vercel global edge network, or a lightweight V8 sandbox in Node.js locally) before a request reaches the page, resulting in minimal latency.

Authentication and Redirect Middleware

// middleware.ts (at project root)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { verifyJWT } from "@/lib/auth";

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // --- Authentication check ---
  if (pathname.startsWith("/dashboard")) {
    const token = request.cookies.get("auth-token")?.value;

    if (!token) {
      return NextResponse.redirect(
        new URL("/login?from=" + encodeURIComponent(pathname), request.url)
      );
    }

    try {
      const payload = await verifyJWT(token);
      // Pass user info to the page via headers
      const response = NextResponse.next();
      response.headers.set("x-user-id", payload.sub);
      response.headers.set("x-user-role", payload.role);
      return response;
    } catch {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }

  // --- A/B testing ---
  if (pathname === "/pricing") {
    const bucket = request.cookies.get("ab-bucket")?.value
      ?? (Math.random() < 0.5 ? "a" : "b");
    const response = NextResponse.rewrite(
      new URL("/pricing-" + bucket, request.url)
    );
    response.cookies.set("ab-bucket", bucket, { maxAge: 60 * 60 * 24 * 30 });
    return response;
  }

  // --- Geolocation-based redirect ---
  const country = request.geo?.country ?? "US";
  if (country === "DE" && !pathname.startsWith("/de")) {
    return NextResponse.redirect(new URL("/de" + pathname, request.url));
  }

  return NextResponse.next();
}

// Control which routes middleware applies to
export const config = {
  matcher: [
    // Match all routes except static files and API
    "/((?!_next/static|_next/image|favicon.ico|api).*)",
  ],
};

Edge Runtime Limitations

Edge Runtime does not support all Node.js APIs. You cannot use the fs module, native Node.js crypto (use Web Crypto API instead), or database drivers that require Node.js-specific APIs. For those operations, use Route Handlers which run in the Node.js runtime.

8. Deployment: Vercel vs Self-Hosted

Next.js can be deployed on any platform that supports Node.js. The two most common options are Vercel (the official platform) and self-hosting using the standalone output mode.

Vercel Deployment

# Deploying to Vercel is a single command
npx vercel

# Or push to main branch with Git integration
git push origin main  # Auto-deploys via GitHub integration

# Environment variables
# Set in: vercel.com/project/settings/environment-variables
# Or via CLI:
vercel env add DATABASE_URL production

Standalone Mode + Docker

// next.config.ts
const nextConfig: NextConfig = {
  output: "standalone", // Creates minimal server bundle
};

// Dockerfile (multi-stage build)
// Stage 1: Dependencies
// FROM node:20-alpine AS deps
// RUN apk add --no-cache libc6-compat
// WORKDIR /app
// COPY package.json package-lock.json ./
// RUN npm ci
//
// Stage 2: Builder
// FROM node:20-alpine AS builder
// WORKDIR /app
// COPY --from=deps /app/node_modules ./node_modules
// COPY . .
// ENV NEXT_TELEMETRY_DISABLED=1
// RUN npm run build
//
// Stage 3: Runner (minimal image ~150MB)
// FROM node:20-alpine AS runner
// WORKDIR /app
// ENV NODE_ENV=production
// COPY --from=builder /app/.next/standalone ./
// COPY --from=builder /app/.next/static ./.next/static
// COPY --from=builder /app/public ./public
// EXPOSE 3000
// CMD ["node", "server.js"]

Environment Variable Best Practices

# .env.local (never commit - for local dev only)
DATABASE_URL=postgresql://user:pass@localhost/mydb
NEXTAUTH_SECRET=local-dev-secret

# .env (safe defaults, can be committed)
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_ANALYTICS_ID=

// Accessing in code:

// Server-only (NOT prefixed with NEXT_PUBLIC_)
// Only accessible in Server Components, Route Handlers, Server Actions
const db = new PrismaClient({
  datasources: { db: { url: process.env.DATABASE_URL } }
});

// Client-accessible (NEXT_PUBLIC_ prefix)
// Inlined at build time into the browser bundle
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;

// Type-safe env with T3 Env
// npm install @t3-oss/env-nextjs zod
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    NEXTAUTH_SECRET: z.string().min(1),
  },
  client: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
  },
  runtimeEnv: process.env,
});

9. Framework Comparison: Next.js vs Remix vs Nuxt vs SvelteKit

Choosing a web framework is a major decision. Here is a detailed comparison of the four leading full-stack frameworks in 2026.

DimensionNext.js 15Remix 2Nuxt 3SvelteKit 2
Underlying FrameworkReact 19React 19Vue 3Svelte 5
Default RenderingRSC (Server)SSR (Server)SSR (Server)SSR (Server)
Data Fetchingfetch + cache / Server Componentsloader() / action()useFetch() / useAsyncData()load() in +page.server.ts
Form HandlingServer Actionsaction() (native forms)useFetch / Server Routesform actions (+page.server.ts)
Routing TypeFile system (app/)File system (routes/)File system (pages/)File system (+page.svelte)
StylingAny (Tailwind official)AnyAny (UnoCSS recommended)Scoped CSS (built-in)
TypeScript SupportExcellent (built-in)Excellent (built-in)Excellent (built-in)Excellent (built-in)
Bundle Size (framework)~85 KB (React runtime)~85 KB (React runtime)~100 KB (Vue runtime)~10-30 KB (compiled)
Learning CurveMedium (RSC concepts)Medium (web standards)Low-Medium (Vue ecosystem)Low (Svelte intuitive)
GitHub Stars~130K~30K~55K~19K
Best ForFull-stack React, enterpriseForm-heavy apps, web standardsVue teams, content sitesPerformance-first, lean teams
EcosystemVery large (React)Large (React)Large (Vue)Medium (growing)

When to Choose Remix

Remix's emphasis on web standards (Fetch API, FormData, HTTP semantics) makes its code more portable and less dependent on framework abstractions. If your team values progressive enhancement for forms (working without JavaScript), Remix is a strong choice.

When to Choose SvelteKit

Svelte eliminates the virtual DOM through a compiler, producing very small bundles. SvelteKit is ideal for projects that need the best runtime performance and small-to-medium teams that don't require the React ecosystem.

10. Frequently Asked Questions

Q1: What is the difference between App Router and Pages Router?

App Router uses React Server Components by default, nested layouts, streaming, and Server Actions. Pages Router uses the traditional getStaticProps/getServerSideProps pattern. App Router is recommended for new projects; existing apps can migrate incrementally.

Q2: When should I use the "use client" directive?

Use "use client" when a component needs: React hooks like useState/useEffect, event listeners like onClick/onChange, browser APIs (window, localStorage), or third-party client-side libraries. All other components should remain Server Components to reduce JS bundle size.

Q3: How does caching work in Next.js 15?

Next.js 15 changed default caching: fetch requests are no longer cached by default (changed from force-cache to no-store). Use cache: "force-cache" or next.revalidate to opt into caching explicitly. Route handlers are also uncached by default. This makes caching behavior more predictable.

Q4: How do React Server Components improve performance?

RSCs render on the server and send zero JavaScript to the client. This means: smaller JS bundles (component code is not bundled), faster FCP (server sends HTML directly), secure server-side data fetching (direct database access, API keys stay on server), and zero client re-renders for purely presentational content.

Q5: What are Server Actions and how do I use them?

Server Actions are async functions marked with the "use server" directive that run on the server. They can be called directly from form action attributes or event handlers. They are ideal for form submissions, database mutations, and file uploads without manually creating API routes.

Q6: What is ISR (Incremental Static Regeneration)?

ISR lets you update static pages after build without rebuilding the entire site. Use the next.revalidate option to set a revalidation interval in seconds, or use revalidatePath/revalidateTag to trigger on-demand revalidation. It combines the performance of static pages with the flexibility of dynamic content.

Q7: What can I use Next.js Middleware for?

Next.js Middleware runs before a request is completed and is suitable for: authentication checks and redirects, A/B testing (geolocation/cookies), rate limiting, request header modification, internationalization (locale-based redirects), and feature flags. Middleware runs on the Edge Runtime for minimal latency.

Q8: Should I deploy Next.js on Vercel or self-host?

Vercel offers the best Next.js integration with ISR, Edge Functions, Analytics, and zero-config deployments, and is suitable for most projects. Self-hosting (standalone output mode + Docker) is ideal when you need full infrastructure control, have compliance requirements, or are cost-sensitive. Both fully support all Next.js features.

11. Production Performance Optimization Checklist

Before shipping your Next.js app to production, check these optimizations to ensure peak performance.

Server Components

  • Maximize Server Component usage
  • Push "use client" to leaf nodes
  • Avoid importing large libs in Client Components
  • Use dynamic imports with React.lazy()

Images and Fonts

  • Use next/image for all images
  • Add priority prop to LCP images
  • Provide correct sizes attribute
  • Use next/font to eliminate CLS

Caching Strategy

  • Use force-cache for static content
  • Set appropriate revalidate for dynamic content
  • Cache DB queries with unstable_cache
  • Implement revalidateTag for on-demand invalidation

Bundle Size

  • Analyze with @next/bundle-analyzer
  • Use named imports (avoid import * from)
  • Lazy-load non-critical third-party scripts
  • Use next/script strategy for external scripts

Analyzing Bundle Size with Bundle Analyzer

# Install bundle analyzer
npm install @next/bundle-analyzer

// next.config.ts
import withBundleAnalyzer from "@next/bundle-analyzer";

const withAnalyzer = withBundleAnalyzer({
  enabled: process.env.ANALYZE === "true",
});

export default withAnalyzer(nextConfig);

# Generate and open the bundle analysis report
ANALYZE=true npm run build

# Example: Dynamic import for heavy charting library
import dynamic from "next/dynamic";

const HeavyChart = dynamic(
  () => import("@/components/HeavyChart"),
  {
    loading: () => <p>Loading chart...</p>,
    ssr: false, // Skip SSR for browser-only lib
  }
);

// Third-party script with strategy
import Script from "next/script";

export function Analytics() {
  return (
    <Script
      src="https://analytics.example.com/script.js"
      strategy="lazyOnload"  // afterInteractive | lazyOnload | beforeInteractive
      onLoad={() => console.log("Analytics loaded")}
    />
  );
}

Conclusion

Next.js 15 App Router represents a mature paradigm for full-stack React development. React Server Components reduce client-side JavaScript, Server Actions simplify data mutation flows, and built-in image, font optimization, and middleware make building production-grade applications more straightforward.

Key recommendations: start new projects with the App Router, use Server Components by default, and reach for "use client" only when interactivity is required. Replace simple API routes with Server Actions, and handle dynamic content with ISR (revalidate + revalidatePath). For deployment, Vercel offers the least configuration overhead, while standalone mode + Docker provides complete infrastructure control.

Related Tools on DevToolBox

Use DevToolBox's JSON Formatter to debug API responses, Regex Tester to validate route matcher patterns, and JWT Decoder to inspect tokens generated by Next-Auth.

𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

最新情報を受け取る

毎週の開発ヒントと新ツール情報。

スパムなし。いつでも解除可能。

Try These Related Tools

{ }JSON FormatterJWTJWT Decoder.*Regex Tester

Related Articles

Next.js App Router: 2026年完全移行ガイド

Next.js App Routerの包括的ガイド。Server Components、データフェッチ、レイアウト、ストリーミング、Server Actions、Pages Routerからの移行手順を解説。

Astro vs Next.js 2026:Islandsアーキテクチャ vs RSC

Astro と Next.js 2026 の詳細比較:islands、RSC、パフォーマンス、SEO。

React Hooks完全ガイド:useState、useEffect、カスタムHooks

実践的な例でReact Hooksをマスター。useState、useEffect、useContext、useReducer、useMemo、useCallback、カスタムHooks、React 18+の並行Hooksを学ぶ。