Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Next.js is a React framework for building full-stack web applications. For a new project, learn the App Router first: it is the current file-system router and uses React Server Components, Suspense, and Server Functions. Existing applications built with the Pages Router do not need an emergency rewrite; that router remains supported. This guide shows how to start, structure routes, choose server or client rendering, fetch fresh or cached data, stream slow work, secure the application, and verify a production build.

Choose the router before you write code

App Router for new work

The App Router uses an app directory. A route is created by folders, and a page.tsx file makes that route reachable. The root layout.tsx supplies shared HTML and UI. The Next.js documentation describes it as “a file-system based router that uses React’s latest features such as Server Components, Suspense, and Server Functions” (updated March 25, 2026).

Pages Router for established applications

The Pages Router uses a pages directory, with files such as pages/index.tsx and pages/api/*.ts. It remains supported in newer Next.js versions. Migrate route by route only when the App Router solves a concrete requirement; otherwise, keep the existing router and update it deliberately.

Decision Best fit Trade-off
App Router New applications and recent React features Requires learning server/client boundaries and new data patterns
Pages Router Existing production code and incremental maintenance Does not provide the App Router model for new routes

Create an application

The official quick start is create-next-app. Check the installation page for the requirements and defaults that match your installed version. The current canary installation source lists Node.js 20.9 as the minimum and supports macOS, Windows (including WSL), and Linux; these values can change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Install a supported Node.js release.
  2. Run npx create-next-app@latest my-app.
  3. Choose TypeScript, linting, and the App Router when prompted. The generated project includes an app directory and a root layout.
  4. Enter the directory and start development: cd my-app, then npm run dev.
  5. Open the local address printed by the command, edit app/page.tsx, and save to see Fast Refresh.

Use the package manager and lockfile selected by your team. Do not assume a tutorial’s defaults match a later Next.js release.

Understand the App Router file structure

app/
  layout.tsx        # required root layout
  page.tsx          # /
  loading.tsx       # loading UI for this segment
  error.tsx         # client error boundary
  blog/
    page.tsx        # /blog
    [slug]/
      page.tsx      # /blog/:slug
public/              # static files

Layouts persist while navigating between child routes. A dynamic segment such as [slug] receives route parameters. Keep route-specific data and UI near the route, but put shared access rules and data functions in dedicated server modules.

Server Components and Client Components

App Router components are Server Components by default. They execute on the server and do not require JavaScript in the browser merely to render HTML. Use them for database queries, private environment variables, and static presentation. Add 'use client' at the top of a file when that component needs browser APIs, event handlers, state, or effects.

// app/products/page.tsx — Server Component
import AddToCart from './AddToCart'

export default async function ProductsPage() {
  const products = await getProductsFromDatabase()
  return <AddToCart products={products} />
}
// app/products/AddToCart.tsx — Client Component
'use client'
import { useState } from 'react'

export default function AddToCart({ products }) {
  const [selected, setSelected] = useState(products[0]?.id)
  return (
    <button onClick={() => setSelected(products[0]?.id)}>
      Add product {selected}
    </button>
  )
}

Keep the boundary as low as practical. A page does not need to become entirely client-rendered because one button is interactive. Props crossing the boundary must be serializable, so perform secrets and database work on the server and pass only the data the browser needs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fetch data with an explicit freshness decision

Server Components can perform asynchronous I/O with fetch or an ORM/database client. Identical fetch requests in a component tree are memoized by default, preventing duplicate work during one render. That does not mean every request is persistently cached: current guidance says fetch requests are not cached by default. Verify behavior for your Next.js version, API, and deployment.

Request-time data

// app/news/page.tsx — App Router Server Component
export default async function NewsPage() {
  const response = await fetch('https://example.com/api/news', {
    cache: 'no-store'
  })
  if (!response.ok) throw new Error('News request failed')
  const stories = await response.json()
  return <NewsList stories={stories} />
}

Use request-time work for data that must be current. Authentication state, request headers, cookies, or other request-time APIs can also make a route dynamic. Treat that behavior as an intentional choice rather than an accidental performance setting.

Reusable or cached data

For content that can be reused, opt into the caching mechanism documented for your installed release, including the use cache directive where supported. Set an invalidation strategy that matches the business requirement: product copy can tolerate reuse, while an account balance usually cannot. The production checklist recommends inspecting every important request instead of applying one global rule.

Stream slow work instead of blocking the whole page

A slow upstream service can delay the first complete response. Streaming lets fast parts appear while a slow section is still pending; it does not accelerate the upstream service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// app/dashboard/page.tsx
import { Suspense } from 'react'
import SlowReport from './SlowReport'

export default function Dashboard() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading report…</p>}>
        <SlowReport />
      </Suspense>
    </main>
  )
}

An adjacent loading.tsx can provide a segment-level fallback. Place a Suspense boundary close to uncached or slow work when users benefit from a more granular loading state. Make the fallback meaningful: preserve layout and explain what is loading rather than showing an unrelated spinner.

Security is still your responsibility

  • Authenticate and authorize inside every Server Action; do not rely only on a proxy, layout, or page check.
  • Move database access into a server-only data-access layer. Never send credentials or privileged query results to a Client Component.
  • Keep .env.* files out of Git. Only variables intentionally exposed to browsers should use the NEXT_PUBLIC_ prefix.
  • Rate-limit expensive operations such as report generation, file processing, and external API calls.
  • Validate input at the server boundary, including values submitted by your own UI.

Metadata, accessibility, and search visibility

Use the Metadata API for page titles and descriptions. Add Open Graph images, a sitemap, and a robots file when they fit the site. These mechanisms help crawlers and link previews; they are not guarantees of search ranking.

Give controls accessible names, preserve keyboard focus, provide useful form errors, and check headings and contrast. Server rendering does not automatically make an interface accessible.

Production checklist

  1. Exercise success, not-found, unauthorized, and error states for every important route.
  2. Review use client boundaries and remove client code that does not need interactivity.
  3. Inspect request-time APIs, fetch caching, invalidation, and streaming fallbacks.
  4. Run type checking and linting; verify that environment variables are available only where intended.
  5. Analyze bundles and check Core Web Vitals on representative pages.
  6. Build and run the production output: next build, then next start. Test it with production-like environment variables and traffic patterns.

Or skip the browser setup

If your application needs automated page images for documentation, previews, visual regression, or social cards, you can capture the deployed URL with ScreenshotNeo instead of maintaining browser automation. It accepts consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One GET request returns PNG, JPEG, WebP, or PDF. The API supports full-page and selector captures, device and viewport settings, dark mode, custom CSS and JavaScript, waits, request blocking, cookies, headers, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous jobs, bulk capture, and usage reporting. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-next-app.example -o shot.webp

See the ScreenshotNeo documentation for parameters and response headers. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

“window is not defined”

The code is running in a Server Component. Move browser-only code into a small file marked 'use client', or replace it with a server-safe API.

A page is unexpectedly stale

Inspect the specific fetch options, cache directives, revalidation, and deployment cache. Do not infer behavior from an older tutorial that says all fetches are cached.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The first screen waits too long

Identify the slow request, then put it behind a nearby Suspense boundary or loading.tsx. Streaming changes delivery order, not upstream latency.

Secrets appear in browser code

Check imports from Client Components and environment variable names. Remove privileged modules from the client graph and reserve NEXT_PUBLIC_ for deliberately public values.

Development works but production fails

Run next build and next start locally with production-like variables. Check case-sensitive paths, dynamic rendering assumptions, error boundaries, and external service permissions.

Frequently Asked Questions

Can I use the Pages Router and App Router in one project?

Yes, Next.js supports a gradual, project-specific transition. Keep existing Pages Router routes while introducing App Router routes where that provides a clear benefit, and verify shared layouts and data behavior carefully.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does streaming make an API faster?

No. Streaming sends completed portions earlier while a slow portion waits; the upstream operation itself still takes the same time.

Where should a database query live?

Keep it in a server-only data-access module or Server Component. Expose only validated, necessary results to Client Components.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.