Why is this website as fast as a ghost?
When you browse world-class websites built with Next.js (like Notion or TikTok's web version), you get this feeling: "It's unbelievably fast!"
You click a link, and the page switches almost "instantly" with no white screen or loading spinner.
Is it because they spent hundreds of thousands on super servers?
No. The secret lies in Next.js's most powerful (and most frustrating for beginners) core mechanism: Cache.
Imagine this scenario:
Your homepage fetches "10 newly listed products" from a database.
If 10,000 visitors flood your homepage simultaneously, your database would get bombarded 10,000 times and crash.
But Next.js says: "Wait! These 10 products won't change for the next hour!"
So, Next.js fetches the data once for the first visitor, then caches (takes a snapshot) it on the server.
For the next 9,999 visitors, Next.js simply serves this "snapshot" without touching the database.
This is the real reason your website can be lightning-fast while keeping database costs low!
The Double-Edged Sword of Caching: Why Won't My Page Update?
Blazing speed comes at a cost.
When Next.js App Router first launched, countless engineers worldwide cried on forums:
"I changed the product price from 100 to 500 in the backend, but the frontend won't update! I refreshed 100 times and it's still the same!"
This happens because Next.js's default behavior is extremely aggressive: it caches all your fetch requests by default, and caches them permanently!
To solve this, you must learn to command Next.js: "Tell it when it can lazily use the cache and when it must fetch fresh data."
Tactic 1: SSG (Static Generation) - The Eternal Fossil
Perfect for pages like "Contact Us" or "Company Info" that never change.
Next.js does this by default—you don't need to write anything; it caches permanently.
Tactic 2: SSR (Server-Side Rendering) - No Laziness Allowed
Ideal for "real-time stock prices," "shopping carts," or "user profiles." You don't want visitors seeing yesterday's cached stock prices.
In Vibe Coding, just ask the AI to add one line to force Next.js to fetch fresh data every time.
// Tell Next.js: DO NOT cache this fetch! Always get fresh data when someone views this page!
const res = await fetch('https://api.example.com/cart', {
cache: 'no-store'
});
Tactic 3: ISR (Incremental Static Regeneration) - The Perfect Balance
This is Next.js's killer feature. Great for "blog posts" or "product listings."
These don't need updates every second but also can't stay stale forever.
// Tell Next.js: Cache this data, but if it's older than 60 seconds,
// secretly fetch fresh data in the background when a visitor arrives.
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 60 }
});
Vibe Prompt in Action: Crafting the Perfect Data Flow Architecture
Setting these caching strategies in real-world projects is brain-melting. But this is where Vibe Coder shines. We don't memorize syntax—we chant business logic.
【Ultimate Next.js Caching Strategy Prompt】
I'm building a Next.js App Router news site. Help me write a Server Component forapp/news/page.tsx.
This page needs to fetch the latest news list fromhttps://api.example.com/news.Apply these business requirements for Data Fetching:
- News doesn't need real-time updates. Use ISR with a 5-minute revalidation (
revalidate: 300).- Add full try-catch error handling. If the API fails, don't crash the page—show a friendly message: "Failed to load news. Please try again later."
- Use TypeScript to define the News type (
id,title,publishedAt).- Include detailed Chinese comments explaining how the caching works.
AI-Generated God-Tier Architecture Code:
import { Suspense } from 'react';
// 1. Define clear TypeScript types for safer development
type NewsItem = {
id: string;
title: string;
publishedAt: string;
};
// 2. This is a Server Component—we fetch data directly here with async
export default async function NewsPage() {
let newsList: NewsItem[] = [];
let errorMsg = '';
try {
// 3. Core magic: ISR (revalidate: 300)
// The server only calls the real API every 5 minutes, saving costs while loading instantly!
const res = await fetch('https://api.example.com/news', {
next: { revalidate: 300 }
});
if (!res.ok) throw new Error('API response error');
newsList = await res.json();
} catch (error) {
// 4. Graceful error handling—no blank screens
console.error('Failed to fetch news:', error);
errorMsg = '🚨 Failed to load news. Our engineers are fixing it!';
}
return (
<main className="p-8">
<h1 className="text-3xl font-bold mb-6">Latest Business News</h1>
{/* Show error message if something fails */}
{errorMsg ? (
<p className="text-red-500">{errorMsg}</p>
) : (
<ul className="space-y-4">
{newsList.map((news) => (
<li key={news.id} className="p-4 bg-gray-100 rounded-lg shadow-sm">
<p className="text-sm text-gray-500">{news.publishedAt}</p>
<h2 className="text-xl font-semibold">{news.title}</h2>
</li>
))}
</ul>
)}
</main>
);
}
Conclusion: Standing on the Shoulders of Giants
After these five chapters of Next.js training, you've evolved from a static HTML beginner to an advanced web engineer who understands Server/Client boundaries, routing navigation, and enterprise-grade caching strategies.
You might think Next.js has too many strict rules. But that's why it's powerful.
It forces you to code "the right way"—optimized for performance and security. With Vibe Coding, AI handles these complex syntax details, letting you focus on core questions: "How fresh should this page's data be? Does this page need SEO?"
Mastering Next.js means you can build the next million-user unicorn website. Go forth boldly and embrace the infinite possibilities of the Web 3.0 and AI era!
Data Fetching Methods
fetch() — Built-in, Cached by Default
// Auto-cached (deduplicated, persisted)
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Cache for 1 hour
}).then(r => r.json());
// No cache — always fresh
const freshData = await fetch('https://api.example.com/data', {
cache: 'no-store'
}).then(r => r.json());
Server Database Queries
import { sql } from '@vercel/postgres';
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// Direct query — no API route needed
const { rows } = await sql`
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = ${id}
GROUP BY u.id
`;
const user = rows[0];
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<p>Orders: {user.order_count}</p>
</div>
);
}
Caching Strategies
| Strategy | Method | Use Case |
|----------|--------|----------|
| Static | Default fetch() | Content that rarely changes |
| Revalidated | next: { revalidate: 3600 } | Content updated hourly |
| On-demand | revalidatePath() / revalidateTag() | Content updated by action |
| Dynamic | cache: 'no-store' | Real-time data |
| ISR | export const dynamic = 'force-static' | Static + revalidate |
On-Demand Revalidation
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const body = await request.json();
// Revalidate by path
revalidatePath('/products');
// Revalidate by tag (add tags to fetch)
revalidateTag('products');
return Response.json({ revalidated: true });
}
Tagged Fetching
// Tag your fetch for targeted revalidation
const data = await fetch('https://api.example.com/products', {
next: {
tags: ['products', 'category-electronics']
}
}).then(r => r.json());
// Later: revalidateTag('category-electronics') clears only that tag
Parallel vs Sequential Data Fetching
Sequential (Slower)
export default async function Page() {
const user = await fetchUser(); // Wait 1
const orders = await fetchOrders(); // Wait 2 (waits for 1)
const recommendations = await fetchRecs(); // Wait 3 (waits for 2)
// Total: 3 sequential requests
}
Parallel (Faster)
export default async function Page() {
// All three start at the same time
const [user, orders, recommendations] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchRecs(),
]);
// Total: only as long as the slowest request
}
Summary
Next.js offers multiple data fetching strategies: cached fetch, direct SQL, ISR, and on-demand revalidation. Use the right strategy based on how fresh your data needs to be.
Key takeaways:
fetch()cached by default — usecache: 'no-store'for fresh data |next: { revalidate: N }— time-based revalidation (ISR) |revalidatePath()/revalidateTag()— on-demand revalidation |- Tag fetches for targeted cache invalidation |
- Direct SQL queries in Server Components — no API needed |
- Parallel fetching with Promise.all for faster pages |
- Use Suspense boundaries for streaming slow queries |
You've completed this course! You now understand the Next.js app router.