Skip to content

Commit a43df32

Browse files
icyJosephdelbaoliveiraJuneezee
authored
Backport/docs fixes jan 25 16.1.x (#89124)
Backport new guide and glossary update. --------- Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Co-authored-by: Eng Zer Jun <engzerjun@gmail.com>
1 parent d6d5734 commit a43df32

File tree

3 files changed

+276
-15
lines changed

3 files changed

+276
-15
lines changed

docs/01-app/01-getting-started/07-fetching-data.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ export default async function Page({ params }) {
499499

500500
Start multiple requests by calling `fetch`, then await them with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). Requests begin as soon as `fetch` is called.
501501

502-
```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,23} switcher
502+
```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,24} switcher
503503
import Albums from './albums'
504504

505505
async function getArtist(username: string) {
@@ -534,7 +534,7 @@ export default async function Page({
534534
}
535535
```
536536

537-
```jsx filename="app/artist/[username]/page.js" highlight={3,8,19} switcher
537+
```jsx filename="app/artist/[username]/page.js" highlight={3,8,20} switcher
538538
import Albums from './albums'
539539

540540
async function getArtist(username) {
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
---
2+
title: Building public pages
3+
description: Learn how to build public, "static" pages that share data across users, such as landing pages, list pages (products, blogs, etc.), marketing and news sites.
4+
nav_title: Public pages
5+
---
6+
7+
Public pages show the same content to every user. Common examples include landing pages, marketing pages, and product pages.
8+
9+
Since data is shared, these kind of pages can be [prerendered](/docs/app/glossary#prerendering) ahead of time and reused. This leads to faster page loads and lower server costs.
10+
11+
This guide will show you how to build public pages that share data across users.
12+
13+
## Example
14+
15+
As an example, we'll build a product list page.
16+
17+
We'll start with a static header, add a product list with async external data, and learn how to render it without blocking the response. Finally, we'll add a user-specific promotion banner without switching the entire page to [request-time rendering](/docs/app/glossary#request-time-rendering).
18+
19+
You can find the resources used in this example here:
20+
21+
- [Video](https://youtu.be/F6romq71KtI)
22+
- [Demo](https://cache-components-public-pages.labs.vercel.dev/)
23+
- [Code](https://github.com/vercel-labs/cache-components-public-pages)
24+
25+
### Step 1: Add a simple header
26+
27+
Let's start with a simple header.
28+
29+
```tsx filename="app/products/page.tsx"
30+
// Static component
31+
function Header() {
32+
return <h1>Shop</h1>
33+
}
34+
35+
export default async function Page() {
36+
return (
37+
<>
38+
<Header />
39+
</>
40+
)
41+
}
42+
```
43+
44+
#### Static components
45+
46+
The `<Header />` component doesn't depend on any inputs that change between requests, such as: external data, request headers, route params, the current time, or random values.
47+
48+
Since its output never changes and can be determined ahead of time, this kind of component is called a **static** component. With no reason to wait for a request, Next.js can safely **prerender** the page at [build time](/docs/app/glossary#build-time).
49+
50+
We can confirm this by running [`next build`](/docs/app/api-reference/cli/next#next-build-options).
51+
52+
```bash filename="Terminal"
53+
Route (app) Revalidate Expire
54+
┌ ○ /products 15m 1y
55+
└ ○ /_not-found
56+
57+
○ (Static) prerendered as static content
58+
```
59+
60+
Notice that the product route is marked as static, even though we didn't add any explicit configuration.
61+
62+
### Step 2: Add the product list
63+
64+
Now, let's fetch and render our product list.
65+
66+
```tsx filename="app/products/page.tsx"
67+
import db from '@/db'
68+
import { List } from '@/app/products/ui'
69+
70+
function Header() {}
71+
72+
// Dynamic component
73+
async function ProductList() {
74+
const products = await db.product.findMany()
75+
return <List items={products} />
76+
}
77+
78+
export default async function Page() {
79+
return (
80+
<>
81+
<Header />
82+
<ProductList />
83+
</>
84+
)
85+
}
86+
```
87+
88+
Unlike the header, the product list depends on external data.
89+
90+
#### Dynamic components
91+
92+
Since this data **can** change over time, the rendered output is no longer guaranteed to be stable. This makes the product list a **dynamic** component.
93+
94+
Without guidance, the framework assumes you want to fetch **fresh** data on every user request. This design choice reflects standard web behavior where a new server request renders the page.
95+
96+
However, if this component is rendered at request time, fetching its data will delay the **entire** route from responding. If we refresh the page, we can see this happen.
97+
98+
Even though the header is rendered instantly, it can't be sent to the browser until the product list has finished fetching.
99+
100+
To protect us from this performance cliff, Next.js will show us a [warning](/docs/messages/blocking-route) the first time we **await** data: `Blocking data was accessed outside of Suspense`
101+
102+
At this point, we have to decide how to **unblock** the response. Either:
103+
104+
- [**Cache**](/docs/app/glossary#cache-components) the component, so it becomes **stable** and can be prerendered with the rest of the page.
105+
- [**Stream**](/docs/app/glossary#streaming) the component, so it becomes **non-blocking** and the rest of the page doesn't have to wait for it.
106+
107+
In our case, the product catalog is shared across all users, so caching is the right choice.
108+
109+
### Cache components
110+
111+
We can mark a function as cacheable using the [`'use cache'`](/docs/app/api-reference/directives/use-cache) directive.
112+
113+
```tsx filename="app/products/page.tsx"
114+
import db from '@/db'
115+
import { List } from '@/app/products/ui'
116+
117+
function Header() {}
118+
119+
// Cache component
120+
async function ProductList() {
121+
'use cache'
122+
const products = await db.product.findMany()
123+
return <List items={products} />
124+
}
125+
126+
export default async function Page() {
127+
return (
128+
<>
129+
<Header />
130+
<ProductList />
131+
</>
132+
)
133+
}
134+
```
135+
136+
This turns it into a [cache component](/docs/app/glossary#cache-components). The first time it runs, whatever we return will be cached and reused.
137+
138+
If a cache component's inputs are available **before** the request arrives, it can be prerendered just like a static component.
139+
140+
If we refresh again, we can see the page loads instantly because the cache component doesn't block the response. And, if we run `next build` again, we can confirm the page is still static:
141+
142+
```bash filename="Terminal"
143+
Route (app) Revalidate Expire
144+
┌ ○ /products 15m 1y
145+
└ ○ /_not-found
146+
147+
○ (Static) prerendered as static content
148+
```
149+
150+
But, pages rarely stay static forever.
151+
152+
### Step 3: Add a dynamic promotion banner
153+
154+
Sooner or later, even simple pages need some dynamic content. To demonstrate this, let's add a promotional banner:
155+
156+
```tsx filename="app/products/page.tsx"
157+
import db from '@/db'
158+
import { List, Promotion } from '@/app/products/ui'
159+
import { getPromotion } from '@/app/products/data'
160+
161+
function Header() {}
162+
163+
async function ProductList() {}
164+
165+
// Dynamic component
166+
async function PromotionContent() {
167+
const promotion = await getPromotion()
168+
return <Promotion data={promotion} />
169+
}
170+
171+
export default async function Page() {
172+
return (
173+
<>
174+
<PromotionContent />
175+
<Header />
176+
<ProductList />
177+
</>
178+
)
179+
}
180+
```
181+
182+
Once again, this starts off as dynamic. And as before, introducing blocking behavior triggers a Next.js warning.
183+
184+
Last time, the data was shared, so it could be cached. This time, the promotion depends on request specific inputs like the user's location and A/B tests, so we can't cache our way out of the blocking behavior.
185+
186+
### Partial prerendering
187+
188+
Adding dynamic content doesn't mean we have to go back to a fully blocking render. We can unblock the response with streaming.
189+
190+
Next.js supports streaming by default. We can use a [Suspense boundary](/docs/app/glossary#suspense-boundary) to tell the framework where to slice the streamed response into _chunks_, and what fallback UI to show while content loads.
191+
192+
```tsx filename="app/products/page.tsx"
193+
import { Suspense } from 'react'
194+
import db from '@/db'
195+
import { List, Promotion, PromotionSkeleton } from '@/app/products/ui'
196+
import { getPromotion } from '@/app/products/data'
197+
198+
function Header() {}
199+
200+
async function ProductList() {}
201+
202+
// Dynamic component (streamed)
203+
async function PromotionContent() {
204+
const promotion = await getPromotion()
205+
return <Promotion data={promotion} />
206+
}
207+
208+
export default async function Page() {
209+
return (
210+
<>
211+
<Suspense fallback={<PromotionSkeleton />}>
212+
<PromotionContent />
213+
</Suspense>
214+
<Header />
215+
<ProductList />
216+
</>
217+
)
218+
}
219+
```
220+
221+
The fallback is prerendered alongside the rest of our static and cached content. The inner component streams in later, once its async work completes.
222+
223+
With this change, Next.js can separate prerenderable work from request-time work and the route becomes [partially prerendered](/docs/app/glossary#partial-prerendering-ppr).
224+
225+
Again, we can confirm this by running `next build`:
226+
227+
```bash filename="Terminal"
228+
Route (app) Revalidate Expire
229+
┌ ◐ /products 15m 1y
230+
└ ◐ /_not-found
231+
232+
◐ (Partial Prerender) Prerendered as static HTML with dynamic server-streamed content
233+
```
234+
235+
At [**build time**](/docs/app/glossary#build-time), most of the page, including the header, product list and promotion fallback, is rendered, cached and pushed to a content delivery network.
236+
237+
At [**request time**](/docs/app/glossary#request-time), the prerendered part is served instantly from a CDN node close to the user.
238+
239+
In parallel, the user specific promotion is rendered on the server, streamed to the client, and swapped into the fallback slot.
240+
241+
If we refresh the page one last time, we can see most of the page loads instantly, while the dynamic parts stream in as they become available.
242+
243+
### Next steps
244+
245+
We've learned how to build mostly static pages that include pockets of dynamic content.
246+
247+
We started with a static page, added async work, and resolved the blocking behavior by caching what could be prerendered, and streaming what couldn't.
248+
249+
In future guides, we'll learn how to:
250+
251+
- Revalidate prerendered pages or cached data.
252+
- Create variants of the same page with route params.
253+
- Create private pages with personalized user data.
Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,9 @@ The process of dividing your application into smaller JavaScript chunks based on
4444

4545
# D
4646

47-
## Dynamic APIs
48-
49-
Functions that access request-specific data, causing a component to opt into [dynamic rendering](#dynamic-rendering). These include:
50-
51-
- [`cookies()`](/docs/app/api-reference/functions/cookies) - Access request cookies
52-
- [`headers()`](/docs/app/api-reference/functions/headers) - Access request headers
53-
- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) - Access URL query parameters
54-
- [`draftMode()`](/docs/app/api-reference/functions/draft-mode) - Enable or check draft mode
55-
5647
## Dynamic rendering
5748

58-
When a component is rendered at [request time](#request-time) rather than [build time](#build-time). A component becomes dynamic when it uses [Dynamic APIs](#dynamic-apis).
49+
See [Request-time rendering](#request-time-rendering).
5950

6051
## Dynamic route segments
6152

@@ -131,6 +122,10 @@ Information about a page used by browsers and search engines, such as title, des
131122

132123
Caching the return value of a function so that calling the same function multiple times during a render pass (request) only executes it once. In Next.js, fetch requests with the same URL and options are automatically memoized. Learn more about [React Cache](https://react.dev/reference/react/cache).
133124

125+
## Middleware
126+
127+
See [Proxy](#proxy).
128+
134129
# N
135130

136131
## Not Found
@@ -161,7 +156,7 @@ Loading a route in the background before the user navigates to it. Next.js autom
161156

162157
## Prerendering
163158

164-
Generating HTML for a page ahead of time, either at build time (static rendering) or in the background (revalidation). The pre-rendered HTML is served immediately, then hydrated on the client.
159+
When a component is rendered at [build time](#build-time) or in the background during [revalidation](#revalidation). The result is HTML and [RSC Payload](#rsc-payload), which can be cached and served from a CDN. Prerendering is the default for components that don't use [Request-time APIs](#request-time-apis).
165160

166161
## Proxy
167162

@@ -177,6 +172,19 @@ Sending users from one URL to another. In Next.js, redirects can be configured i
177172

178173
The time when a user makes a request to your application. At request time, dynamic routes are rendered, cookies and headers are accessible, and request-specific data can be used.
179174

175+
## Request-time APIs
176+
177+
Functions that access request-specific data, causing a component to opt into [request-time rendering](#request-time-rendering). These include:
178+
179+
- [`cookies()`](/docs/app/api-reference/functions/cookies) - Access request cookies
180+
- [`headers()`](/docs/app/api-reference/functions/headers) - Access request headers
181+
- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) - Access URL query parameters
182+
- [`draftMode()`](/docs/app/api-reference/functions/draft-mode) - Enable or check draft mode
183+
184+
## Request-time rendering
185+
186+
When a component is rendered at [request time](#request-time) rather than [build time](#build-time). A component becomes dynamic when it uses [Request-time APIs](#request-time-apis).
187+
180188
## Revalidation
181189

182190
The process of updating cached data. Can be time-based (using [`cacheLife()`](/docs/app/api-reference/functions/cacheLife) to set cache duration) or on-demand (using [`cacheTag()`](/docs/app/api-reference/functions/cacheTag) to tag data, then [`updateTag()`](/docs/app/api-reference/functions/updateTag) to invalidate). Learn more in [Caching and Revalidating](/docs/app/getting-started/caching-and-revalidating).
@@ -221,15 +229,15 @@ A deployment mode that generates a fully static site with HTML, CSS, and JavaScr
221229

222230
## Static rendering
223231

224-
When a component is rendered at [build time](#build-time) or in the background during [revalidation](#revalidation). The result is cached and can be served from a CDN. Static rendering is the default for components that don't use [Dynamic APIs](#dynamic-apis).
232+
See [Prerendering](#prerendering).
225233

226234
## Static Assets
227235

228236
Files such as images, fonts, videos, and other media that are served directly without processing. Static assets are typically stored in the `public` directory and referenced by their relative paths. Learn more in [Static Assets](/docs/app/api-reference/file-conventions/public-folder).
229237

230238
## Static Shell
231239

232-
The pre-rendered HTML structure of a page that's served immediately to the browser. With [Partial Prerendering](#partial-prerendering-ppr), the static shell includes all statically renderable content plus [Suspense boundary](#suspense-boundary) fallbacks for dynamic content that streams in later.
240+
The prerendered HTML structure of a page that's served immediately to the browser. With [Partial Prerendering](#partial-prerendering-ppr), the static shell includes all statically renderable content plus [Suspense boundary](#suspense-boundary) fallbacks for dynamic content that streams in later.
233241

234242
## Streaming
235243

0 commit comments

Comments
 (0)