Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Streaming Suspense
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Streaming and Suspense

Progressive rendering with React Suspense

Streaming and Suspense enable progressive rendering—showing parts of your page immediately while others load. Instead of waiting for all data before showing anything, stream HTML to the browser as it's ready. Use React Suspense boundaries to show loading states for async components. Master streaming and create fluid, responsive user experiences with instant feedback!

What is Streaming?

Traditional Server Rendering:

  1. Server fetches ALL data
  2. Server renders COMPLETE HTML
  3. Server sends HTML to browser
  4. Browser shows page (all at once)
  5. Total wait time: Sum of all data fetching + rendering

Streaming Server Rendering:

  1. Server starts rendering immediately
  2. Server sends HTML chunks as they're ready
  3. Browser shows content progressively
  4. Slower parts load in their Suspense boundaries
  5. First content visible: Almost immediately!

Benefits of Streaming

  • Faster Time to First Byte (TTFB): Browser receives HTML sooner
  • Progressive Enhancement: Show important content first
  • Better Perceived Performance: Users see content loading
  • Non-Blocking: Slow data doesn't block fast content
  • SEO-Friendly: Search engines see content immediately

React Suspense Basics

Simple Suspense Boundary

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

// Async Server Component
async function UserProfile() {
  // Simulate slow data fetching
  const user = await fetch('https://api.example.com/user').then(res => res.json());
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// Loading fallback
function UserProfileSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-8 w-48 bg-gray-200 rounded mb-2"></div>
      <div className="h-4 w-32 bg-gray-200 rounded"></div>
    </div>
  );
}

// Page with Suspense
export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Suspense boundary */}
      <Suspense fallback={<UserProfileSkeleton />}>
        <UserProfile />
      </Suspense>
    </div>
  );
}

// ✅ Page renders immediately
// ✅ Shows skeleton while UserProfile loads
// ✅ UserProfile streams in when ready

Multiple Suspense Boundaries

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

async function UserProfile() {
  const user = await fetch('/api/user', { cache: 'no-store' }).then(r => r.json());
  return <div>User: {user.name}</div>;
}

async function RecentActivity() {
  // This takes longer
  await new Promise(resolve => setTimeout(resolve, 2000));
  const activity = await fetch('/api/activity').then(r => r.json());
  return <div>Activity: {activity.count} items</div>;
}

async function Statistics() {
  const stats = await fetch('/api/stats').then(r => r.json());
  return <div>Stats: {stats.total}</div>;
}

function LoadingSkeleton() {
  return <div className="animate-pulse h-20 bg-gray-200 rounded"></div>;
}

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Each component loads independently */}
      <Suspense fallback={<LoadingSkeleton />}>
        <UserProfile />
      </Suspense>
      
      <Suspense fallback={<LoadingSkeleton />}>
        <RecentActivity />
      </Suspense>
      
      <Suspense fallback={<LoadingSkeleton />}>
        <Statistics />
      </Suspense>
    </div>
  );
}

// ✅ Page shows immediately
// ✅ Each section loads independently
// ✅ Fast sections don't wait for slow ones
// ✅ Progressive loading experience

Nested Suspense Boundaries

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

async function Header() {
  const data = await fetch('/api/header').then(r => r.json());
  return <header>{data.title}</header>;
}

async function Sidebar() {
  const data = await fetch('/api/sidebar').then(r => r.json());
  return <aside>{data.content}</aside>;
}

async function MainContent() {
  const data = await fetch('/api/content').then(r => r.json());
  return <main>{data.body}</main>;
}

export default function Page() {
  return (
    <div>
      {/* Outer boundary for entire page */}
      <Suspense fallback={<div>Loading page...</div>}>
        
        {/* Inner boundary for header */}
        <Suspense fallback={<div>Loading header...</div>}>
          <Header />
        </Suspense>
        
        <div className="flex">
          {/* Inner boundary for sidebar */}
          <Suspense fallback={<div>Loading sidebar...</div>}>
            <Sidebar />
          </Suspense>
          
          {/* Inner boundary for main content */}
          <Suspense fallback={<div>Loading content...</div>}>
            <MainContent />
          </Suspense>
        </div>
      </Suspense>
    </div>
  );
}

// ✅ Nested boundaries
// ✅ Granular loading states
// ✅ Each section independent

Loading UI Patterns

Skeleton Loaders

app/components/LoadingSkeleton.tsx
export function CardSkeleton() {
  return (
    <div className="border rounded-lg p-6 animate-pulse">
      <div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
      <div className="h-4 bg-gray-200 rounded w-1/2 mb-4"></div>
      <div className="h-4 bg-gray-200 rounded w-5/6"></div>
    </div>
  );
}

export function ListSkeleton({ count = 3 }: { count?: number }) {
  return (
    <div className="space-y-4">
      {Array.from({ length: count }).map((_, i) => (
        <div key={i} className="flex items-center space-x-4 animate-pulse">
          <div className="h-12 w-12 bg-gray-200 rounded-full"></div>
          <div className="flex-1 space-y-2">
            <div className="h-4 bg-gray-200 rounded w-3/4"></div>
            <div className="h-4 bg-gray-200 rounded w-1/2"></div>
          </div>
        </div>
      ))}
    </div>
  );
}

export function TableSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-12 bg-gray-200 rounded mb-4"></div>
      {Array.from({ length: 5 }).map((_, i) => (
        <div key={i} className="h-16 bg-gray-100 rounded mb-2"></div>
      ))}
    </div>
  );
}

// ✅ Reusable skeleton components
// ✅ Match layout of actual content
// ✅ Smooth loading experience

Using Skeleton Loaders

app/dashboard/page.tsx
import { Suspense } from 'react';
import { CardSkeleton, ListSkeleton } from '@/components/LoadingSkeleton';

async function UserCard() {
  const user = await fetchUser();
  return (
    <div className="border rounded-lg p-6">
      <h2 className="text-xl font-bold">{user.name}</h2>
      <p className="text-gray-600">{user.email}</p>
      <p className="text-sm">{user.bio}</p>
    </div>
  );
}

async function ActivityList() {
  const activities = await fetchActivities();
  return (
    <div className="space-y-4">
      {activities.map(activity => (
        <div key={activity.id} className="flex items-center space-x-4">
          <img 
            src={activity.avatar} 
            alt={activity.user}
            className="h-12 w-12 rounded-full" 
          />
          <div>
            <p className="font-medium">{activity.user}</p>
            <p className="text-sm text-gray-600">{activity.action}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      <Suspense fallback={<CardSkeleton />}>
        <UserCard />
      </Suspense>
      
      <Suspense fallback={<ListSkeleton count={5} />}>
        <ActivityList />
      </Suspense>
    </div>
  );
}

// ✅ Skeleton matches actual content layout
// ✅ Smooth visual transition
// ✅ Better UX than spinners

Spinner Loaders

app/components/Spinner.tsx
export function Spinner({ size = 'md' }: { size?: 'sm' | 'md' | 'lg' }) {
  const sizeClasses = {
    sm: 'h-4 w-4',
    md: 'h-8 w-8',
    lg: 'h-12 w-12',
  };
  
  return (
    <div className="flex items-center justify-center">
      <div
        className={`${sizeClasses[size]} border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin`}
      />
    </div>
  );
}

export function SpinnerWithText({ text = 'Loading...' }: { text?: string }) {
  return (
    <div className="flex flex-col items-center justify-center py-12">
      <Spinner />
      <p className="mt-4 text-gray-600">{text}</p>
    </div>
  );
}

// Use for full-page or section loading
export function PageSpinner() {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <SpinnerWithText text="Loading page..." />
    </div>
  );
}

// ✅ Reusable spinner components
// ✅ Different sizes
// ✅ Optional text

Route-Level Loading UI

loading.tsx File

app/dashboard/loading.tsx
export default function Loading() {
  return (
    <div className="animate-pulse">
      <div className="h-8 bg-gray-200 rounded w-48 mb-8"></div>
      
      <div className="grid grid-cols-3 gap-6">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="border rounded-lg p-6">
            <div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
            <div className="h-4 bg-gray-200 rounded w-1/2"></div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ✅ Automatic loading UI for route
// ✅ Shows while page.tsx loads
// ✅ Wraps entire page in Suspense automatically

How loading.tsx Works

TYPESCRIPT
// File structure:
app/
  dashboard/
    loading.tsx    // Loading UI
    page.tsx       // Page component

// Next.js automatically creates:
<Suspense fallback={<Loading />}>
  <Page />
</Suspense>

// ✅ No manual Suspense needed
// ✅ Entire route wrapped automatically
// ✅ loading.tsx applies to page.tsx and all nested routes

Nested Loading States

TYPESCRIPT
// File structure:
app/
  dashboard/
    loading.tsx         // Dashboard loading
    page.tsx            // Dashboard page
    settings/
      loading.tsx       // Settings loading (more specific)
      page.tsx          // Settings page

// Navigation to /dashboard → Shows dashboard/loading.tsx
// Navigation to /dashboard/settings → Shows dashboard/settings/loading.tsx

// ✅ More specific loading.tsx overrides parent
// ✅ Granular control over loading states

Streaming Patterns

Parallel Data Fetching with Suspense

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

// These fetch in parallel
async function UserStats() {
  const stats = await fetch('/api/stats').then(r => r.json());
  return <div>Total: {stats.total}</div>;
}

async function RecentOrders() {
  const orders = await fetch('/api/orders').then(r => r.json());
  return <div>Orders: {orders.length}</div>;
}

async function Revenue() {
  const revenue = await fetch('/api/revenue').then(r => r.json());
  return <div>Revenue: ${revenue.amount}</div>;
}

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      <div className="grid grid-cols-3 gap-6">
        {/* All three components fetch in parallel */}
        <Suspense fallback={<LoadingSkeleton />}>
          <UserStats />
        </Suspense>
        
        <Suspense fallback={<LoadingSkeleton />}>
          <RecentOrders />
        </Suspense>
        
        <Suspense fallback={<LoadingSkeleton />}>
          <Revenue />
        </Suspense>
      </div>
    </div>
  );
}

// ✅ Three API calls happen simultaneously
// ✅ Each component streams when ready
// ✅ No waterfall - all parallel

Preloading Pattern

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

// Preload function (starts fetch immediately)
function preloadUser() {
  return fetch('/api/user').then(r => r.json());
}

function preloadStats() {
  return fetch('/api/stats').then(r => r.json());
}

async function UserProfile({ userPromise }: { userPromise: Promise<any> }) {
  // Wait for preloaded data
  const user = await userPromise;
  return <div>{user.name}</div>;
}

async function Stats({ statsPromise }: { statsPromise: Promise<any> }) {
  const stats = await statsPromise;
  return <div>{stats.total}</div>;
}

export default function DashboardPage() {
  // Start fetching immediately (before Suspense)
  const userPromise = preloadUser();
  const statsPromise = preloadStats();
  
  return (
    <div>
      <h1>Dashboard</h1>
      
      <Suspense fallback={<div>Loading user...</div>}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
      
      <Suspense fallback={<div>Loading stats...</div>}>
        <Stats statsPromise={statsPromise} />
      </Suspense>
    </div>
  );
}

// ✅ Fetches start immediately
// ✅ No waiting for Suspense
// ✅ Faster data loading

Grouping Slow Operations

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

// Fast components (no Suspense needed)
async function Header() {
  const data = await fetch('/api/header', { cache: 'force-cache' }).then(r => r.json());
  return <header>{data.title}</header>;
}

async function Navigation() {
  const items = await fetch('/api/nav', { cache: 'force-cache' }).then(r => r.json());
  return <nav>{items.map(i => <a key={i.id} href={i.href}>{i.label}</a>)}</nav>;
}

// Slow component (needs Suspense)
async function DynamicContent() {
  // This takes 2+ seconds
  const data = await fetch('/api/content', { cache: 'no-store' }).then(r => r.json());
  return <main>{data.body}</main>;
}

export default function Page() {
  return (
    <div>
      {/* Fast content renders immediately */}
      <Header />
      <Navigation />
      
      {/* Only slow content in Suspense */}
      <Suspense fallback={<div>Loading content...</div>}>
        <DynamicContent />
      </Suspense>
    </div>
  );
}

// ✅ Fast content shows immediately
// ✅ Only slow content suspended
// ✅ Optimal user experience

Streaming and Suspense Structure

Organization of components with Suspense boundaries

appImportant

Select a file or folder to see details

Advanced Streaming Patterns

Conditional Suspense

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

async function PremiumFeature() {
  const data = await fetch('/api/premium').then(r => r.json());
  return <div>Premium Content: {data.content}</div>;
}

export default async function DashboardPage() {
  // Check user status (fast)
  const user = await fetch('/api/user', { cache: 'force-cache' }).then(r => r.json());
  
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome, {user.name}!</p>
      
      {/* Conditionally show Suspense */}
      {user.isPremium ? (
        <Suspense fallback={<div>Loading premium features...</div>}>
          <PremiumFeature />
        </Suspense>
      ) : (
        <div>Upgrade to premium for more features!</div>
      )}
    </div>
  );
}

// ✅ Check conditions before Suspense
// ✅ Only fetch if needed
// ✅ Better performance

Error Boundaries with Suspense

app/components/ErrorBoundary.tsx
'use client';

import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
}

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="text-red-600">
          Something went wrong. Please try again.
        </div>
      );
    }
    
    return this.props.children;
  }
}
app/dashboard/page.tsx
import { Suspense } from 'react';
import { ErrorBoundary } from '@/components/ErrorBoundary';

async function RiskyComponent() {
  const data = await fetch('/api/risky').then(r => {
    if (!r.ok) throw new Error('Failed to fetch');
    return r.json();
  });
  return <div>{data.content}</div>;
}

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Error boundary + Suspense */}
      <ErrorBoundary fallback={<div>Failed to load. Try refreshing.</div>}>
        <Suspense fallback={<div>Loading...</div>}>
          <RiskyComponent />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

// ✅ Handles errors gracefully
// ✅ Shows error UI instead of crashing
// ✅ Suspense for loading, ErrorBoundary for errors

Streaming with Dynamic Routes

app/posts/[id]/page.tsx
import { Suspense } from 'react';

async function Post({ id }: { id: string }) {
  const post = await fetch(`/api/posts/${id}`).then(r => r.json());
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

async function Comments({ postId }: { postId: string }) {
  // Slower query
  const comments = await fetch(`/api/posts/${postId}/comments`).then(r => r.json());
  return (
    <div>
      <h2>Comments ({comments.length})</h2>
      {comments.map(c => (
        <div key={c.id}>{c.text}</div>
      ))}
    </div>
  );
}

async function RelatedPosts({ postId }: { postId: string }) {
  const related = await fetch(`/api/posts/${postId}/related`).then(r => r.json());
  return (
    <div>
      <h2>Related Posts</h2>
      {related.map(p => (
        <a key={p.id} href={`/posts/${p.id}`}>{p.title}</a>
      ))}
    </div>
  );
}

export default function PostPage({ params }: { params: { id: string } }) {
  return (
    <div>
      {/* Main post (fast) */}
      <Suspense fallback={<div>Loading post...</div>}>
        <Post id={params.id} />
      </Suspense>
      
      {/* Comments (slower) */}
      <Suspense fallback={<div>Loading comments...</div>}>
        <Comments postId={params.id} />
      </Suspense>
      
      {/* Related posts (slowest) */}
      <Suspense fallback={<div>Loading related posts...</div>}>
        <RelatedPosts postId={params.id} />
      </Suspense>
    </div>
  );
}

// ✅ Post content shows first
// ✅ Comments stream in next
// ✅ Related posts last
// ✅ Progressive experience

Streaming Best Practices

1. Use Multiple Suspense Boundaries

TYPESCRIPT
// ✅ GOOD: Multiple boundaries
<div>
  <Suspense fallback={<UserSkeleton />}>
    <UserProfile />
  </Suspense>
  
  <Suspense fallback={<ActivitySkeleton />}>
    <RecentActivity />
  </Suspense>
</div>

// ❌ BAD: Single boundary for everything
<Suspense fallback={<div>Loading...</div>}>
  <UserProfile />
  <RecentActivity />
  <Statistics />
  <Comments />
</Suspense>

// Single boundary waits for ALL components
// Use multiple for independent loading

2. Match Skeleton to Actual Content

TYPESCRIPT
// ✅ GOOD: Skeleton matches layout
function UserCardSkeleton() {
  return (
    <div className="border rounded-lg p-6">
      <div className="h-12 w-12 bg-gray-200 rounded-full mb-4"></div>
      <div className="h-6 bg-gray-200 rounded w-3/4 mb-2"></div>
      <div className="h-4 bg-gray-200 rounded w-1/2"></div>
    </div>
  );
}

// ❌ BAD: Generic spinner
function Loading() {
  return <div className="spinner"></div>;
}

// Skeleton provides layout stability
// Prevents content shift when loading completes

3. Stream Critical Content First

TYPESCRIPT
// ✅ GOOD: Critical content outside Suspense
export default function Page() {
  return (
    <div>
      {/* Critical: No Suspense */}
      <Header />
      <Navigation />
      
      {/* Non-critical: In Suspense */}
      <Suspense fallback={<Skeleton />}>
        <Recommendations />
      </Suspense>
    </div>
  );
}

// Show important content immediately
// Suspend less important parts

4. Avoid Suspense for Fast Operations

TYPESCRIPT
// ✅ GOOD: Only suspend slow operations
async function FastData() {
  const data = await fetch('/api/fast', {
    cache: 'force-cache', // Instant
  }).then(r => r.json());
  return <div>{data.content}</div>;
}

async function SlowData() {
  const data = await fetch('/api/slow', {
    cache: 'no-store', // 2+ seconds
  }).then(r => r.json());
  return <div>{data.content}</div>;
}

export default function Page() {
  return (
    <div>
      {/* No Suspense for fast data */}
      <FastData />
      
      {/* Suspense for slow data */}
      <Suspense fallback={<Skeleton />}>
        <SlowData />
      </Suspense>
    </div>
  );
}

// Don't add Suspense overhead for fast operations

5. Preload Data When Possible

TYPESCRIPT
// ✅ GOOD: Start fetching early
export default function Page() {
  // Start fetching immediately
  const dataPromise = fetch('/api/data').then(r => r.json());
  
  return (
    <div>
      <Suspense fallback={<Skeleton />}>
        <AsyncComponent dataPromise={dataPromise} />
      </Suspense>
    </div>
  );
}

// Fetch starts before Suspense
// Reduces wait time

Key Takeaways

  • Streaming - send HTML progressively as it's ready
  • Suspense boundaries - show fallback while async content loads
  • loading.tsx - automatic route-level loading UI
  • Multiple boundaries - independent loading per component
  • Skeleton loaders - match actual content layout
  • Parallel fetching - Suspense enables parallel data loading
  • Progressive rendering - fast content first, slow content streams
  • Better UX - users see content loading, not blank pages

What's Next?

You've mastered streaming and Suspense! Next, we'll explore Not Found and Global Error Pages—creating custom 404 pages with not-found.tsx, building global error handlers, implementing error recovery, and handling different error scenarios gracefully. You'll complete your application's error handling strategy!

We'll cover not-found.tsx, global-error.tsx, error.tsx, and complete error handling patterns.

⚡ Performance Tip

Use Suspense strategically: wrap slow operations but not fast ones, use multiple boundaries for independent loading, match skeleton loaders to actual content layout, and stream critical content first. Streaming dramatically improves perceived performance!

Test Your Understanding

Question 1 of 4

What is streaming in Next.js?

Master streaming and React Suspense in Next.js! Learn progressive rendering and loading patterns.

Previous
Environment Variables and Configuration
Next
Not Found and Global Error Pages

Master Next.js Performance

Join 2,000+ developers building performant Next.js apps. Get the final lesson on error pages - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo