Next.jsReactWeb Development

Getting Started with Next.js: A Complete Guide for 2026

Adrijan Omičević··13 min read
Share

Getting Started with Next.js: A Complete Guide for 2026

Next.js has established itself as the go-to React framework for building production-grade web applications. Whether you're creating a marketing site, a SaaS dashboard, or an e-commerce platform, Next.js provides the tools to ship fast, SEO-friendly, and scalable products without wrestling with complex configurations.

In this comprehensive guide, we'll walk through everything you need to know to start building with Next.js — from initial project setup to deployment.

# Why Choose Next.js?#

Before diving into code, let's understand what makes Next.js stand out from other React frameworks and why thousands of companies — from startups to enterprises — rely on it.

Server-Side Rendering and Static Generation#

Traditional single-page applications (SPAs) built with plain React send an empty HTML shell to the browser, then JavaScript takes over to render everything client-side. This approach has two significant problems:

  1. 1
    Poor SEO — Search engines receive empty HTML with no content to index
  2. 2
    Slow initial load — Users stare at a blank screen while JavaScript downloads and executes

Next.js solves both problems with multiple rendering strategies:

  • Static Site Generation (SSG) — Pages are pre-rendered at build time, resulting in lightning-fast load times and perfect SEO. Ideal for blogs, landing pages, and documentation.
  • Server-Side Rendering (SSR) — Pages are rendered on each request, ensuring fresh data while still delivering full HTML to search engines and users.
  • Incremental Static Regeneration (ISR) — Combines the speed of static pages with the freshness of server rendering by revalidating cached pages in the background.

File-Based Routing#

Instead of configuring a router library and manually defining routes, Next.js uses your file system structure. Create a file at app/about/page.js, and you automatically have a /about route. This convention-over-configuration approach eliminates boilerplate and makes your app's structure immediately understandable.

Built-In API Routes#

Need a backend endpoint? Create a file in app/api/ and export HTTP method handlers. No separate server needed. This is perfect for form handling, webhooks, authentication endpoints, and lightweight APIs.

React Server Components#

Next.js fully embraces React Server Components (RSC), allowing you to render components on the server without sending their JavaScript to the client. This means smaller bundle sizes, faster page loads, and the ability to directly access databases and file systems from your components.

⚠️ Warning: RSC changes how you think about state management. You can't use hooks like useState or useEffect in Server Components. These features only work in Client Components (marked with 'use client'). This learning curve can surprise developers coming from traditional React, so plan your team training accordingly.

Image and Font Optimization#

The built-in next/image component automatically optimizes images with lazy loading, responsive sizing, and modern formats (WebP, AVIF). Similarly, next/font handles font loading with zero layout shift.

# Setting Up Your First Project#

Let's create a new Next.js application. You'll need Node.js 18.17 or later installed on your machine.

Creating the Project#

Open your terminal and run:

Bash
npx create-next-app@latest my-app

The CLI will ask you several questions:

Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like your code inside a `src/` directory? Yes
Would you like to use App Router? Yes
Would you like to use Turbopack for `next dev`? Yes
Would you like to customize the import alias? No

We recommend answering Yes to all of these for the best development experience.

💡 Tip: TypeScript catches bugs before they reach production. While optional, it's highly recommended for any professional Next.js project. The initial setup time pays dividends through better IDE support, refactoring safety, and catching type errors early.

Project Structure#

After creation, your project structure looks like this:

my-app/
├── src/
│   ├── app/
│   │   ├── layout.js      # Root layout (wraps all pages)
│   │   ├── page.js         # Homepage (/)
│   │   └── globals.css     # Global styles
│   └── ...
├── public/                  # Static assets
├── next.config.js           # Next.js configuration
├── tailwind.config.js       # Tailwind CSS configuration
├── tsconfig.json            # TypeScript configuration
└── package.json

Running the Development Server#

Navigate to your project directory and start the development server:

Bash
cd my-app
npm run dev

Open http://localhost:3000 in your browser. You should see the Next.js welcome page. The development server features hot module replacement (HMR), so any changes you save will appear instantly in the browser.

# Understanding the App Router#

The App Router, introduced in Next.js 13 and now the default in Next.js 16, represents a paradigm shift in how you build React applications. It's built on top of React Server Components and introduces several powerful concepts.

Pages and Layouts#

Every route needs a page.js file that exports a React component:

JSX
// src/app/page.js — Homepage
export default function Home() {
  return (
    <main>
      <h1>Welcome to My App</h1>
      <p>This is the homepage.</p>
    </main>
  );
}

Layouts wrap pages and persist across navigations, making them perfect for shared UI like navigation bars and footers:

JSX
// src/app/layout.js — Root layout
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <nav>My App</nav>
        {children}
        <footer>© 2026</footer>
      </body>
    </html>
  );
}

Nested Routes#

Create folders to build nested routes. For example:

src/app/
├── page.js              → /
├── about/
│   └── page.js          → /about
├── blog/
│   ├── page.js          → /blog
│   └── [slug]/
│       └── page.js      → /blog/my-post
└── dashboard/
    ├── layout.js         → Shared dashboard layout
    ├── page.js           → /dashboard
    └── settings/
        └── page.js       → /dashboard/settings

Dynamic Routes#

Use square brackets for dynamic segments:

JSX
// src/app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
  const { slug } = await params;

  // Fetch post data using the slug
  const post = await getPost(slug);

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

Loading and Error States#

Next.js provides special files for handling loading and error states:

JSX
// src/app/blog/loading.js — Shows while data loads
export default function Loading() {
  return <div>Loading blog posts...</div>;
}

// src/app/blog/error.js — Shows on error
'use client';
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

# Data Fetching in Next.js#

Data fetching in Next.js is straightforward because Server Components can be async functions. This means you can await data directly in your components — no useEffect hooks, no loading state management, no client-side data fetching libraries needed for initial data.

Fetching Data in Server Components#

JSX
// This component runs on the server
export default async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600 }, // Revalidate every hour
  }).then(res => res.json());

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>{product.name} — ${product.price}</li>
      ))}
    </ul>
  );
}

Static vs. Dynamic Rendering#

By default, Next.js statically renders routes at build time. If your page fetches data with cache: 'no-store' or uses dynamic functions like cookies() or headers(), it switches to dynamic rendering automatically.

JSX
// Static: Rendered at build time, revalidated every hour
const data = await fetch('https://api.example.com/data', {
  next: { revalidate: 3600 },
});

// Dynamic: Rendered on every request
const data = await fetch('https://api.example.com/data', {
  cache: 'no-store',
});

ℹ️ Note: Next.js caches fetch requests intelligently. If you don't specify cache options, Next.js treats the request as static by default. This means your API data gets cached even if you didn't explicitly ask for it. Always be explicit about your caching strategy to avoid stale data surprises.

Server Actions#

Server Actions let you define server-side functions that can be called directly from client components. They're perfect for form submissions and data mutations:

JSX
// src/app/contact/page.js
export default function ContactPage() {
  async function submitForm(formData) {
    'use server';
    const name = formData.get('name');
    const email = formData.get('email');
    const message = formData.get('message');

    await saveToDatabase({ name, email, message });
    await sendNotificationEmail({ name, email, message });
  }

  return (
    <form action={submitForm}>
      <input name="name" placeholder="Your name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button type="submit">Send</button>
    </form>
  );
}

# Styling Your Application#

Next.js supports multiple styling approaches out of the box.

Tailwind CSS#

Tailwind CSS is the recommended styling solution and comes pre-configured when you select it during project setup:

JSX
export default function Card({ title, description }) {
  return (
    <div className="rounded-lg border border-gray-200 p-6 shadow-sm hover:shadow-md transition-shadow">
      <h3 className="text-xl font-semibold mb-2">{title}</h3>
      <p className="text-gray-600">{description}</p>
    </div>
  );
}

CSS Modules#

For component-scoped CSS without any runtime overhead:

CSS
/* Button.module.css */
.button {
  padding: 12px 24px;
  font-weight: 600;
  border: none;
  cursor: pointer;
}

.primary {
  background: #0070f3;
  color: white;
}
JSX
import styles from './Button.module.css';

export default function Button({ children, variant = 'primary' }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

# SEO and Metadata#

Next.js provides a powerful Metadata API for managing SEO tags. Export a metadata object or generateMetadata function from any page:

JSX
// Static metadata
export const metadata = {
  title: 'About Us | My App',
  description: 'Learn about our team and mission.',
  openGraph: {
    title: 'About Us | My App',
    description: 'Learn about our team and mission.',
    images: ['/og-about.png'],
  },
};

// Dynamic metadata
export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);

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

Structured Data (JSON-LD)#

For rich search results, add structured data to your pages:

JSX
export default function BlogPost({ post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    datePublished: post.date,
    author: {
      '@type': 'Person',
      name: post.author,
    },
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{/* Post content */}</article>
    </>
  );
}

# Performance Optimization#

Next.js includes many performance optimizations out of the box, but here are additional techniques to maximize your scores.

Image Optimization#

Always use the next/image component instead of plain <img> tags:

JSX
import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero banner showing our product"
      width={1200}
      height={630}
      priority // Preload above-the-fold images
      sizes="(max-width: 768px) 100vw, 1200px"
    />
  );
}

The next/image component automatically:

  • Serves images in WebP/AVIF format when the browser supports it
  • Lazy loads images below the fold
  • Generates responsive srcset for different screen sizes
  • Prevents Cumulative Layout Shift (CLS)

Font Optimization#

Use next/font to self-host fonts with zero layout shift:

JSX
import { Inter } from 'next/font/google';

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

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

Dynamic Imports#

Reduce initial bundle size by dynamically importing heavy components:

JSX
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false, // Skip server-side rendering for client-only components
});

# Deployment#

Next.js applications can be deployed anywhere that supports Node.js, but the most seamless experience is with Vercel (the company behind Next.js).

Deploying to Vercel#

  1. 1
    Push your code to GitHub, GitLab, or Bitbucket
  2. 2
    Go to vercel.com and import your repository
  3. 3
    Vercel automatically detects Next.js and configures the build
  4. 4
    Every push to your main branch triggers a production deployment
  5. 5
    Pull requests get preview deployments with unique URLs

Self-Hosting#

For self-hosting, build and start your application:

Bash
npm run build
npm run start

Or use Docker:

Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
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"]

Enable standalone output in your next.config.js:

JavaScript
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};

export default nextConfig;

# Common Patterns and Best Practices#

Authentication#

Use middleware to protect routes:

JavaScript
// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('auth-token');

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

  return NextResponse.next();
}

export const config = {
  matcher: '/dashboard/:path*',
};

Internationalization (i18n)#

For multi-language sites, use a library like next-intl with the App Router:

src/app/
└── [locale]/
    ├── layout.js
    ├── page.js
    └── about/
        └── page.js

This creates routes like /en, /en/about, /hr, /hr/about — each serving content in the appropriate language.

Error Boundaries#

Create error.js files at each route segment to catch and handle errors gracefully. This prevents a single failing component from crashing your entire application.

# What's New in Next.js 16#

Next.js 16 brings several improvements:

  • Turbopack stable — The Rust-based bundler is now the default for next dev, offering significantly faster hot module replacement
  • React 19 support — Full integration with the latest React features including improved Server Components and Actions
  • Improved caching — More predictable caching behavior with explicit opt-in strategies
  • Partial Prerendering — Combine static shells with dynamic content for the best of both worlds

# Key Takeaways#

  • Multiple rendering strategies: Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR) give you flexibility to optimize for performance and freshness on a per-page basis.
  • App Router advantages: The file-based routing system with Server Components, layouts, and nested routes makes building scalable applications straightforward and intuitive.
  • Server Components impact: RSC eliminates the need for client-side data fetching libraries and reduces JavaScript bundle size, but requires a mental model shift from traditional React patterns.
  • Built-in optimizations: Image optimization, font loading, dynamic imports, and intelligent caching come automatically, delivering better performance without extra configuration.
  • Multiple deployment options: Whether you choose Vercel, self-hosting with Docker, or any Node.js environment, Next.js adapts to your infrastructure needs.

# Conclusion#

Next.js provides everything you need to build modern web applications — from routing and data fetching to SEO optimization and deployment. Its convention-over-configuration approach means less boilerplate and more time building features that matter to your users.

The framework's focus on performance (through Server Components, static generation, and built-in optimizations) ensures that your applications are fast by default, while its flexibility allows you to choose the right rendering strategy for each page.

Ready to start your Next.js project? Our Next.js development team builds SEO-optimized web applications with server-side rendering and Lighthouse 100 scores. Get a free estimate and let's discuss how we can help you build a fast, scalable web application.

Share
A
Adrijan OmičevićSamioda Team
All articles →

Need help with your project?

We build custom solutions using the technologies discussed in this article. Senior team, fixed prices.