Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/build_reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,22 @@ jobs:
with:
fetch-depth: 25

# Cache pnpm store on GitHub-hosted runners (self-hosted runners have their own persistent storage)
- name: Get pnpm store directory
if: ${{ runner.environment == 'github-hosted' }}
id: get-store-path
run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT

- name: Cache pnpm store
if: ${{ runner.environment == 'github-hosted' }}
uses: actions/cache@v4
timeout-minutes: 5
id: cache-pnpm-store
with:
path: ${{ steps.get-store-path.outputs.STORE_PATH }}
key: pnpm-store-v2-${{ hashFiles('pnpm-lock.yaml') }}
# Do not use restore-keys since it leads to indefinite growth of the cache.

# local action -> needs to run after checkout
- name: Install Rust
uses: ./.github/actions/setup-rust
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/trigger_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ on:
default: false
type: boolean

secrets:
RELEASE_BOT_GITHUB_TOKEN:
required: true

name: Trigger Release

env:
Expand Down Expand Up @@ -62,6 +58,9 @@ jobs:

- name: Check token
run: gh auth status
# This sometimes fails for unknown reasons.
# Ignoring failures for now to check if a failure truly implies a failed publish.
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}

Expand Down
4 changes: 2 additions & 2 deletions docs/01-app/01-getting-started/07-fetching-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ export default async function Page({ params }) {

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.

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

async function getArtist(username: string) {
Expand Down Expand Up @@ -534,7 +534,7 @@ export default async function Page({
}
```

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

async function getArtist(username) {
Expand Down
44 changes: 16 additions & 28 deletions docs/01-app/02-guides/public-static-pages.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Building public pages
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.
nav_title: Building public pages
nav_title: Public pages
---

Public pages show the same content to every user. Common examples include landing pages, marketing pages, and product pages.
Expand All @@ -26,7 +26,7 @@ You can find the resources used in this example here:

Let's start with a simple header.

```tsx filename=app/products/page.tsx
```tsx filename="app/products/page.tsx"
// Static component
function Header() {
return <h1>Shop</h1>
Expand All @@ -45,13 +45,11 @@ export default async function Page() {

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.

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).
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).

We can confirm this by running [`next build`](/docs/app/api-reference/cli/next#next-build-options).

```bash filename=terminal
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ○ /products 15m 1y
└ ○ /_not-found
Expand All @@ -65,7 +63,7 @@ Notice that the product route is marked as static, even though we didn't add any

Now, let's fetch and render our product list.

```tsx filename=page.tsx
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List } from '@/app/products/ui'

Expand All @@ -91,15 +89,11 @@ Unlike the header, the product list depends on external data.

#### Dynamic components

Because this data **can** change over time, the rendered output is no longer guaranteed to be stable.

This makes it a **dynamic** component.
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.

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.

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.
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.

Even though the header is rendered instantly, it can't be sent to the browser until the product list has finished fetching.

Expand All @@ -116,7 +110,7 @@ In our case, the product catalog is shared across all users, so caching is the r

We can mark a function as cacheable using the [`'use cache'`](/docs/app/api-reference/directives/use-cache) directive.

```tsx filename=page.tsx
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List } from '@/app/products/ui'

Expand All @@ -139,15 +133,13 @@ export default async function Page() {
}
```

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.
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.

If a cache component's inputs are available **before** the request arrives, it can be prerendered just like a static component.

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:

```bash filename=terminal
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ○ /products 15m 1y
└ ○ /_not-found
Expand All @@ -159,11 +151,9 @@ But, pages rarely stay static forever.

### Step 3: Add a dynamic promotion banner

Sooner or later, even simple pages need some dynamic content.

To demonstrate this, let's add a promotional banner.
Sooner or later, even simple pages need some dynamic content. To demonstrate this, let's add a promotional banner:

```tsx filename=app/products/page.tsx
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List, Promotion } from '@/app/products/ui'
import { getPromotion } from '@/app/products/data'
Expand Down Expand Up @@ -191,17 +181,15 @@ export default async function Page() {

Once again, this starts off as dynamic. And as before, introducing blocking behavior triggers a Next.js warning.

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.
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.

### Partial prerendering

Adding dynamic content doesn't mean we have to go back to a fully blocking render. We can unblock the response with streaming.

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.

```tsx filename=app/products/page.tsx
```tsx filename="app/products/page.tsx"
import { Suspense } from 'react'
import db from '@/db'
import { List, Promotion, PromotionSkeleton } from '@/app/products/ui'
Expand Down Expand Up @@ -234,9 +222,9 @@ The fallback is prerendered alongside the rest of our static and cached content.

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).

Again, we can confirm this by running `next build`
Again, we can confirm this by running `next build`:

```bash filename=terminal
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ◐ /products 15m 1y
└ ◐ /_not-found
Expand Down
71 changes: 71 additions & 0 deletions errors/deploymentid-invalid-characters.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: '`deploymentId` contains invalid characters'
---

## Why This Error Occurred

The `deploymentId` in your `next.config.js` contains characters that are not allowed. Only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), and underscores (\_) are permitted.

## Possible Ways to Fix It

### Option 1: Remove Invalid Characters

Remove or replace any characters that are not alphanumeric, hyphens, or underscores:

```js
// ✅ Correct
module.exports = {
deploymentId: 'my-deployment-123', // Only alphanumeric, hyphens, underscores
}

// ❌ Incorrect
module.exports = {
deploymentId: 'my deployment 123', // Contains spaces
deploymentId: 'my.deployment.123', // Contains dots
deploymentId: 'my/deployment/123', // Contains slashes
deploymentId: 'my@deployment#123', // Contains special characters
}
```

### Option 2: Sanitize the Deployment ID

If you're generating the ID from environment variables or other sources, sanitize it to remove invalid characters:

```js
// next.config.js
const rawId = process.env.DEPLOYMENT_ID || 'default-id'
// Remove all characters that are not alphanumeric, hyphens, or underscores
const sanitizedId = rawId.replace(/[^a-zA-Z0-9_-]/g, '')

module.exports = {
deploymentId: sanitizedId,
}
```

### Option 3: Use a Valid Format

Common valid formats include:

```js
// next.config.js
module.exports = {
// Using hyphens
deploymentId: 'my-deployment-id',

// Using underscores
deploymentId: 'my_deployment_id',

// Alphanumeric only
deploymentId: 'mydeployment123',

// Mixed format
deploymentId: 'my-deployment_123',
}
```

## Additional Information

- The deployment ID is used for skew protection and asset versioning
- Invalid characters can cause issues with URL encoding and routing
- Keep the ID URL-friendly by using only the allowed character set
- The validation ensures compatibility across different systems and environments
33 changes: 33 additions & 0 deletions errors/deploymentid-not-a-string.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: '`deploymentId` must be a string'
---

## Why This Error Occurred

The `deploymentId` option in your `next.config.js` must be a string value.

## Possible Ways to Fix It

Ensure your `deploymentId` is a string:

```js
// ✅ Correct
module.exports = {
deploymentId: 'my-deployment-123',
}

// ✅ Using environment variables
module.exports = {
deploymentId: process.env.GIT_HASH || 'default-id',
}

// ❌ Incorrect
module.exports = {
deploymentId: 12345, // Must be a string, not a number
}
```

The `deploymentId` can be:

- A string: `deploymentId: 'my-deployment-123'`
- `undefined` (will use `NEXT_DEPLOYMENT_ID` environment variable if set)
37 changes: 37 additions & 0 deletions errors/deploymentid-too-long.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: '`deploymentId` exceeds maximum length'
---

## Why This Error Occurred

The `deploymentId` in your `next.config.js` exceeds the maximum length of 32 characters.

## Possible Ways to Fix It

### Option 1: Shorten Your Deployment ID

Reduce the length of your `deploymentId` to 32 characters or less:

```js
// next.config.js
module.exports = {
deploymentId: 'my-short-id', // ✅ 12 characters
}
```

### Option 2: Truncate Environment Variables

If using environment variables, ensure the value is truncated to 32 characters:

```js
// next.config.js
module.exports = {
deploymentId: process.env.DEPLOYMENT_ID?.substring(0, 32),
}
```

## Additional Information

- The deployment ID is used for skew protection and asset versioning
- Keep it concise but meaningful for your use case
- Consider using hashes or shortened identifiers if you need unique values
5 changes: 4 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -984,5 +984,8 @@
"983": "Invariant: global-error module is required but not found in loader tree",
"984": "LRUCache: calculateSize returned %s, but size must be > 0. Items with size 0 would never be evicted, causing unbounded cache growth.",
"985": "No response is returned from route handler '%s'. Expected a Response object but received '%s' (method: %s, url: %s). Ensure you return a \\`Response\\` or a \\`NextResponse\\` in all branches of your handler.",
"986": "Server Action arguments list is too long (%s). Maximum allowed is %s."
"986": "Server Action arguments list is too long (%s). Maximum allowed is %s.",
"987": "Invalid \\`deploymentId\\` configuration: exceeds maximum length of 32 characters. See https://nextjs.org/docs/messages/deploymentid-too-long",
"988": "Invalid \\`deploymentId\\` configuration: must be a string. See https://nextjs.org/docs/messages/deploymentid-not-a-string",
"989": "Invalid \\`deploymentId\\` configuration: contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed. See https://nextjs.org/docs/messages/deploymentid-invalid-characters"
}
3 changes: 3 additions & 0 deletions packages/next/src/build/generate-routes-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface GenerateRoutesManifestOptions {
restrictedRedirectPaths: string[]
isAppPPREnabled: boolean
appType: 'pages' | 'app' | 'hybrid'
deploymentId?: string
}

export interface GenerateRoutesManifestResult {
Expand All @@ -60,6 +61,7 @@ export function generateRoutesManifest(
restrictedRedirectPaths,
isAppPPREnabled,
appType,
deploymentId,
} = options

const sortedRoutes = sortPages([...pageKeys.pages, ...(pageKeys.app ?? [])])
Expand Down Expand Up @@ -134,6 +136,7 @@ export function generateRoutesManifest(
queryHeader: NEXT_REWRITTEN_QUERY_HEADER,
},
skipProxyUrlNormalize: config.skipProxyUrlNormalize,
deploymentId: deploymentId || undefined,
ppr: isAppPPREnabled
? {
chain: {
Expand Down
Loading
Loading