Module 14 — Data Fetching in Next.js
Data fetching is one of the most important parts of a Next.js application.
Almost every real application needs to retrieve data:
- courses
- products
- users
- blog posts
- orders
- dashboards
- comments
- search results
- notifications
Next.js gives you several ways to fetch this data.
The important skill is not simply knowing how to call fetch().
The real goal is understanding:
Where should the data be fetched, when should it be fetched, how long should it remain fresh, and what should happen when the request fails?
1. What Is Data Fetching?
Data fetching means retrieving information from another source.
That source could be:
1Database 2REST API 3External API 4Internal API 5File 6CMS 7Microservice 8Authentication service
A typical application looks like:
1Next.js Page 2 │ 3 ▼ 4Data Fetching 5 │ 6 ├── Database 7 ├── Internal API 8 ├── External API 9 └── Other Service 10 │ 11 ▼ 12Data 13 │ 14 ▼ 15React UI
Next.js allows this process to happen either primarily on the server or in the browser.
2. Server-Side Data Fetching
With the App Router, Server Components are the natural place for many data-fetching operations.
For example:
1export default async function CoursesPage() { 2 const response = await fetch( 3 "https://api.example.com/courses" 4 ); 5 6 if (!response.ok) { 7 throw new Error("Failed to fetch courses"); 8 } 9 10 const courses = await response.json(); 11 12 return ( 13 <main> 14 <h1>Courses</h1> 15 16 {courses.map((course: any) => ( 17 <article key={course.id}> 18 <h2>{course.title}</h2> 19 </article> 20 ))} 21 </main> 22 ); 23}
Notice that the component is:
1async function CoursesPage()
because the component performs asynchronous work.
The flow is:
1Browser requests page 2 ↓ 3Next.js Server 4 ↓ 5Fetch courses 6 ↓ 7Render page 8 ↓ 9Send result to browser
3. Why Server Fetching Is Powerful
Server-side fetching provides several important benefits.
Database access
The server can directly communicate with a database.
1Server Component 2 ↓ 3Database 4 ↓ 5Data
Private credentials
Private API keys can remain on the server.
1Server 2 │ 3 ├── API key 4 ├── Database password 5 └── Private credentials
Reduced client-side JavaScript
The browser does not need to perform every data request itself.
Better initial rendering
The server can prepare the page before the browser receives it.
SEO-friendly content
Important content can be rendered as part of the server-first page architecture.
4. Fetching Data from a REST API
REST APIs are one of the most common sources of application data.
Suppose an API provides:
1GET /api/courses
A Server Component can fetch it:
1export default async function CoursesPage() { 2 const response = await fetch( 3 "https://api.example.com/api/courses" 4 ); 5 6 if (!response.ok) { 7 throw new Error("Unable to load courses"); 8 } 9 10 const courses = await response.json(); 11 12 return ( 13 <main> 14 <h1>Courses</h1> 15 16 {courses.map((course: any) => ( 17 <div key={course.id}> 18 {course.title} 19 </div> 20 ))} 21 </main> 22 ); 23}
The API may be hosted separately:
1Next.js 2 │ 3 │ HTTPS 4 ▼ 5REST API 6 │ 7 ▼ 8Database
5. External APIs
An external API is a service operated outside your application.
Examples include:
1Payment API 2Weather API 3Maps API 4AI API 5Currency API 6GitHub API 7News API 8Email API
For example:
1export default async function WeatherPage() { 2 const response = await fetch( 3 "https://api.example.com/weather" 4 ); 5 6 if (!response.ok) { 7 throw new Error("Failed to fetch weather"); 8 } 9 10 const weather = await response.json(); 11 12 return ( 13 <main> 14 <h1>Weather</h1> 15 16 <p>Temperature: {weather.temperature}°C</p> 17 </main> 18 ); 19}
If the external API requires a private API key, keep that key on the server.
For example:
1const response = await fetch( 2 "https://api.example.com/data", 3 { 4 headers: { 5 Authorization: `Bearer ${process.env.API_KEY}`, 6 }, 7 } 8);
Do not expose private credentials through Client Components.
6. Internal APIs
An internal API is an API provided by your own application.
For example:
1Next.js 2│ 3├── /api/courses 4├── /api/users 5├── /api/orders 6└── /api/search
A Client Component might call:
1"use client"; 2 3import { useEffect, useState } from "react"; 4 5export default function Courses() { 6 const [courses, setCourses] = useState([]); 7 8 useEffect(() => { 9 fetch("/api/courses") 10 .then((response) => response.json()) 11 .then(setCourses); 12 }, []); 13 14 return ( 15 <div> 16 {courses.map((course: any) => ( 17 <p key={course.id}>{course.title}</p> 18 ))} 19 </div> 20 ); 21}
This is useful when the browser needs to request data after the page has already loaded.
However, if the data is needed to render the initial page, you should consider fetching it directly on the server instead of creating an unnecessary browser → Next.js API → database round trip.
7. Direct Database Queries
Server Components can be particularly useful when working with databases.
For example:
1import { db } from "@/lib/db"; 2 3export default async function UsersPage() { 4 const users = await db.user.findMany(); 5 6 return ( 7 <main> 8 <h1>Users</h1> 9 10 {users.map((user) => ( 11 <p key={user.id}> 12 {user.name} 13 </p> 14 ))} 15 </main> 16 ); 17}
The architecture becomes:
1Browser 2 │ 3 ▼ 4Next.js Server 5 │ 6 ▼ 7Database 8 │ 9 ▼ 10Users 11 │ 12 ▼ 13Rendered Page
The database connection never needs to be exposed to the browser.
8. Server Fetching vs API Routes
Suppose your Next.js application has:
1/api/courses
and the page needs courses.
You could do:
1Browser 2 ↓ 3Next.js Page 4 ↓ 5/api/courses 6 ↓ 7Database
But if the page itself is already running on the server, you can often do:
1Browser 2 ↓ 3Next.js Page 4 ↓ 5Database
For example:
1export default async function CoursesPage() { 2 const courses = await db.course.findMany(); 3 4 return ( 5 <main> 6 {courses.map((course) => ( 7 <h2 key={course.id}> 8 {course.title} 9 </h2> 10 ))} 11 </main> 12 ); 13}
You avoid an unnecessary internal HTTP request.
A useful principle is:
Server code should generally communicate directly with server-side resources when there is no reason to go through an internal HTTP API.
9. Client-Side Data Fetching
Client fetching is useful when the browser needs to retrieve or refresh data after the page loads.
For example:
1"use client"; 2 3import { useEffect, useState } from "react"; 4 5type Course = { 6 id: string; 7 title: string; 8}; 9 10export default function Courses() { 11 const [courses, setCourses] = useState<Course[]>([]); 12 13 useEffect(() => { 14 fetch("/api/courses") 15 .then((response) => response.json()) 16 .then((data) => { 17 setCourses(data); 18 }); 19 }, []); 20 21 return ( 22 <div> 23 {courses.map((course) => ( 24 <p key={course.id}> 25 {course.title} 26 </p> 27 ))} 28 </div> 29 ); 30}
The flow is:
1Browser loads page 2 ↓ 3Client Component mounts 4 ↓ 5useEffect() 6 ↓ 7fetch() 8 ↓ 9API 10 ↓ 11Data 12 ↓ 13setCourses() 14 ↓ 15UI updates
10. When Client Fetching Makes Sense
Client-side fetching is useful for data that is:
User-specific
For example:
1Notifications 2Live profile data 3Personal dashboard 4User preferences
Highly interactive
For example:
1Search 2Filters 3Autocomplete 4Pagination 5Infinite scrolling
Frequently changing
For example:
1Live scores 2Stock information 3Notifications 4Real-time dashboards
Dependent on browser state
For example:
1Geolocation 2Browser preferences 3Local storage 4Device information
11. Server Fetching vs Client Fetching
The fundamental difference is:
1SERVER FETCHING 2 3Server 4 ↓ 5Fetch data 6 ↓ 7Render page 8 ↓ 9Browser
while:
1CLIENT FETCHING 2 3Server 4 ↓ 5Send page 6 ↓ 7Browser 8 ↓ 9JavaScript 10 ↓ 11Fetch data 12 ↓ 13Update UI
This difference affects performance, security, caching, and user experience.
12. Request Caching
Caching means storing previously fetched data so it does not always need to be retrieved again.
Conceptually:
1First request 2 ↓ 3Fetch API 4 ↓ 5Store result 6 ↓ 7Cache 8 9Next request 10 ↓ 11Cache 12 ↓ 13Return data
Caching can improve:
1Performance 2Response time 3API load 4Database load 5Infrastructure cost
In modern Next.js, caching behavior depends on the Next.js version, request configuration, and rendering strategy. Therefore, do not assume that every fetch() is automatically cached forever.
For example, when you explicitly need a revalidation policy, you can use:
1const response = await fetch( 2 "https://api.example.com/courses", 3 { 4 next: { 5 revalidate: 3600, 6 }, 7 } 8);
Here:
13600 seconds 2= 31 hour
The intent is:
Keep the fetched result reusable for approximately the configured revalidation period before it needs to be refreshed according to Next.js caching behavior.
13. Revalidation
Revalidation means updating previously generated or cached data.
Imagine a course page:
110:00 AM 2Course data fetched 3 ↓ 4Cached 5 610:30 AM 7Course page requested 8 ↓ 9Cached data can be reused 10 11After revalidation period 12 ↓ 13Data can be refreshed
For example:
1export default async function CoursesPage() { 2 const response = await fetch( 3 "https://api.example.com/courses", 4 { 5 next: { 6 revalidate: 3600, 7 }, 8 } 9 ); 10 11 const courses = await response.json(); 12 13 return ( 14 <main> 15 {courses.map((course: any) => ( 16 <h2 key={course.id}> 17 {course.title} 18 </h2> 19 ))} 20 </main> 21 ); 22}
This is useful for data that changes periodically but does not need to be updated on every request.
Examples:
1Course catalog 2Blog posts 3Documentation 4Product catalog 5Public statistics 6CMS content
14. Dynamic Data
Some data needs to be fresh for every request.
Examples include:
1Account balance 2Private dashboard 3Current authorization state 4User-specific information 5Real-time availability
In such situations, aggressive caching may be inappropriate.
Conceptually:
1Request 2 ↓ 3Fetch fresh data 4 ↓ 5Render response
The important question is:
Does this data need to be fresh for every request?
If yes, use an appropriate dynamic strategy instead of treating it like static content.
15. Static-Like Data
Some data changes rarely.
For example:
1Documentation 2Course metadata 3Blog articles 4Categories 5Public landing-page content
For this type of data, caching and revalidation can be very effective.
1Request 2 ↓ 3Cached content 4 ↓ 5Fast response
Then the data can be refreshed periodically.
16. Choosing a Revalidation Time
There is no universal value.
Think about how frequently the data changes.
Rarely changing
124 hours
Changes several times a day
11–6 hours
Changes frequently
1Seconds to minutes
Must always be current
1Fetch dynamically
The correct value depends on the application's requirements.
17. Error Handling
Network requests can fail.
For example:
1const response = await fetch( 2 "https://api.example.com/courses" 3); 4 5if (!response.ok) { 6 throw new Error("Failed to fetch courses"); 7} 8 9const courses = await response.json();
The important part is:
1if (!response.ok)
A failed HTTP request should not automatically be treated as valid data.
18. Server-Side Error Handling
A Server Component can throw an error:
1export default async function CoursesPage() { 2 const response = await fetch( 3 "https://api.example.com/courses" 4 ); 5 6 if (!response.ok) { 7 throw new Error("Unable to load courses"); 8 } 9 10 const courses = await response.json(); 11 12 return ( 13 <main> 14 <h1>Courses</h1> 15 16 {courses.map((course: any) => ( 17 <p key={course.id}> 18 {course.title} 19 </p> 20 ))} 21 </main> 22 ); 23}
You can then provide an error boundary for the route.
For example:
1"use client"; 2 3export default function Error({ 4 reset, 5}: { 6 error: Error & { digest?: string }; 7 reset: () => void; 8}) { 9 return ( 10 <div> 11 <h2>Something went wrong.</h2> 12 13 <button onClick={() => reset()}> 14 Try again 15 </button> 16 </div> 17 ); 18}
This gives the user a recovery mechanism instead of leaving the page in an unusable state.
19. Client-Side Error Handling
Client fetching should also check for failures.
Instead of:
1fetch("/api/courses") 2 .then((response) => response.json()) 3 .then(setCourses);
use:
1"use client"; 2 3import { useEffect, useState } from "react"; 4 5export default function Courses() { 6 const [courses, setCourses] = useState([]); 7 const [error, setError] = useState(""); 8 9 useEffect(() => { 10 async function loadCourses() { 11 try { 12 const response = await fetch("/api/courses"); 13 14 if (!response.ok) { 15 throw new Error("Failed to fetch courses"); 16 } 17 18 const data = await response.json(); 19 20 setCourses(data); 21 } catch (error) { 22 setError("Unable to load courses."); 23 } 24 } 25 26 loadCourses(); 27 }, []); 28 29 if (error) { 30 return <p>{error}</p>; 31 } 32 33 return ( 34 <div> 35 {courses.map((course: any) => ( 36 <p key={course.id}> 37 {course.title} 38 </p> 39 ))} 40 </div> 41 ); 42}
Now the UI can communicate failure to the user.
20. Loading States
Data fetching takes time.
A good application should communicate that something is happening.
For Client Components:
1"use client"; 2 3import { useEffect, useState } from "react"; 4 5export default function Courses() { 6 const [courses, setCourses] = useState([]); 7 const [loading, setLoading] = useState(true); 8 9 useEffect(() => { 10 async function loadCourses() { 11 try { 12 const response = await fetch("/api/courses"); 13 14 if (!response.ok) { 15 throw new Error("Failed to fetch courses"); 16 } 17 18 const data = await response.json(); 19 20 setCourses(data); 21 } finally { 22 setLoading(false); 23 } 24 } 25 26 loadCourses(); 27 }, []); 28 29 if (loading) { 30 return <p>Loading courses...</p>; 31 } 32 33 return ( 34 <div> 35 {courses.map((course: any) => ( 36 <p key={course.id}> 37 {course.title} 38 </p> 39 ))} 40 </div> 41 ); 42}
The state lifecycle becomes:
1Loading 2 ↓ 3Request 4 ↓ 5┌───────────────┐ 6│ │ 7▼ ▼ 8Success Failure 9│ │ 10▼ ▼ 11Data Error
21. Next.js Loading UI
For route-level Server Component loading, Next.js provides a special file:
1loading.tsx
Example:
1app/ 2└── courses/ 3 ├── page.tsx 4 └── loading.tsx
loading.tsx:
1export default function Loading() { 2 return ( 3 <div> 4 Loading courses... 5 </div> 6 ); 7}
This allows Next.js to display loading UI while the route's server-rendered content is being prepared.
A better production loading screen might use a skeleton:
1export default function Loading() { 2 return ( 3 <div className="space-y-4"> 4 <div className="h-8 w-48 animate-pulse rounded bg-gray-200" /> 5 6 <div className="h-24 animate-pulse rounded bg-gray-200" /> 7 8 <div className="h-24 animate-pulse rounded bg-gray-200" /> 9 </div> 10 ); 11}
22. Server Loading vs Client Loading
There are two different situations.
Server loading
The server is preparing the page:
1Request 2 ↓ 3Server fetch 4 ↓ 5Loading UI 6 ↓ 7Rendered page
Use:
1loading.tsx
for route-level loading UI.
Client loading
The page has already loaded, but a Client Component is fetching additional data:
1Page loaded 2 ↓ 3Client request 4 ↓ 5Loading state 6 ↓ 7Data received 8 ↓ 9UI updated
Use React state:
1const [loading, setLoading] = useState(true);
23. Fetching Based on User Interaction
Suppose a user searches courses.
You don't necessarily want to fetch every possible search result when the page initially loads.
Instead:
1User enters query 2 ↓ 3Client Component 4 ↓ 5API request 6 ↓ 7Search results 8 ↓ 9Update UI
Example:
1"use client"; 2 3import { useState } from "react"; 4 5export default function CourseSearch() { 6 const [query, setQuery] = useState(""); 7 const [results, setResults] = useState([]); 8 9 async function search() { 10 if (!query.trim()) return; 11 12 const response = await fetch( 13 `/api/courses/search?q=${encodeURIComponent(query)}` 14 ); 15 16 const data = await response.json(); 17 18 setResults(data); 19 } 20 21 return ( 22 <div> 23 <input 24 value={query} 25 onChange={(event) => setQuery(event.target.value)} 26 placeholder="Search courses..." 27 /> 28 29 <button onClick={search}> 30 Search 31 </button> 32 33 {results.map((course: any) => ( 34 <p key={course.id}> 35 {course.title} 36 </p> 37 ))} 38 </div> 39 ); 40}
This is an appropriate use of client-side fetching because the request depends on browser interaction.
24. External API with Authentication
Suppose an external service requires an API key.
Do not do this in a Client Component:
1"use client"; 2 3fetch("https://api.example.com/data", { 4 headers: { 5 Authorization: `Bearer ${process.env.API_KEY}`, 6 }, 7});
Instead, keep the secret on the server:
1export async function getExternalData() { 2 const response = await fetch( 3 "https://api.example.com/data", 4 { 5 headers: { 6 Authorization: `Bearer ${process.env.API_KEY}`, 7 }, 8 } 9 ); 10 11 if (!response.ok) { 12 throw new Error("External API request failed"); 13 } 14 15 return response.json(); 16}
Then use the server function from server-side code.
25. Parallel Data Fetching
Suppose a dashboard requires three independent pieces of data:
1User 2Statistics 3Notifications
Avoid unnecessary sequential fetching when the requests do not depend on each other.
Instead of:
1const user = await getUser(); 2const stats = await getStats(); 3const notifications = await getNotifications();
you can often run them concurrently:
1const [user, stats, notifications] = await Promise.all([ 2 getUser(), 3 getStats(), 4 getNotifications(), 5]);
The architecture becomes:
1 Dashboard 2 │ 3 ┌─────────┼─────────┐ 4 ▼ ▼ ▼ 5 User Stats Notifications 6 │ │ │ 7 └─────────┼─────────┘ 8 ▼ 9 Render
This can reduce the time spent waiting for independent requests.
26. Sequential Data Fetching
Sometimes requests actually depend on each other.
For example:
1Get user 2 ↓ 3Get user's organization 4 ↓ 5Get organization's courses
In that case, sequential fetching can be necessary:
1const user = await getUser(); 2 3const organization = await getOrganization( 4 user.organizationId 5); 6 7const courses = await getCourses( 8 organization.id 9);
Do not force everything into Promise.all() when later requests depend on earlier results.
27. Waterfalls
A request waterfall happens when one request unnecessarily waits for another.
For example:
1Request A 2 ↓ 3Request B 4 ↓ 5Request C 6 ↓ 7Request D
If these requests are independent, this is inefficient.
Prefer:
1Request A ──┐ 2Request B ──┼──→ Render 3Request C ──┤ 4Request D ──┘
using concurrent operations where appropriate.
28. Server Fetching Is Often Better for Initial Data
Suppose a page requires course data to display its main content.
A server-first architecture can be:
1Request 2 ↓ 3Server 4 ↓ 5Fetch courses 6 ↓ 7Render page 8 ↓ 9Browser
Instead of:
1Request 2 ↓ 3Browser receives empty page 4 ↓ 5JavaScript loads 6 ↓ 7useEffect() 8 ↓ 9Fetch courses 10 ↓ 11Render courses
For initial page content, server fetching is often the cleaner architecture.
29. Client Fetching Is Often Better for Interaction
Now consider:
1User searches 2User changes filter 3User selects date 4User opens dropdown 5User requests next page
These actions happen after the initial page is displayed.
Client fetching can be appropriate:
1User interaction 2 ↓ 3Client Component 4 ↓ 5Fetch data 6 ↓ 7Update state 8 ↓ 9Update UI
This is why strong Next.js applications often combine both approaches.
30. Hybrid Data Fetching
A production application might use:
1 Page 2 │ 3 ▼ 4 Server Fetching 5 │ 6 ▼ 7 Initial Content 8 │ 9 ┌──────────┴──────────┐ 10 │ │ 11 Server Client 12 │ │ 13 Main data Search / Filters 14 │ 15 ▼ 16 Client Fetching
For example, a course platform:
1Course Page 2│ 3├── Course information 4│ └── Server fetch 5│ 6├── Lesson list 7│ └── Server fetch 8│ 9├── Search lessons 10│ └── Client fetch 11│ 12├── Bookmark 13│ └── Client interaction 14│ 15└── Recommendations 16 └── Client fetch when requested
This is often the best balance.
31. Request Caching Strategy
Think about data in categories.
Static or slowly changing
1Course catalog 2Blog posts 3Documentation 4Categories
Possible strategy:
1Cache + revalidate
Frequently changing
1Notifications 2Dashboard statistics 3Live availability
Possible strategy:
1Dynamic fetching
User-interactive
1Search 2Filters 3Autocomplete 4Pagination
Possible strategy:
1Client-side fetching
Private server data
1User account 2Database records 3Private API
Possible strategy:
1Server-side fetching
32. A Practical Decision Tree
Use this decision tree whenever you need to fetch data:
1Do I need this data for the initial page? 2 │ 3 YES 4 │ 5 ▼ 6Can the server safely fetch it? 7 │ 8 YES 9 │ 10 ▼ 11 Fetch on Server 12 │ 13 ▼ 14Does it need periodic freshness? 15 │ 16 ┌────┴────┐ 17 YES NO 18 │ │ 19 ▼ ▼ 20 Revalidate Cache/ 21 as needed appropriate strategy
For browser interaction:
1Does fetching depend on user interaction? 2 │ 3 YES 4 │ 5 ▼ 6 Client Fetching
33. Complete Production Example
Let's build a course page.
Server page
1import CourseSearch from "./CourseSearch"; 2 3export default async function CoursesPage() { 4 const response = await fetch( 5 "https://api.example.com/courses", 6 { 7 next: { 8 revalidate: 3600, 9 }, 10 } 11 ); 12 13 if (!response.ok) { 14 throw new Error("Failed to fetch courses"); 15 } 16 17 const courses = await response.json(); 18 19 return ( 20 <main> 21 <h1>Courses</h1> 22 23 <CourseSearch courses={courses} /> 24 </main> 25 ); 26}
Client component
1"use client"; 2 3import { useState } from "react"; 4 5type Course = { 6 id: string; 7 title: string; 8}; 9 10export default function CourseSearch({ 11 courses, 12}: { 13 courses: Course[]; 14}) { 15 const [query, setQuery] = useState(""); 16 17 const filteredCourses = courses.filter((course) => 18 course.title 19 .toLowerCase() 20 .includes(query.toLowerCase()) 21 ); 22 23 return ( 24 <section> 25 <input 26 value={query} 27 onChange={(event) => setQuery(event.target.value)} 28 placeholder="Search courses..." 29 /> 30 31 {filteredCourses.map((course) => ( 32 <article key={course.id}> 33 <h2>{course.title}</h2> 34 </article> 35 ))} 36 </section> 37 ); 38}
Notice what happened.
The server handles:
1API request 2Caching/revalidation 3Initial data
The client handles:
1Search state 2Input events 3Filtering 4Interactive UI
This is an excellent example of separating responsibilities.
34. Error + Loading Architecture
A complete route can contain:
1app/ 2└── courses/ 3 ├── page.tsx 4 ├── loading.tsx 5 └── error.tsx
Conceptually:
1Request 2 │ 3 ▼ 4loading.tsx 5 │ 6 ▼ 7Fetch data 8 │ 9 ├──────────────┐ 10 │ │ 11 Success Failure 12 │ │ 13 ▼ ▼ 14page.tsx error.tsx
This gives users a proper experience during:
1Loading 2Success 3Failure 4Retry
35. Best Practices
1. Prefer server fetching for initial page data
If the data is required to render the page, consider fetching it on the server.
2. Use client fetching for interaction
Search, filters, infinite scrolling, and user-triggered updates are common examples.
3. Keep secrets on the server
Never expose private API keys or database credentials.
4. Check response.ok
Do not assume every HTTP request succeeded.
5. Handle loading states
Users should know when data is being loaded.
6. Handle errors
Network requests can fail.
7. Cache intentionally
Decide how fresh the data needs to be.
8. Revalidate when appropriate
Slowly changing content does not always need a fresh request every time.
9. Avoid unnecessary internal API requests
Server code can often access server-side resources directly.
10. Fetch independent data concurrently
Use Promise.all() when requests do not depend on one another.
11. Keep Client Components focused
Don't convert an entire page into a Client Component just to fetch one interactive piece of data.
36. The Most Important Mental Model
Think about data fetching like this:
1 DATA 2 │ 3 ┌─────────────┴─────────────┐ 4 │ │ 5 SERVER CLIENT 6 │ │ 7 Initial data User interaction 8 Database Search 9 Private APIs Filters 10 External APIs Pagination 11 SEO content Dynamic updates 12 │ │ 13 └─────────────┬─────────────┘ 14 │ 15 ▼ 16 UI
The server and client are not competing approaches.
They solve different problems.
37. Final Learning Goal
The most important lesson of this module is:
Do not ask only "How do I fetch data?" Ask "Where should I fetch this data?"
For initial content:
1Server 2 ↓ 3Fetch 4 ↓ 5Render
For browser interaction:
1Client 2 ↓ 3User action 4 ↓ 5Fetch 6 ↓ 7Update UI
For slowly changing content:
1Fetch 2 ↓ 3Cache 4 ↓ 5Revalidate
For dynamic data:
1Request 2 ↓ 3Fresh data 4 ↓ 5Render
For production applications:
1 Next.js Application 2 │ 3 ┌──────────────┴──────────────┐ 4 │ │ 5 SERVER CLIENT 6 │ │ 7 Database useState 8 APIs useEffect 9 Secrets Events 10 Initial data Browser APIs 11 SEO content Interactive UI 12 │ │ 13 └──────────────┬──────────────┘ 14 │ 15 ▼ 16 User
If you understand this architecture, you can make much better decisions about performance, security, caching, SEO, loading states, and user experience in Next.js.