Skip to content

Commit e7e12ad

Browse files
Update Next.js Article Formatting
1 parent 99b9409 commit e7e12ad

File tree

1 file changed

+45
-45
lines changed
  • src/pages/2023-12/next-js-app-router-cache

1 file changed

+45
-45
lines changed

src/pages/2023-12/next-js-app-router-cache/index.mdx

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: "Next.js improves your application's performance and reduces costs
66
tags: ["Next.js"]
77
---
88

9-
import Tangent from "@blogComponents/lib/Tangent.astro";
9+
import Tangent from "@blogComponents/lib/Tangent.astro"
1010

1111
## Introduction
1212

@@ -39,7 +39,7 @@ For each of the above, I will delve into their specific roles, where they're sto
3939

4040
### Request Memoization
4141

42-
If you have ever built an application before, you have probably had to fetch and display the same information in multiple places. As an example, if you are fetching some user data, it might need to be displayed in more than one place. There is one problem, though. If our `fetch` request is computationally heavy, this can increase the load on the server if it's continuously fetching from a data source. This becomes even more problematic when we consider the fact that user details are unlikely to change within a single request. So, it doesnt really make sense to make a fresh `fetch` call each time we want the user details.
42+
If you have ever built an application before, you have probably had to fetch and display the same information in multiple places. As an example, if you are fetching some user data, it might need to be displayed in more than one place. There is one problem, though. If our `fetch` request is computationally heavy, this can increase the load on the server if it's continuously fetching from a data source. This becomes even more problematic when we consider the fact that user details are unlikely to change within a single request. So, it doesn't really make sense to make a fresh `fetch` call each time we want the user details.
4343

4444
Luckily, Request Memoization solves this exact problem. Whenever we make a `fetch` request in a render cycle (which basically just refers to the process of rendering all the components on a page), its result gets stored for any subsequent requests that have identical parameters.
4545

@@ -75,37 +75,37 @@ While Request Memoization in Next.js is tailored only for the `GET` method in th
7575
To do this, we can uset React's `cache()` function.
7676

7777
```jsx
78-
import { cache } from "react";
79-
import { queryDatabase } from "./databaseClient"; // Assume this is our database client function
78+
import { cache } from "react"
79+
import { queryDatabase } from "./databaseClient" // Assume this is our database client function
8080

8181
export default async function fetchUserData(userId) {
8282
return cache(async () => {
8383
// Direct database query
84-
return queryDatabase("SELECT * FROM users WHERE id = ?", [userId]);
85-
});
84+
return queryDatabase("SELECT * FROM users WHERE id = ?", [userId])
85+
})
8686
}
8787
```
8888

89-
In this code above, the first time `fetchUserData()` is called, it queries the database directly, as theres no cached result yet. But the next time this function is called with the same `userId` in a different component, it smartly retrieves from the cache. Just like with `fetch`, this memoization is a one-time deal, valid only for the duration of a single render pass, but at least now we know that we arent limited to `fetch`.
89+
In this code above, the first time `fetchUserData()` is called, it queries the database directly, as there's no cached result yet. But the next time this function is called with the same `userId` in a different component, it smartly retrieves from the cache. Just like with `fetch`, this memoization is a one-time deal, valid only for the duration of a single render pass, but at least now we know that we aren't limited to `fetch`.
9090

9191
#### Revalidation
9292

9393
In Next.js, revalidation refers to the process of refreshing old cached data with new data straight from the database. This feature is vital for determining how long the data you serve stays up-to-date in the cache, ensuring that your users always have access to the most relevant information.
9494

95-
As of right now, there is no way we can revalidate the memoized values. This might seem like a limitation, but memoization is generally for a single render pass, and its unlikely that your data will change in that short interval. Still, if you find yourself needing to revalidate, there is an option to opt out.
95+
As of right now, there is no way we can revalidate the memoized values. This might seem like a limitation, but memoization is generally for a single render pass, and it's unlikely that your data will change in that short interval. Still, if you find yourself needing to revalidate, there is an option to opt out.
9696

9797
#### Opting out
9898

9999
To opt out of the cache, we can pass in an `AbortController` signal as a parameter to the `fetch` request.
100100

101101
```tsx {1, 4, 5}
102-
const { signal } = new AbortController();
102+
const { signal } = new AbortController()
103103
async function fetchUserData(userId) {
104104
// The `fetch` function is automatically memoized by Next.js during SSR
105105
const res = await fetch(`https://api.example.com/users/${userId}`, {
106106
signal,
107-
});
108-
return res.json();
107+
})
108+
return res.json()
109109
}
110110
```
111111

@@ -128,21 +128,21 @@ To do this, we can use the Data Cache, which is enabled by default in Next.js. T
128128

129129
```jsx {3}
130130
export default async function Page({ params }) {
131-
const city = params.city;
132-
const res = await fetch(`https://api.globetrotter.com/guides/${city}`);
133-
const guideData = await res.json();
131+
const city = params.city
132+
const res = await fetch(`https://api.globetrotter.com/guides/${city}`)
133+
const guideData = await res.json()
134134

135135
return (
136136
<div>
137137
<h1>{guideData.title}</h1>
138138
<p>{guideData.content}</p>
139139
{/* Render the guide data */}
140140
</div>
141-
);
141+
)
142142
}
143143
```
144144

145-
In the simple code I have above, all we are doing is making a `fetch` request to an API and dynamically passing in a `city` parameter so we can display travel guides for a given city. The main thing to focus on here is the `fetch` request. If there are four users accessing this page with the same city parameter, Next.js will `fetch` the data after the first user's request and store it in the Data Cache. This means that for the next three users, no actual fetch call is made as its retrieved directly from the Data Cache. In other words, the Data Cache allows us to reduce calls to our original data source, providing a much smoother experience for our users.
145+
In the simple code I have above, all we are doing is making a `fetch` request to an API and dynamically passing in a `city` parameter so we can display travel guides for a given city. The main thing to focus on here is the `fetch` request. If there are four users accessing this page with the same city parameter, Next.js will `fetch` the data after the first user's request and store it in the Data Cache. This means that for the next three users, no actual fetch call is made as it's retrieved directly from the Data Cache. In other words, the Data Cache allows us to reduce calls to our original data source, providing a much smoother experience for our users.
146146

147147
#### `unstable_cache`
148148

@@ -151,23 +151,23 @@ So far, we have only seen how to cache `fetch` requests with the Data Cache, but
151151
If we go back to our previous example of city guides, we might want to pull data directly from our database. For this, we can use the `unstable_cache()` function that's provided by Next.js. This is like Next.js's version of the `cache()`, except it applies to the Data Cache.
152152

153153
```jsx {4}
154-
import { getGuides } from "./data";
155-
import { unstable_cache } from "next/cache";
154+
import { getGuides } from "./data"
155+
import { unstable_cache } from "next/cache"
156156

157157
const getCachedGuides = unstable_cache(
158-
async (city) => getGuides(city),
158+
async city => getGuides(city),
159159
["guides-cache-key"]
160-
);
160+
)
161161

162162
export default async function Page({ params }) {
163-
const guides = await getCachedGuides(params.city);
163+
const guides = await getCachedGuides(params.city)
164164
// ...
165165
}
166166
```
167167

168168
The code above is short, but it can be confusing if this is the first time you are seeing the `unstable_cache()` function being used, so let me explain.
169169

170-
I am using the `getGuides()` function to fetch guides from the database given a city (I left out its implementation as its not important). Then, I am declaring a `const` called `getCachedGuides` and using it to store the result I get from calling the `unstable_cache()` function. You will notice there are two parameters: the `getGuides()` function and an array. This array `['guides-cache-key']` acts like a unique identifier and assigns our cached guides a category.
170+
I am using the `getGuides()` function to fetch guides from the database given a city (I left out its implementation as it's not important). Then, I am declaring a `const` called `getCachedGuides` and using it to store the result I get from calling the `unstable_cache()` function. You will notice there are two parameters: the `getGuides()` function and an array. This array `['guides-cache-key']` acts like a unique identifier and assigns our cached guides a category.
171171

172172
Then, when we call `getCachedGuides()` inside Page, it will act exactly the same as the fetch requests did previously with the Data Cache.
173173

@@ -184,7 +184,7 @@ One approach is called time-based revalidation. This is pretty easy to grasp. Es
184184
const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
185185
// 3600 seconds equal 1 hour
186186
next: { revalidate: 3600 },
187-
});
187+
})
188188
```
189189

190190
With the code above, our application will `fetch` the most updated data from the data source every hour. One potential problem with this approach for our use case is that if an article is added within the revalidation period, users will only see the update after it's revalidated. Also, if no new content is added for hours on end, our code will still refresh the Data Cache.
@@ -196,7 +196,7 @@ When we want to invalidate the cache and fetch new data only when a new article
196196
On-demand revalidation shines when you want to refresh data in response to specific events.
197197

198198
```jsx {10}
199-
import { revalidatePath } from "next/cache";
199+
import { revalidatePath } from "next/cache"
200200

201201
export async function publishArticle() {
202202
try {
@@ -205,7 +205,7 @@ export async function publishArticle() {
205205
// ...
206206
}
207207

208-
revalidatePath("/guides/paris");
208+
revalidatePath("/guides/paris")
209209
}
210210
```
211211

@@ -221,13 +221,13 @@ We can control revalidation for specific tags.
221221
```jsx
222222
const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
223223
next: { tags: ["city-guides"] },
224-
});
224+
})
225225
```
226226

227227
Here, we're adding `["city-guides"]` tag to our `fetch` request so we can target it with `revalidatePath`.
228228

229229
```jsx {10}
230-
import { revalidateTag } from "next/cache";
230+
import { revalidateTag } from "next/cache"
231231

232232
export async function publishArticle() {
233233
try {
@@ -236,7 +236,7 @@ export async function publishArticle() {
236236
// ...
237237
}
238238

239-
revalidateTag("city-guides");
239+
revalidateTag("city-guides")
240240
}
241241
```
242242

@@ -252,7 +252,7 @@ There are a few straightforward ways to opt out of the Data Cache.
252252
```jsx
253253
const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
254254
cache: "no-store",
255-
});
255+
})
256256
```
257257

258258
In the example above, we've tweaked our `fetch` by adding `cache:"no-store"`. This opts a specific `fetch` request out of the Data Cache.
@@ -262,7 +262,7 @@ In the example above, we've tweaked our `fetch` by adding `cache:"no-store"`. Th
262262
If we want to change the caching behavior for an entire page and not just a specific `fetch` request, we can add this piece of code to the top level of our file.
263263

264264
```jsx
265-
export const dynamic = "force-dynamic";
265+
export const dynamic = "force-dynamic"
266266
```
267267

268268
##### `export const revalidate = 0`
@@ -281,28 +281,28 @@ The third type of cache I will cover is the Full Route Cache, which is also enab
281281

282282
In Next.js, the pages we render to our clients consist of HTML and something called the React Server Component Payload (RSCP). The payload contains instructions for how the client components should work together with the rendered server components to render the page. The Full Route Cache stores the HTML and RSCP for static pages at Build Time.
283283

284-
Now that we know what it stores, lets take a look at an example.
284+
Now that we know what it stores, let's take a look at an example.
285285

286286
```jsx
287-
import Link from "next/link";
287+
import Link from "next/link"
288288

289289
async function getBlogList() {
290290
const blogPosts = await fetch("https://api.blogposts.com/1", {
291291
cache: "force-cache",
292-
});
292+
})
293293

294-
const blogData = await blogPosts.json();
295-
return blogData;
294+
const blogData = await blogPosts.json()
295+
return blogData
296296
}
297297

298298
export default async function Page() {
299-
const blogData = await getBlogList();
299+
const blogData = await getBlogList()
300300

301301
return (
302302
<div>
303303
<h1>Blog Posts</h1>
304304
<ul>
305-
{blogData.map((post) => (
305+
{blogData.map(post => (
306306
<li key={post.slug}>
307307
<Link href={`/blog/${post.slug}`}>
308308
<a>{post.title}</a>
@@ -312,7 +312,7 @@ export default async function Page() {
312312
))}
313313
</ul>
314314
</div>
315-
);
315+
)
316316
}
317317
```
318318

@@ -351,18 +351,18 @@ The diagram below demonstrates the step-by-step process of how Full Route Cache
351351

352352
### Router Cache
353353

354-
The last cache I am going to talk about is the router cache. It is the only client-side cache in Next.js and can be the source of many bugs if not understood properly. This is mainly because it is enabled by default and caches routes that have already been visited before. Typically, the browser needs to re-fetch the data and re-render a page when it is visited again. This process, of course, takes longer than caching a page, which is why Next.js takes the caching approach. While this approach is an advantage when it comes to page loading speeds, it can also be quite frustrating. Lets take a look below at why.
354+
The last cache I am going to talk about is the router cache. It is the only client-side cache in Next.js and can be the source of many bugs if not understood properly. This is mainly because it is enabled by default and caches routes that have already been visited before. Typically, the browser needs to re-fetch the data and re-render a page when it is visited again. This process, of course, takes longer than caching a page, which is why Next.js takes the caching approach. While this approach is an advantage when it comes to page loading speeds, it can also be quite frustrating. Let's take a look below at why.
355355

356356
```jsx {10,11,12}
357357
// /page.jsx
358358
export default async function Page() {
359-
const blogData = await getBlogList();
359+
const blogData = await getBlogList()
360360

361361
return (
362362
<div>
363363
<h1>Blog Posts</h1>
364364
<ul>
365-
{blogData.map((post) => (
365+
{blogData.map(post => (
366366
<li key={post.slug}>
367367
<Link href={`/blog/${post.slug}`}>
368368
<a>{post.title}</a>
@@ -372,11 +372,11 @@ export default async function Page() {
372372
))}
373373
</ul>
374374
</div>
375-
);
375+
)
376376
}
377377
```
378378

379-
In the code I have above, when the user navigates to `/page`, its RSCP gets stored in the Router Cache. Similarly, when we navigate to `/blog`, its RSCP also gets cached. The problem arises when we navigate back from `/blog` to `/page` within a certain period. This means that if we have anything dynamic on `/page`, it wont get updated until after the cache is invalidated or we refresh our page. Essentially, whats happening is that Next.js is retrieving the cached RSCP for `/page` from the Router Cache, sacrificing data freshness for performance. This approach might lead us to see stale data if we were expecting it to update upon navigation.
379+
In the code I have above, when the user navigates to `/page`, its RSCP gets stored in the Router Cache. Similarly, when we navigate to `/blog`, its RSCP also gets cached. The problem arises when we navigate back from `/blog` to `/page` within a certain period. This means that if we have anything dynamic on `/page`, it won't get updated until after the cache is invalidated or we refresh our page. Essentially, what's happening is that Next.js is retrieving the cached RSCP for `/page` from the Router Cache, sacrificing data freshness for performance. This approach might lead us to see stale data if we were expecting it to update upon navigation.
380380

381381
Before we jump into the details of revalidating this cache, it's worth noting a side point: by default, Next.js also analyzes the structure of our web page, paying special attention to `<Link>` components that the user might click on next. It then preemptively fetches the data or resources for those pages to make them load faster upon request.
382382

@@ -417,14 +417,14 @@ In our example, this is useful when a user comments on one of the blog posts. Af
417417

418418
```jsx
419419
// After submitting a comment
420-
router.refresh();
420+
router.refresh()
421421
```
422422

423423
If our site has personalized content that changes based on user login status (like showing recommended articles), logging in or out should update what's displayed.
424424

425425
```jsx
426426
// On login or logout
427-
cookies.set("authToken", userToken); // or cookies.delete('authToken');
427+
cookies.set("authToken", userToken) // or cookies.delete('authToken');
428428
```
429429

430430
By using the above, we update the authentication cookies and the Router Cache is automatically invalidated.

0 commit comments

Comments
 (0)