Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🟣 Data, Server & APIs  ·  Lesson 19

Data Fetching & Caching

Fetching is just async/await

In the App Router you do not need useEffect or loading state for most data. Make the Server Component async and await your data — the HTML arrives already filled, which is perfect for SEO.

JSX – async Server Component
// app/page.jsx — a Server Component can be async
async function getStats() {
  return { students: 1200, courses: 6, rating: 4.8 }   // could be a DB call
}

export default async function Home() {
  const stats = await getStats()
  return <p>{stats.students}+ students · {stats.courses} courses · ⭐ {stats.rating}</p>
}

Old React way (client)

  • Empty HTML first
  • useEffect → fetch in browser
  • Loading spinner
  • Google may see no content

Next.js Server Component

  • Data fetched on server
  • HTML arrives complete
  • No spinner needed
  • Perfect for SEO
Why server fetching is better for content pages

Fetching from an API

JSX – app/jobs/page.jsx
// app/jobs/page.jsx
async function getJobs() {
  const res = await fetch('https://api.example.com/jobs?city=delhi')
  if (!res.ok) throw new Error('Failed to load jobs')   // → error.jsx
  return res.json()
}

export default async function Jobs() {
  const jobs = await getJobs()
  return (
    <ul>
      {jobs.map((j) => <li key={j.id}>{j.title} – {j.company}</li>)}
    </ul>
  )
}

Reading local files (Markdown blog)

Server Components can use Node.js modules. EduSite's blog reads .md files from content/posts/:

Markdown – content/posts/how-to-start-coding.md
---
title: How to Start Coding in 2026
date: 2026-03-10
excerpt: A simple 30-day plan for complete beginners.
---

## Day 1–7: Pick one language
Start with **Python** or **C** …
JS – lib/posts.js
// lib/posts.js  (from the EduSite project)
import fs from 'node:fs'
import path from 'node:path'
import matter from 'gray-matter'       // reads the --- front matter ---
import { marked } from 'marked'        // converts Markdown → HTML

const POSTS_DIR = path.join(process.cwd(), 'content', 'posts')

export function getAllPosts() {
  return fs.readdirSync(POSTS_DIR)
    .filter((f) => f.endsWith('.md'))
    .map((file) => {
      const slug = file.replace(/\.md$/, '')
      const { data } = matter(fs.readFileSync(path.join(POSTS_DIR, file), 'utf8'))
      return { slug, title: data.title, date: data.date, excerpt: data.excerpt }
    })
    .sort((a, b) => (a.date < b.date ? 1 : -1))   // newest first
}

export function getPost(slug) {
  const file = path.join(POSTS_DIR, slug + '.md')
  if (!/^[a-z0-9-]+$/.test(slug) || !fs.existsSync(file)) return null
  const { data, content } = matter(fs.readFileSync(file, 'utf8'))
  return { slug, ...data, html: marked.parse(content) }
}
content/posts/*.mdfilesgray-mattertitle, datemarkedHTMLblog/page.jsxcards
No database needed for a blog
Next.js blog page generated from Markdown files
EduSite /blog — every card comes from a Markdown file

Caching fetch results

JS – four caching options
// 1) No caching (default) – fresh on every request
await fetch('https://api.example.com/price')

// 2) Cache forever (until next deploy or revalidate)
await fetch('https://api.example.com/states', { cache: 'force-cache' })

// 3) Cache, but refresh at most every 1 hour (ISR-style)
await fetch('https://api.example.com/courses', { next: { revalidate: 3600 } })

// 4) Cache with a tag so you can refresh it on demand
await fetch('https://api.example.com/courses', { next: { tags: ['courses'] } })
Data typeBest option
Live price, user dashboardno cache (default)
List of states, rarely changesforce-cache
Blog posts, course listrevalidate: 3600
Admin edits coursestags + revalidateTag

Revalidate: fresh data on a schedule

JS – route segment config
// app/blog/page.jsx — whole page regenerates at most every 10 minutes
export const revalidate = 600
VisitorNext.js cacheData source10:00 requestbuild pagedata10:05 request → cached (fast)10:11 request → stale, served + rebuild in backgroundfresh data
Stale-while-revalidate: visitors never wait

On-demand revalidation with tags

JS – app/admin/actions.js
// app/admin/actions.js
'use server'
import { revalidateTag, revalidatePath } from 'next/cache'

export async function addCourse(formData) {
  await saveCourseToDatabase(formData)
  revalidateTag('courses', 'max')   // every fetch tagged 'courses' is stale now
  revalidatePath('/courses')        // or: refresh one path
}

Parallel vs sequential requests

JS – Promise.all
// ❌ Sequential: 1s + 1s + 1s = 3 seconds
const course  = await getCourse(slug)
const reviews = await getReviews(slug)
const teacher = await getTeacher(slug)

// ✅ Parallel: all start together ≈ 1 second
const [course, reviews, teacher] = await Promise.all([
  getCourse(slug),
  getReviews(slug),
  getTeacher(slug),
])

Sequential

  • getCourse ▓▓▓
  • getReviews ▓▓▓
  • getTeacher ▓▓▓
  • ≈ 3 s

Parallel (Promise.all)

  • getCourse ▓▓▓
  • getReviews ▓▓▓
  • getTeacher ▓▓▓
  • ≈ 1 s
Independent requests should run together

Fetching in Client Components

Use browser fetching only for data that changes while the user watches (live seats, chat) or depends on user actions.

JSX – components/LiveSeats.jsx
// components/LiveSeats.jsx — refresh seat count every 10s in the browser
'use client'
import { useEffect, useState } from 'react'

export default function LiveSeats({ slug }) {
  const [seats, setSeats] = useState(null)
  useEffect(() => {
    const load = () => fetch(`/api/seats?course=${slug}`).then((r) => r.json()).then((d) => setSeats(d.left))
    load()
    const id = setInterval(load, 10000)
    return () => clearInterval(id)
  }, [slug])
  return <p>{seats === null ? 'Checking seats…' : `${seats} seats left`}</p>
}

Common mistakes

  • Calling your own /api/... route from a Server Component → unnecessary HTTP hop; call the function (e.g. getCourses()) directly.
  • Expecting fetch to be cached automatically (old Next.js 13/14 behaviour) → add force-cache or revalidate.
  • Awaiting independent requests one by one → use Promise.all.
  • Not checking res.ok → a 500 error page is parsed as JSON and crashes.

Summary

  • Server Components: async + await data directly (API, files, database).
  • Caching is opt-in: force-cache, revalidate, tags.
  • revalidateTag/revalidatePath after updates; Promise.all for speed.
✅ Practice

Create content/posts with 3 Markdown files and show them on /blog sorted by date. Then fetch a public API with next: { revalidate: 60 } and show the time it was fetched.

Frequently Asked Questions

Is fetch cached by default in Next.js 16?
No. Since Next.js 15, fetch requests are not cached by default. Add cache: 'force-cache' or next: { revalidate: seconds } to cache a response.
Do I need useEffect to load data in Next.js?
Not for most pages. Make the Server Component async and await the data. useEffect is only for data that must load in the browser after user interaction.
What is revalidateTag?
A function you call after data changes (for example in a Server Action). It marks all fetches with that tag as stale, so the next visitor gets fresh data. In Next.js 16 pass a profile: revalidateTag('courses', 'max').
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicSSG, SSR & ISR Rendering Your progress: 19 / 37 (51%) · Lesson 20 / 37 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free