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
4 changes: 4 additions & 0 deletions .github/workflows/graphite_ci_optimizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ jobs:
name: Graphite CI Optimizer
runs-on: ubuntu-latest
outputs:
# `== 'true'` comparison: If `step.check-skip` fails (`continue-on-error`), that output will
# be an empty string, which sets our `skip` output to `'false'`.
skip: ${{ env.HAS_BYPASS_LABEL == 'false' && steps.check-skip.outputs.skip == 'true' }}
steps:
- name: Optimize CI
id: check-skip
# Graphite's action is designed to fail open, but still sometimes fails if GH's infra flakes
continue-on-error: true
uses: withgraphite/graphite-ci-action@ee395f3a78254c006d11339669c6cabddf196f72 # main
with:
graphite_token: ${{ secrets.GRAPHITE_TOKEN }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
---
title: unstable_io
description: API Reference for the unstable_io function.
title: io
description: API Reference for the io function.
version: draft
---

`unstable_io()` informs Next.js that an IO operation will follow this call. When [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) is not enabled or when rendering in Pages Router, signifying IO in this way is not meaningful and the call will always resolve immediately. When Cache Components is enabled, you may be required to add `await unstable_io()` preceding synchronous IO that is encountered while prerendering pages (`new Date()` for example). Additionally, if you want to avoid having genuine uncached IO invoked while prerendering, you shield it by preceeding it with `await unstable_io()`.
`io()` informs Next.js that an IO operation will follow this call. When [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) is not enabled or when rendering in Pages Router, signifying IO in this way is not meaningful and the call will always resolve immediately. When Cache Components is enabled, you may be required to add `await io()` preceding synchronous IO that is encountered while prerendering pages (`new Date()` for example). Additionally, if you want to avoid having genuine uncached IO invoked while prerendering, you shield it by preceeding it with `await io()`.

```ts filename="app/page.tsx" switcher
import { unstable_io } from 'next/cache'
import { io } from 'next/cache'
import { db } from '@/lib/db'

export default async function Page() {
// Synchronous IO: new Date() would fail during prerender without this
await unstable_io()
await io()
const now = new Date().toISOString()

// Async IO: the query would run and be discarded during prerender;
// unstable_io() above lets Next.js skip it entirely
// io() above lets Next.js skip it entirely
const orders = await db.query('SELECT * FROM orders LIMIT 10')

return (
Expand All @@ -33,16 +33,16 @@ export default async function Page() {
```

```js filename="app/page.js" switcher
import { unstable_io } from 'next/cache'
import { io } from 'next/cache'
import { db } from '@/lib/db'

export default async function Page() {
// Synchronous IO: new Date() would fail during prerender without this
await unstable_io()
await io()
const now = new Date().toISOString()

// Async IO: the query would run and be discarded during prerender;
// unstable_io() above lets Next.js skip it entirely
// io() above lets Next.js skip it entirely
const orders = await db.query('SELECT * FROM orders LIMIT 10')

return (
Expand All @@ -62,16 +62,16 @@ export default async function Page() {

[`connection()`](/docs/app/api-reference/functions/connection) requires an active HTTP request context and signals that the component needs request-specific data. It is imported from `next/server`.

`unstable_io()` does not require a request context. It can be used inside `"use cache"` scopes, client components, and anywhere you perform IO that should not be included in a static prerender. It is imported from `next/cache`.
`io()` does not require a request context. It can be used inside `"use cache"` scopes, client components, and anywhere you perform IO that should not be included in a static prerender. It is imported from `next/cache`.

Use `connection()` when you need the request itself (cookies, headers, etc.). Use `unstable_io()` when you perform IO that is independent of the request but should still prevent static prerendering.
Use `connection()` when you need the request itself (cookies, headers, etc.). Use `io()` when you perform IO that is independent of the request but should still prevent static prerendering.

## Reference

### Type

```ts
function unstable_io(): Promise<void>
function io(): Promise<void>
```

### Parameters
Expand All @@ -84,12 +84,12 @@ function unstable_io(): Promise<void>

## Good to know

- `unstable_io()` is imported from `next/cache`, not `next/server`.
- Inside `"use cache"` scopes, `unstable_io()` resolves immediately. The cache captures the IO result at fill time.
- In client components, `unstable_io()` resolves immediately since there is no prerender context in the browser.
- `io()` is imported from `next/cache`, not `next/server`.
- Inside `"use cache"` scopes, `io()` resolves immediately. The cache captures the IO result at fill time.
- In client components, `io()` resolves immediately since there is no prerender context in the browser.

### Version History

| Version | Changes |
| --------- | -------------------- |
| `v16.x.x` | `unstable_io` added. |
| Version | Changes |
| --------- | ----------- |
| `v16.x.x` | `io` added. |
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"registry": "https://registry.npmjs.org/"
}
},
"version": "16.3.0-canary.14"
"version": "16.3.0-canary.16"
}
2 changes: 1 addition & 1 deletion packages/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"keywords": [
"react",
"next",
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
Expand All @@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
"@next/eslint-plugin-next": "16.3.0-canary.14",
"@next/eslint-plugin-next": "16.3.0-canary.16",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/font/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-bundle-analyzer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-codemod/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-env/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/env",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-mdx/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/next-playwright/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/playwright",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-playwright"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-plugin-storybook/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-polyfill-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-polyfill-nomodule/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-routing/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/routing",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-rspack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-rspack",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-rspack"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-swc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"private": true,
"files": [
"native/"
Expand Down
2 changes: 1 addition & 1 deletion packages/next/cache.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export {

export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'

export { unstable_io } from 'next/dist/server/request/io'
export { io } from 'next/dist/server/request/io'

import { cacheTag } from 'next/dist/server/use-cache/cache-tag'

Expand Down
6 changes: 3 additions & 3 deletions packages/next/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (process.env.NEXT_RUNTIME === '') {
}
},
unstable_noStore: function unstable_noStore() {},
unstable_io: require('next/dist/client/request/io.browser').unstable_io,
io: require('next/dist/client/request/io.browser').io,

updateTag: notAvailableInClient('updateTag'),
revalidateTag: notAvailableInClient('revalidateTag'),
Expand Down Expand Up @@ -46,7 +46,7 @@ if (process.env.NEXT_RUNTIME === '') {
unstable_noStore:
require('next/dist/server/web/spec-extension/unstable-no-store')
.unstable_noStore,
unstable_io: require('next/dist/server/request/io').unstable_io,
io: require('next/dist/server/request/io').io,
cacheLife: require('next/dist/server/use-cache/cache-life').cacheLife,
cacheTag: require('next/dist/server/use-cache/cache-tag').cacheTag,
}
Expand Down Expand Up @@ -94,4 +94,4 @@ exports.unstable_cacheLife = cacheExports.unstable_cacheLife
exports.cacheTag = cacheExports.cacheTag
exports.unstable_cacheTag = cacheExports.unstable_cacheTag
exports.refresh = cacheExports.refresh
exports.unstable_io = cacheExports.unstable_io
exports.io = cacheExports.io
14 changes: 7 additions & 7 deletions packages/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next",
"version": "16.3.0-canary.14",
"version": "16.3.0-canary.16",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
Expand Down Expand Up @@ -101,7 +101,7 @@
]
},
"dependencies": {
"@next/env": "16.3.0-canary.14",
"@next/env": "16.3.0-canary.16",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
Expand Down Expand Up @@ -165,11 +165,11 @@
"@modelcontextprotocol/sdk": "1.18.1",
"@mswjs/interceptors": "0.23.0",
"@napi-rs/triples": "1.2.0",
"@next/font": "16.3.0-canary.14",
"@next/polyfill-module": "16.3.0-canary.14",
"@next/polyfill-nomodule": "16.3.0-canary.14",
"@next/react-refresh-utils": "16.3.0-canary.14",
"@next/swc": "16.3.0-canary.14",
"@next/font": "16.3.0-canary.16",
"@next/polyfill-module": "16.3.0-canary.16",
"@next/polyfill-nomodule": "16.3.0-canary.16",
"@next/react-refresh-utils": "16.3.0-canary.16",
"@next/swc": "16.3.0-canary.16",
"@opentelemetry/api": "1.6.0",
"@playwright/test": "1.58.2",
"@rspack/core": "1.6.7",
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/build/templates/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
NodeNextResponse,
} from '../../server/base-http/node' with { 'turbopack-transition': 'next-server-utility' }
import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr' with { 'turbopack-transition': 'next-server-utility' }
import { isRSCRequestHeader } from '../../server/lib/is-rsc-request' with { 'turbopack-transition': 'next-server-utility' }
import {
getFallbackRouteParams,
getPlaceholderFallbackRouteParams,
Expand Down Expand Up @@ -294,7 +295,8 @@ export async function handler(
// NOTE: Don't delete headers[RSC] yet, it still needs to be used in renderToHTML later

const isRSCRequest =
getRequestMeta(req, 'isRSCRequest') ?? Boolean(req.headers[RSC_HEADER])
getRequestMeta(req, 'isRSCRequest') ??
isRSCRequestHeader(req.headers[RSC_HEADER])

const isPossibleServerAction = getIsPossibleServerAction(req)

Expand Down Expand Up @@ -426,7 +428,7 @@ export async function handler(
const isInstantNavigationTest =
exposeTestingApi &&
(req.headers[NEXT_INSTANT_PREFETCH_HEADER] === '1' ||
(req.headers[RSC_HEADER] === undefined &&
(!isRSCRequestHeader(req.headers[RSC_HEADER]) &&
typeof req.headers.cookie === 'string' &&
req.headers.cookie.includes(NEXT_INSTANT_TEST_COOKIE + '=')))

Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/client/request/io.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ const resolvedIOPromise: Promise<void> = Promise.resolve(undefined)
;(resolvedIOPromise as any).value = undefined

/**
* Browser implementation of unstable_io(). On the client there is no
* Browser implementation of io(). On the client there is no
* prerender context so we always resolve immediately.
*/
export function unstable_io(): Promise<void> {
export function io(): Promise<void> {
return resolvedIOPromise
}
15 changes: 7 additions & 8 deletions packages/next/src/client/script.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { ScriptHTMLAttributes } from 'react'
import { HeadManagerContext } from '../shared/lib/head-manager-context.shared-runtime'
import { setAttributesFromProps } from './set-attributes-from-props'
import { requestIdleCallback } from './request-idle-callback'
import { htmlEscapeJsonString } from '../shared/lib/htmlescape'

const ScriptCache = new Map()
const LoadCache = new Set()
Expand Down Expand Up @@ -327,10 +328,9 @@ function Script(props: ScriptProps): JSX.Element | null {
<script
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `(self.__next_s=self.__next_s||[]).push(${JSON.stringify([
0,
{ ...restProps, id },
])})`,
__html: `(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
JSON.stringify([0, { ...restProps, id }])
)})`,
}}
/>
)
Expand All @@ -351,10 +351,9 @@ function Script(props: ScriptProps): JSX.Element | null {
<script
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `(self.__next_s=self.__next_s||[]).push(${JSON.stringify([
src,
{ ...restProps, id },
])})`,
__html: `(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString(
JSON.stringify([src, { ...restProps, id }])
)})`,
}}
/>
)
Expand Down
Loading
Loading