diff --git a/.cargo/config.toml b/.cargo/config.toml
index a38223d24f380..18dc923049119 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -2,15 +2,25 @@
CARGO_WORKSPACE_DIR = { value = "", relative = true }
[build]
-
+rustflags = [
+ "--cfg",
+ "tokio_unstable",
+ "-Zshare-generics=y", # make the current crate share its generic instantiations
+ "-Zthreads=8", # parallel frontend https://blog.rust-lang.org/2023/11/09/parallel-rustc.html
+ "-Csymbol-mangling-version=v0",
+]
rustdocflags = []
[target.x86_64-pc-windows-msvc]
linker = "rust-lld"
rustflags = ["-C", "target-feature=+crt-static"]
+
[target.i686-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
+[target.aarch64-apple-darwin]
+linker = "rust-lld"
+
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
@@ -27,11 +37,3 @@ rustflags = [
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
-
-[target.'cfg(all())']
-rustflags = [
- "--cfg",
- "tokio_unstable",
- "-Zshare-generics=y",
- "-Csymbol-mangling-version=v0",
-]
diff --git a/.eslintignore b/.eslintignore
index 315d281d5686b..7e2800e2acb98 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,6 +4,7 @@ node_modules
**/.vscode/**
**/dist/**
e2e-tests/**
+examples/cms-sanity/sanity.types.ts
examples/with-eslint/**
examples/with-typescript-eslint-jest/**
examples/with-kea/**
diff --git a/.github/actions/next-integration-stat/package.json b/.github/actions/next-integration-stat/package.json
index f7139529c5c8a..efe20f8ad066b 100644
--- a/.github/actions/next-integration-stat/package.json
+++ b/.github/actions/next-integration-stat/package.json
@@ -22,7 +22,7 @@
},
"engines": {
"node": ">=18.17.0",
- "pnpm": "8.15.4"
+ "pnpm": "8.15.7"
},
- "packageManager": "pnpm@8.15.4"
+ "packageManager": "pnpm@8.15.7"
}
diff --git a/.github/actions/next-stats-action/package.json b/.github/actions/next-stats-action/package.json
index 1be4a941450e5..4269e374084f8 100644
--- a/.github/actions/next-stats-action/package.json
+++ b/.github/actions/next-stats-action/package.json
@@ -19,7 +19,7 @@
},
"engines": {
"node": ">=18.17.0",
- "pnpm": "8.15.4"
+ "pnpm": "8.15.7"
},
- "packageManager": "pnpm@8.15.4"
+ "packageManager": "pnpm@8.15.7"
}
diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml
index bb6044cbf08f1..b64b23b584ced 100644
--- a/.github/actions/setup-rust/action.yml
+++ b/.github/actions/setup-rust/action.yml
@@ -4,61 +4,17 @@ inputs:
targets:
description: 'Comma-separated list of target triples to install for this toolchain'
required: false
- components:
- description: 'Comma-separated list of components to be additionally installed'
- required: false
- skip-install:
- description: 'Sets environment variables without installing the rust toolchain'
- required: false
runs:
using: 'composite'
steps:
- - name: 'Get toolchain version from file'
- id: file
- shell: bash
- run: echo "toolchain=$(cat ./rust-toolchain)" >> $GITHUB_OUTPUT
-
- - shell: bash
- run: |
- : force toolchain version
- echo "RUST_TOOLCHAIN=${{ steps.file.outputs.toolchain }}" >> $GITHUB_ENV
-
- - shell: bash
- run: |
- : disable incremental compilation
- if [ -z "${CARGO_INCREMENTAL+set}" ]; then
- echo CARGO_INCREMENTAL=0 >> $GITHUB_ENV
- fi
-
- - shell: bash
- run: |
- : enable colors in Cargo output
- if [ -z "${CARGO_TERM_COLOR+set}" ]; then
- echo CARGO_TERM_COLOR=always >> $GITHUB_ENV
- fi
-
- - shell: bash
- run: |
- : enable rust backtrace
- if [ -z "${RUST_BACKTRACE+set}" ]; then
- echo RUST_BACKTRACE=short >> $GITHUB_ENV
- fi
-
- - shell: bash
- run: |
- : enable faster cargo sparse registry
- if [ -z "${CARGO_REGISTRIES_CRATES_IO_PROTOCOL+set}" ]; then
- echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse >> $GITHUB_ENV
- fi
-
- name: 'Setup Rust toolchain'
- uses: dtolnay/rust-toolchain@master
- if: ${{ !inputs.skip-install }}
+ uses: actions-rust-lang/setup-rust-toolchain@v1
with:
- toolchain: ${{ steps.file.outputs.toolchain }}
- targets: ${{ inputs.targets }}
- components: ${{ inputs.components }}
+ target: ${{ inputs.targets }}
+ # needed to not make it override the defaults
+ rustflags: ''
+ cache: false
- name: 'Add cargo problem matchers'
shell: bash
diff --git a/.github/actions/upload-turboyet-data/package.json b/.github/actions/upload-turboyet-data/package.json
index d99a219464652..ef65a1117b81a 100644
--- a/.github/actions/upload-turboyet-data/package.json
+++ b/.github/actions/upload-turboyet-data/package.json
@@ -13,7 +13,7 @@
},
"engines": {
"node": ">=18.17.0",
- "pnpm": "8.15.4"
+ "pnpm": "8.15.7"
},
- "packageManager": "pnpm@8.15.4"
+ "packageManager": "pnpm@8.15.7"
}
diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml
index 82ac9931614af..85399c929ffef 100644
--- a/.github/workflows/build_and_deploy.yml
+++ b/.github/workflows/build_and_deploy.yml
@@ -156,8 +156,7 @@ jobs:
set -e &&
apt update &&
apt install -y pkg-config xz-utils dav1d libdav1d-dev &&
- rustup toolchain install "${RUST_TOOLCHAIN}" &&
- rustup default "${RUST_TOOLCHAIN}" &&
+ rustup show &&
rustup target add x86_64-unknown-linux-gnu &&
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" &&
unset CC_x86_64_unknown_linux_gnu && unset CC &&
@@ -177,8 +176,7 @@ jobs:
set -e &&
apk update &&
apk add --no-cache libc6-compat pkgconfig dav1d libdav1d dav1d-dev &&
- rustup toolchain install "${RUST_TOOLCHAIN}" &&
- rustup default "${RUST_TOOLCHAIN}" &&
+ rustup show &&
rustup target add x86_64-unknown-linux-musl &&
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" &&
cd packages/next-swc && npm run build-native-release -- --target x86_64-unknown-linux-musl &&
@@ -197,8 +195,7 @@ jobs:
apt update &&
apt install -y pkg-config xz-utils dav1d libdav1d-dev &&
export JEMALLOC_SYS_WITH_LG_PAGE=16 &&
- rustup toolchain install "${RUST_TOOLCHAIN}" &&
- rustup default "${RUST_TOOLCHAIN}" &&
+ rustup show &&
rustup target add aarch64-unknown-linux-gnu &&
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" &&
export CC_aarch64_unknown_linux_gnu=/usr/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc &&
@@ -220,8 +217,7 @@ jobs:
apk add --no-cache libc6-compat pkgconfig dav1d libdav1d dav1d-dev &&
export JEMALLOC_SYS_WITH_LG_PAGE=16 &&
npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" &&
- rustup toolchain install "${RUST_TOOLCHAIN}" &&
- rustup default "${RUST_TOOLCHAIN}" &&
+ rustup show &&
rustup target add aarch64-unknown-linux-musl &&
cd packages/next-swc && npm run build-native-release -- --target aarch64-unknown-linux-musl &&
llvm-strip -x native/next-swc.*.node
@@ -255,11 +251,11 @@ jobs:
check-latest: true
- run: corepack enable
+ # we always want to run this to set environment variables
- name: Install Rust
uses: ./.github/actions/setup-rust
with:
targets: ${{ matrix.settings.target }}
- skip-install: ${{ matrix.settings.docker }}
- name: normalize versions
run: node scripts/normalize-version-bump.js
@@ -269,12 +265,15 @@ jobs:
if: ${{ matrix.settings.setup }}
- name: Cache on ${{ github.ref_name }}
- uses: ijjk/rust-cache@turbo-cache-v1.0.7
+ uses: ijjk/rust-cache@turbo-cache-v1.0.8
with:
save-if: 'true'
cache-provider: 'turbo'
shared-key: build-${{ matrix.settings.target }}-${{ hashFiles('.cargo/config.toml') }}
+ - name: Clear native build
+ run: rm -rf packages/next-swc/native
+
# we only need custom caching for docker builds
# as they are on an older Node.js version and have
# issues with turbo caching
@@ -289,7 +288,7 @@ jobs:
- name: Build in docker
if: ${{ matrix.settings.docker && steps.build-exists.outputs.BUILD_EXISTS == 'no' }}
- run: docker run -v "/var/run/docker.sock":"/var/run/docker.sock" -e RUST_TOOLCHAIN -e RUST_BACKTRACE -e NAPI_CLI_VERSION -e CARGO_TERM_COLOR -e CARGO_INCREMENTAL -e CARGO_PROFILE_RELEASE_LTO -e CARGO_REGISTRIES_CRATES_IO_PROTOCOL -e TURBO_API -e TURBO_TEAM -e TURBO_TOKEN -e TURBO_VERSION -e TURBO_REMOTE_ONLY -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build --entrypoint=bash ${{ matrix.settings.docker }} -c "${{ matrix.settings.build }}"
+ run: docker run -v "/var/run/docker.sock":"/var/run/docker.sock" -e RUST_BACKTRACE -e NAPI_CLI_VERSION -e CARGO_TERM_COLOR -e CARGO_INCREMENTAL -e CARGO_PROFILE_RELEASE_LTO -e CARGO_REGISTRIES_CRATES_IO_PROTOCOL -e TURBO_API -e TURBO_TEAM -e TURBO_TOKEN -e TURBO_VERSION -e TURBO_REMOTE_ONLY -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build --entrypoint=bash ${{ matrix.settings.docker }} -c "${{ matrix.settings.build }}"
- name: cache build
if: ${{ matrix.settings.docker && steps.build-exists.outputs.BUILD_EXISTS == 'no' }}
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index e0caa55ebc2e8..b7a44dd852a13 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -154,7 +154,7 @@ jobs:
test-turbopack-dev:
name: test turbopack dev
- needs: ['changes', 'build-next']
+ needs: ['changes', 'build-next', 'build-native']
if: ${{ needs.changes.outputs.docs-only == 'false' }}
strategy:
@@ -169,7 +169,7 @@ jobs:
test-turbopack-integration:
name: test turbopack development integration
- needs: ['changes', 'build-next']
+ needs: ['changes', 'build-next', 'build-native']
if: ${{ needs.changes.outputs.docs-only == 'false' }}
strategy:
@@ -185,7 +185,7 @@ jobs:
test-turbopack-production:
name: test turbopack production
- needs: ['changes', 'build-next']
+ needs: ['changes', 'build-next', 'build-native']
if: ${{ needs.changes.outputs.docs-only == 'false' }}
strategy:
@@ -201,7 +201,7 @@ jobs:
test-turbopack-production-integration:
name: test turbopack production integration
- needs: ['changes', 'build-next']
+ needs: ['changes', 'build-next', 'build-native']
if: ${{ needs.changes.outputs.docs-only == 'false' }}
strategy:
@@ -461,6 +461,8 @@ jobs:
'test-turbopack-integration',
'test-new-tests-dev',
'test-new-tests-start',
+ 'test-turbopack-production',
+ 'test-turbopack-production-integration',
]
if: always()
diff --git a/.github/workflows/build_reusable.yml b/.github/workflows/build_reusable.yml
index 0d48f5403af8a..43f7c1c419190 100644
--- a/.github/workflows/build_reusable.yml
+++ b/.github/workflows/build_reusable.yml
@@ -105,8 +105,6 @@ jobs:
- name: Install Rust
uses: ./.github/actions/setup-rust
if: ${{ inputs.skipNativeBuild != 'yes' || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}
- with:
- components: rustfmt, clippy
- name: 'Install mold linker'
if: ${{ inputs.mold == 'yes' }}
@@ -125,7 +123,7 @@ jobs:
- run: corepack prepare --activate yarn@1.22.19 && npm i -g "turbo@${TURBO_VERSION}" "@napi-rs/cli@${NAPI_CLI_VERSION}"
- name: Cache on ${{ github.ref_name }}
- uses: ijjk/rust-cache@turbo-cache-v1.0.7
+ uses: ijjk/rust-cache@turbo-cache-v1.0.8
if: ${{ inputs.rustCacheKey }}
with:
cache-provider: 'turbo'
diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml
index 0a958c47efa97..b990ee37f02cc 100644
--- a/.github/workflows/triage.yml
+++ b/.github/workflows/triage.yml
@@ -35,3 +35,4 @@ jobs:
reproduction-link-section: '### Link to the code that reproduces this issue(.*)### To Reproduce'
reproduction-invalid-label: 'invalid link'
reproduction-issue-labels: 'template: bug,'
+ comment-unhelpful-weight: 0.5
diff --git a/Cargo.lock b/Cargo.lock
index bc4ed22446014..f1e0ab00bc2e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -321,7 +321,7 @@ dependencies = [
[[package]]
name = "auto-hash-map"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"serde",
"smallvec",
@@ -384,51 +384,6 @@ dependencies = [
"arrayvec",
]
-[[package]]
-name = "axum"
-version = "0.6.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349f8ccfd9221ee7d1f3d4b33e1f8319b3a81ed8f61f2ea40b37b859794b4491"
-dependencies = [
- "async-trait",
- "axum-core",
- "bitflags 1.3.2",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "hyper",
- "itoa",
- "matchit",
- "memchr",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "rustversion",
- "serde",
- "sync_wrapper",
- "tower",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "axum-core"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "mime",
- "rustversion",
- "tower-layer",
- "tower-service",
-]
-
[[package]]
name = "backtrace"
version = "0.3.68"
@@ -838,42 +793,6 @@ dependencies = [
"windows-sys 0.45.0",
]
-[[package]]
-name = "console-api"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86"
-dependencies = [
- "prost",
- "prost-types",
- "tonic",
- "tracing-core",
-]
-
-[[package]]
-name = "console-subscriber"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be"
-dependencies = [
- "console-api",
- "crossbeam-channel",
- "crossbeam-utils",
- "futures",
- "hdrhistogram",
- "humantime",
- "prost-types",
- "serde",
- "serde_json",
- "thread_local",
- "tokio",
- "tokio-stream",
- "tonic",
- "tracing",
- "tracing-core",
- "tracing-subscriber",
-]
-
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
@@ -1737,9 +1656,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
@@ -1752,9 +1671,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
"futures-sink",
@@ -1768,9 +1687,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
@@ -1779,15 +1698,15 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
@@ -1807,21 +1726,21 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
-version = "0.3.28"
+version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
@@ -2003,19 +1922,6 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
-[[package]]
-name = "hdrhistogram"
-version = "7.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8"
-dependencies = [
- "base64 0.13.1",
- "byteorder",
- "flate2",
- "nom",
- "num-traits",
-]
-
[[package]]
name = "heapless"
version = "0.7.16"
@@ -2114,12 +2020,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
-[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
[[package]]
name = "hyper"
version = "0.14.28"
@@ -2157,18 +2057,6 @@ dependencies = [
"tokio-rustls",
]
-[[package]]
-name = "hyper-timeout"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
-dependencies = [
- "hyper",
- "pin-project-lite",
- "tokio",
- "tokio-io-timeout",
-]
-
[[package]]
name = "hyper-tls"
version = "0.5.0"
@@ -2801,12 +2689,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
-[[package]]
-name = "matchit"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
-
[[package]]
name = "maybe-rayon"
version = "0.1.1"
@@ -3104,15 +2986,11 @@ dependencies = [
"anyhow",
"futures",
"indexmap 1.9.3",
- "indoc",
"next-core",
- "once_cell",
"serde",
"serde_json",
"shadow-rs",
- "tokio",
"tracing",
- "tracing-subscriber",
"turbo-tasks",
"turbopack-binding",
]
@@ -3121,9 +2999,7 @@ dependencies = [
name = "next-build"
version = "0.1.0"
dependencies = [
- "console-subscriber",
"next-core",
- "turbo-tasks",
"turbopack-binding",
"vergen 7.5.1",
]
@@ -3218,7 +3094,7 @@ dependencies = [
[[package]]
name = "node-file-trace"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"serde",
@@ -3934,38 +3810,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "prost"
-version = "0.11.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537"
-dependencies = [
- "bytes",
- "prost-derive",
-]
-
-[[package]]
-name = "prost-derive"
-version = "0.11.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b"
-dependencies = [
- "anyhow",
- "itertools 0.10.5",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "prost-types"
-version = "0.11.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88"
-dependencies = [
- "prost",
-]
-
[[package]]
name = "psm"
version = "0.1.21"
@@ -6566,12 +6410,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "sync_wrapper"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
-
[[package]]
name = "sys-info"
version = "0.9.1"
@@ -6825,20 +6663,9 @@ dependencies = [
"signal-hook-registry",
"socket2 0.5.6",
"tokio-macros",
- "tracing",
"windows-sys 0.48.0",
]
-[[package]]
-name = "tokio-io-timeout"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
-dependencies = [
- "pin-project-lite",
- "tokio",
-]
-
[[package]]
name = "tokio-macros"
version = "2.1.0"
@@ -6976,64 +6803,6 @@ dependencies = [
"winnow",
]
-[[package]]
-name = "tonic"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum",
- "base64 0.13.1",
- "bytes",
- "futures-core",
- "futures-util",
- "h2",
- "http",
- "http-body",
- "hyper",
- "hyper-timeout",
- "percent-encoding",
- "pin-project",
- "prost",
- "prost-derive",
- "tokio",
- "tokio-stream",
- "tokio-util",
- "tower",
- "tower-layer",
- "tower-service",
- "tracing",
- "tracing-futures",
-]
-
-[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "indexmap 1.9.3",
- "pin-project",
- "pin-project-lite",
- "rand",
- "slab",
- "tokio",
- "tokio-util",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "tower-layer"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
-
[[package]]
name = "tower-service"
version = "0.3.2"
@@ -7084,16 +6853,6 @@ dependencies = [
"valuable",
]
-[[package]]
-name = "tracing-futures"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
-dependencies = [
- "pin-project",
- "tracing",
-]
-
[[package]]
name = "tracing-log"
version = "0.2.0"
@@ -7105,16 +6864,6 @@ dependencies = [
"tracing-core",
]
-[[package]]
-name = "tracing-serde"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
-dependencies = [
- "serde",
- "tracing-core",
-]
-
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
@@ -7125,15 +6874,12 @@ dependencies = [
"nu-ansi-term",
"once_cell",
"regex",
- "serde",
- "serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
- "tracing-serde",
]
[[package]]
@@ -7174,7 +6920,7 @@ dependencies = [
[[package]]
name = "turbo-tasks"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-trait",
@@ -7205,7 +6951,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-build"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"cargo-lock",
@@ -7217,7 +6963,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-bytes"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"bytes",
@@ -7231,7 +6977,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-env"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"dotenvs",
@@ -7245,7 +6991,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fetch"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"lazy_static",
@@ -7261,7 +7007,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fs"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"auto-hash-map",
@@ -7293,7 +7039,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-hash"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"base16",
"hex",
@@ -7305,7 +7051,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"proc-macro-error",
@@ -7318,7 +7064,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros-shared"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"proc-macro2",
"quote",
@@ -7328,7 +7074,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-malloc"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"mimalloc",
]
@@ -7336,7 +7082,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-memory"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"auto-hash-map",
@@ -7361,7 +7107,7 @@ dependencies = [
[[package]]
name = "turbopack"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-recursion",
@@ -7391,7 +7137,7 @@ dependencies = [
[[package]]
name = "turbopack-binding"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"auto-hash-map",
"mdxjs",
@@ -7431,7 +7177,7 @@ dependencies = [
[[package]]
name = "turbopack-browser"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -7454,7 +7200,7 @@ dependencies = [
[[package]]
name = "turbopack-cli-utils"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"clap",
@@ -7471,7 +7217,7 @@ dependencies = [
[[package]]
name = "turbopack-core"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-recursion",
@@ -7500,7 +7246,7 @@ dependencies = [
[[package]]
name = "turbopack-css"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -7527,7 +7273,7 @@ dependencies = [
[[package]]
name = "turbopack-dev-server"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-compression",
@@ -7563,7 +7309,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-trait",
@@ -7598,7 +7344,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-hmr-protocol"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"serde",
"serde_json",
@@ -7609,7 +7355,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-plugins"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-trait",
@@ -7633,7 +7379,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-runtime"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indoc",
@@ -7649,7 +7395,7 @@ dependencies = [
[[package]]
name = "turbopack-env"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -7665,7 +7411,7 @@ dependencies = [
[[package]]
name = "turbopack-image"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"base64 0.21.4",
@@ -7684,7 +7430,7 @@ dependencies = [
[[package]]
name = "turbopack-json"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"serde",
@@ -7699,7 +7445,7 @@ dependencies = [
[[package]]
name = "turbopack-mdx"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"mdxjs",
@@ -7714,7 +7460,7 @@ dependencies = [
[[package]]
name = "turbopack-node"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"async-stream",
@@ -7748,7 +7494,7 @@ dependencies = [
[[package]]
name = "turbopack-nodejs"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -7768,7 +7514,7 @@ dependencies = [
[[package]]
name = "turbopack-resolve"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -7786,7 +7532,7 @@ dependencies = [
[[package]]
name = "turbopack-static"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"serde",
@@ -7802,7 +7548,7 @@ dependencies = [
[[package]]
name = "turbopack-swc-utils"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"swc_core",
"turbo-tasks",
@@ -7813,7 +7559,7 @@ dependencies = [
[[package]]
name = "turbopack-trace-utils"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"crossbeam-channel",
@@ -7829,7 +7575,7 @@ dependencies = [
[[package]]
name = "turbopack-wasm"
version = "0.1.0"
-source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240410.2#0265f1fdc5761f0d53e58f6aa6ffa36203283b22"
+source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240416.1#c8fd56653ae3be19a158319e931422f1203d6cfa"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@@ -8304,6 +8050,7 @@ dependencies = [
"serde-wasm-bindgen",
"serde_json",
"swc_core",
+ "tracing",
"turbopack-binding",
"wasm-bindgen",
"wasm-bindgen-futures",
diff --git a/Cargo.toml b/Cargo.toml
index 89bac628aac9c..ffabdcc19f398 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,11 +37,11 @@ swc_core = { version = "0.90.30", features = [
testing = { version = "0.35.22" }
# Turbo crates
-turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240410.2" }
+turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240416.1" }
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
-turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240410.2" }
+turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240416.1" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
-turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240410.2" }
+turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240416.1" }
# General Deps
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index a4a63e68955c0..14c69e0bbc3ab 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -69,8 +69,9 @@ stages:
- script: npx playwright@1.35.1 install chromium
condition: eq(variables['isDocsOnly'], 'No')
+ # Test critical app router and CNA tests to cover basic usage cases with windows
- script: |
- node run-tests.js -c 1 test/production/pages-dir/production/test/index.test.ts test/integration/css-client-nav/test/index.test.js test/integration/rewrites-has-condition/test/index.test.js
+ node run-tests.js -c 1 test/production/pages-dir/production/test/index.test.ts test/integration/css-client-nav/test/index.test.js test/integration/rewrites-has-condition/test/index.test.js test/integration/create-next-app/examples.test.ts test/integration/create-next-app/index.test.ts test/integration/create-next-app/package-manager/pnpm.test.ts
condition: eq(variables['isDocsOnly'], 'No')
displayName: 'Run tests'
diff --git a/docs/02-app/01-building-your-application/01-routing/12-route-handlers.mdx b/docs/02-app/01-building-your-application/01-routing/12-route-handlers.mdx
index 122a4c620e8de..5e5d19eeeff97 100644
--- a/docs/02-app/01-building-your-application/01-routing/12-route-handlers.mdx
+++ b/docs/02-app/01-building-your-application/01-routing/12-route-handlers.mdx
@@ -406,8 +406,6 @@ const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
-export const runtime = 'edge'
-
export async function POST(req: Request) {
const { messages } = await req.json()
const response = await openai.chat.completions.create({
@@ -430,8 +428,6 @@ const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
-export const runtime = 'edge'
-
export async function POST(req) {
const { messages } = await req.json()
const response = await openai.chat.completions.create({
@@ -649,16 +645,6 @@ export async function POST(request) {
Notably, unlike API Routes with the Pages Router, you do not need to use `bodyParser` to use any additional configuration.
-### Edge and Node.js Runtimes
-
-Route Handlers have an isomorphic Web API to support both Edge and Node.js runtimes seamlessly, including support for streaming. Since Route Handlers use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) as Pages and Layouts, they support long-awaited features like general-purpose [statically regenerated](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data) Route Handlers.
-
-You can use the `runtime` segment config option to specify the runtime:
-
-```ts
-export const runtime = 'edge' // 'nodejs' is the default
-```
-
### Non-UI Responses
You can use Route Handlers to return non-UI content. Note that [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts), [`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file), [`app icons`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx), and [open graph images](/docs/app/api-reference/file-conventions/metadata/opengraph-image) all have built-in support.
diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx
index f62589ed66423..eeeef2f3620d0 100644
--- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx
+++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx
@@ -917,6 +917,7 @@ export function addItem() {
Defining a Server Action inside a component creates a [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) where the action has access to the outer function's scope. For example, the `publish` action has access to the `publishVersion` variable:
```tsx filename="app/page.tsx" switcher
+'use client'
export default function Page() {
const publishVersion = await getLatestVersion();
@@ -928,11 +929,12 @@ export default function Page() {
...
}
- return ;
+ return ;
}
```
```jsx filename="app/page.js" switcher
+'use client'
export default function Page() {
const publishVersion = await getLatestVersion();
@@ -944,7 +946,7 @@ export default function Page() {
...
}
- return ;
+ return ;
}
```
diff --git a/docs/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx b/docs/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx
index ededec85c10a7..7b858831e83df 100644
--- a/docs/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx
+++ b/docs/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx
@@ -1,84 +1,26 @@
---
-title: Edge and Node.js Runtimes
+title: Runtimes
description: Learn about the switchable runtimes (Edge and Node.js) in Next.js.
+related:
+ description: View the Edge Runtime API reference.
+ links:
+ - app/api-reference/edge
---
{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
-In the context of Next.js, runtime refers to the set of libraries, APIs, and general functionality available to your code during execution.
+Next.js has two server runtimes you can use in your application:
-On the server, there are two runtimes where parts of your application code can be rendered:
+- The **Node.js Runtime** (default) which has access to all Node.js APIs and compatible packages from the ecosystem.
+- The **Edge Runtime** which contains a more limited [set of APIs](/docs/app/api-reference/edge).
-- The **Node.js Runtime** (default) has access to all Node.js APIs and compatible packages from the ecosystem.
-- The **Edge Runtime** is based on [Web APIs](/docs/app/api-reference/edge).
+## Use Cases
-## Runtime Differences
+- The Node.js runtime is used for rendering your application.
+- The Edge runtime is used for Middleware (routing rules like redirects, rewrites, and setting headers).
-There are many considerations to make when choosing a runtime. This table shows the major differences at a glance. If you want a more in-depth analysis of the differences, check out the sections below.
+## Caveats
-| | Node | Serverless | Edge |
-| ------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ---------------- |
-| Cold Boot | / | Normal | Low |
-| [HTTP Streaming](/docs/app/building-your-application/routing/loading-ui-and-streaming) | Yes | Yes | Yes |
-| IO | All | All | `fetch` |
-| Scalability | / | High | Highest |
-| Security | Normal | High | High |
-| Latency | Normal | Low | Lowest |
-| npm Packages | All | All | A smaller subset |
-| [Static Rendering](/docs/app/building-your-application/rendering/server-components#static-rendering-default) | Yes | Yes | No |
-| [Dynamic Rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering) | Yes | Yes | Yes |
-| [Data Revalidation w/ `fetch`](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data) | Yes | Yes | Yes |
-
-### Edge Runtime
-
-In Next.js, the lightweight Edge Runtime is a subset of available Node.js APIs.
-
-The Edge Runtime is ideal if you need to deliver dynamic, personalized content at low latency with small, simple functions. The Edge Runtime's speed comes from its minimal use of resources, but that can be limiting in many scenarios.
-
-For example, code executed in the Edge Runtime [on Vercel cannot exceed between 1 MB and 4 MB](https://vercel.com/docs/concepts/limits/overview#edge-middleware-and-edge-functions-size), this limit includes imported packages, fonts and files, and will vary depending on your deployment infrastructure. In addition, the Edge Runtime does not support all Node.js APIs meaning some `npm` packages may not work. For example, "Module not found: Can't resolve 'fs'" or similar errors. We recommend using the Node.js runtime if you need to use these APIs or packages.
-
-### Node.js Runtime
-
-Using the Node.js runtime gives you access to all Node.js APIs, and all npm packages that rely on them. However, it's not as fast to start up as routes using the Edge runtime.
-
-Deploying your Next.js application to a Node.js server will require managing, scaling, and configuring your infrastructure. Alternatively, you can consider deploying your Next.js application to a serverless platform like Vercel, which will handle this for you.
-
-### Serverless Node.js
-
-Serverless is ideal if you need a scalable solution that can handle more complex computational loads than the Edge Runtime. With Serverless Functions on Vercel, for example, your overall code size is [50MB](https://vercel.com/docs/concepts/limits/overview#serverless-function-size) including imported packages, fonts, and files.
-
-The downside compared to routes using the [Edge](https://vercel.com/docs/concepts/functions/edge-functions) is that it can take hundreds of milliseconds for Serverless Functions to boot up before they begin processing requests. Depending on the amount of traffic your site receives, this could be a frequent occurrence as the functions are not frequently "warm".
-
-
-
-## Examples
-
-### Segment Runtime Option
-
-You can specify a runtime for individual route segments in your Next.js application. To do so, [declare a variable called `runtime` and export it](/docs/app/api-reference/file-conventions/route-segment-config). The variable must be a string, and must have a value of either `'nodejs'` or `'edge'` runtime.
-
-The following example demonstrates a page route segment that exports a `runtime` with a value of `'edge'`:
-
-```tsx filename="app/page.tsx" switcher
-export const runtime = 'edge' // 'nodejs' (default) | 'edge'
-```
-
-```jsx filename="app/page.js" switcher
-export const runtime = 'edge' // 'nodejs' (default) | 'edge'
-```
-
-You can also define `runtime` on a layout level, which will make all routes under the layout run on the edge runtime:
-
-```tsx filename="app/layout.tsx" switcher
-export const runtime = 'edge' // 'nodejs' (default) | 'edge'
-```
-
-```jsx filename="app/layout.js" switcher
-export const runtime = 'edge' // 'nodejs' (default) | 'edge'
-```
-
-If the segment runtime is _not_ set, the default `nodejs` runtime will be used. You do not need to use the `runtime` option if you do not plan to change from the Node.js runtime.
-
-
-
-> Please refer to the [Node.js Docs](https://nodejs.org/docs/latest/api/) and [Edge Docs](/docs/app/api-reference/edge) for the full list of available APIs. Both runtimes can also support [streaming](/docs/app/building-your-application/routing/loading-ui-and-streaming) depending on your deployment infrastructure.
+- The Edge runtime does not support all Node.js APIs. Some packages will not work. Learn more about the unsupported APIs in the [Edge Runtime](/docs/app/api-reference/edge#unsupported-apis).
+- The Edge runtime does not support Incremental Static Regeneration (ISR).
+- Both runtimes can support [streaming](/docs/app/building-your-application/routing/loading-ui-and-streaming) depending on your deployment infrastructure.
diff --git a/docs/02-app/01-building-your-application/05-styling/01-css-modules.mdx b/docs/02-app/01-building-your-application/05-styling/01-css-modules.mdx
index ddcdb6c0af6b9..07493272c3770 100644
--- a/docs/02-app/01-building-your-application/05-styling/01-css-modules.mdx
+++ b/docs/02-app/01-building-your-application/05-styling/01-css-modules.mdx
@@ -1,6 +1,7 @@
---
-title: CSS Modules
-description: Style your Next.js Application with CSS Modules.
+title: CSS Modules and Global Styles
+nav_title: CSS Modules
+description: Style your Next.js Application with CSS Modules, Global Styles, and external stylesheets.
---
{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
@@ -16,6 +17,14 @@ description: Style your Next.js Application with CSS Modules.
+Next.js supports different types of stylesheets, including:
+
+- [CSS Modules](#css-modules)
+- [Global Styles](#global-styles)
+- [External Stylesheets](#external-stylesheets)
+
+## CSS Modules
+
Next.js has built-in support for CSS Modules using the `.module.css` extension.
CSS Modules locally scope CSS by automatically creating a unique class name. This allows you to use the same class name in different files without worrying about collisions. This behavior makes CSS Modules the ideal way to include component-level CSS.
@@ -91,8 +100,7 @@ export function Button() {
-CSS Modules are an _optional feature_ and are **only enabled for files with the `.module.css` extension**.
-Regular `` stylesheets and global CSS files are still supported.
+CSS Modules are **only enabled for files with the `.module.css` and `.module.sass` extensions**.
In production, all CSS Module files will be automatically concatenated into **many minified and code-split** `.css` files.
These `.css` files represent hot execution paths in your application, ensuring the minimal amount of CSS is loaded for your application to paint.
@@ -288,6 +296,61 @@ function ExampleDialog(props) {
+
+
+## Ordering and Merging
+
+Next.js optimizes CSS during production builds by automatically chunking (merging) stylesheets. The CSS order is determined by the order in which you import the stylesheets into your application code.
+
+For example, `base-button.module.css` will be ordered before `page.module.css` since `` is imported first in ``:
+
+```tsx filename="base-button.tsx" switcher
+import styles from './base-button.module.css'
+
+export function BaseButton() {
+ return
+}
+```
+
+```jsx filename="base-button.js" switcher
+import styles from './base-button.module.css'
+
+export function BaseButton() {
+ return
+}
+```
+
+```tsx filename="page.ts" switcher
+import { BaseButton } from './base-button'
+import styles from './page.module.css'
+
+export function Page() {
+ return
+}
+```
+
+```jsx filename="page.js" switcher
+import { BaseButton } from './base-button'
+import styles from './page.module.css'
+
+export function Page() {
+ return
+}
+```
+
+To maintain a predictable order, we recommend the following:
+
+- Only import a CSS file in a single JS/TS file.
+ - If using global class names, import the global styles in the same file in the order you want them to be applied.
+- Prefer CSS Modules over global styles.
+ - Use a consistent naming convention for your CSS modules. For example, using `.module.css` over `.tsx`.
+- Extract shared styles into a separate shared component.
+- If using [Tailwind](/docs/app/building-your-application/styling/tailwind-css), import the stylesheet at the top of the file, preferably in the [Root Layout](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required).
+
+> **Good to know:** CSS ordering behaves differently in development mode, always ensure to check preview deployments to verify the final CSS order in your production build.
+
+
+
## Additional Features
Next.js includes additional features to improve the authoring experience of adding styles:
diff --git a/docs/02-app/01-building-your-application/06-optimizing/04-metadata.mdx b/docs/02-app/01-building-your-application/06-optimizing/04-metadata.mdx
index e6bb31efd2e4d..4648804555c4c 100644
--- a/docs/02-app/01-building-your-application/06-optimizing/04-metadata.mdx
+++ b/docs/02-app/01-building-your-application/06-optimizing/04-metadata.mdx
@@ -247,15 +247,11 @@ export const metadata = {
The `ImageResponse` constructor allows you to generate dynamic images using JSX and CSS. This is useful for creating social media images such as Open Graph images, Twitter cards, and more.
-`ImageResponse` uses the [Edge Runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#edge-runtime), and Next.js automatically adds the correct headers to cached images at the edge, helping improve performance and reducing recomputation.
-
To use it, you can import `ImageResponse` from `next/og`:
```jsx filename="app/about/route.js"
import { ImageResponse } from 'next/og'
-export const runtime = 'edge'
-
export async function GET() {
return new ImageResponse(
(
diff --git a/docs/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx b/docs/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx
index 901ea85aeab72..a6e3257743454 100644
--- a/docs/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx
+++ b/docs/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx
@@ -70,7 +70,7 @@ export async function register() {
### Importing runtime-specific code
-Next.js calls `register` in all environments, so it's important to conditionally import any code that doesn't support specific runtimes (e.g. [Edge](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#edge-runtime) or [Node.js](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#nodejs-runtime)). You can use the `NEXT_RUNTIME` environment variable to get the current environment:
+Next.js calls `register` in all environments, so it's important to conditionally import any code that doesn't support specific runtimes (e.g. [Edge or Node.js](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes)). You can use the `NEXT_RUNTIME` environment variable to get the current environment:
```ts filename="instrumentation.ts" switcher
export async function register() {
diff --git a/docs/02-app/01-building-your-application/06-optimizing/13-memory-usage.mdx b/docs/02-app/01-building-your-application/06-optimizing/13-memory-usage.mdx
index 5c793fbce12cd..263d99531f6d5 100644
--- a/docs/02-app/01-building-your-application/06-optimizing/13-memory-usage.mdx
+++ b/docs/02-app/01-building-your-application/06-optimizing/13-memory-usage.mdx
@@ -13,6 +13,12 @@ Applications with a large amount of dependencies will use more memory.
The [Bundle Analyzer](/docs/app/building-your-application/optimizing/bundle-analyzer) can help you investigate large dependencies in your application that may be able to be removed to improve performance and memory usage.
+## Run `next build` with `--experimental-debug-memory-usage`
+
+Starting in `14.2.0`, you can run `next build --experimental-debug-memory-usage` to run the build in a mode where Next.js will print out information about memory usage continuously throughout the build, such as heap usage and garbage collection statistics. Heap snapshots will also be taken automatically when memory usage gets close to the configured limit.
+
+> **Good to know**: This feature is not compatible with the Webpack build worker option which is auto-enabled unless you have custom webpack config.
+
## Record a heap profile
To look for memory issues, you can record a heap profile from Node.js and load it in Chrome DevTools to identify potential sources of memory leaks.
@@ -32,9 +38,13 @@ In Chrome DevTools, you can open the Memory tab and click on the "Load Profile"
You can use an inspector tool to analyze the memory usage of the application.
When running the `next build` or `next dev` command, add `NODE_OPTIONS=--inspect` to the beginning of the command. This will expose the inspector agent on the default port.
-If you wish to break before any user code starts, you can pass `--inspect-brk` instead.
+If you wish to break before any user code starts, you can pass `--inspect-brk` instead. While the process is running, you can use a tool such as Chrome DevTools to connect to the debugging port to record and analyze a snapshot of the heap to see what memory is being retained.
+
+Starting in `14.2.0`, you can also run `next build` with the `--experimental-debug-memory-usage` flag to make it easier to take heap snapshots.
+
+While running in this mode, you can send a `SIGUSR2` signal to the process at any point, and the process will take a heap snapshot.
-Now, you can use a tool such as Chrome DevTools to connect to the debugging port to record and analyze a snapshot of the heap to see what memory is being retained.
+The heap snapshot will be saved to the project root of the Next.js application and can be loaded in any heap analyzer, such as Chrome DevTools, to see what memory is retained. This mode is not yet compatible with Webpack build workers.
See [how to record and analyze heap snapshots](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots) for more information.
@@ -62,11 +72,11 @@ const nextConfig = {
config,
{ buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }
) => {
- if (cfg.cache && !dev) {
- cfg.cache = Object.freeze({
+ if (config.cache && !dev) {
+ config.cache = Object.freeze({
type: 'memory',
})
- cfg.cache.maxMemoryGenerations = 0
+ config.cache.maxMemoryGenerations = 0
}
// Important: return the modified config
return config
diff --git a/docs/02-app/01-building-your-application/07-configuring/05-mdx.mdx b/docs/02-app/01-building-your-application/07-configuring/05-mdx.mdx
index fb7338f44f5c5..64b238ee34689 100644
--- a/docs/02-app/01-building-your-application/07-configuring/05-mdx.mdx
+++ b/docs/02-app/01-building-your-application/07-configuring/05-mdx.mdx
@@ -24,25 +24,45 @@ Output:
Next.js can support both local MDX content inside your application, as well as remote MDX files fetched dynamically on the server. The Next.js plugin handles transforming markdown and React components into HTML, including support for usage in Server Components (the default in App Router).
-## `@next/mdx`
+> **Good to know**: View the [Portfolio Starter Kit](https://vercel.com/templates/next.js/portfolio-starter-kit) template for a complete working example.
-The `@next/mdx` package is used to configure Next.js so it can process markdown and MDX. **It sources data from local files**, allowing you to create pages with a `.mdx` extension, directly in your `/pages` or `/app` directory.
+## Install dependencies
-Let's walk through how to configure and use MDX with Next.js.
+The `@next/mdx` package, and related packages, are used to configure Next.js so it can process markdown and MDX. **It sources data from local files**, allowing you to create pages with a `.md` or `.mdx` extension, directly in your `/pages` or `/app` directory.
-## Getting Started
-
-Install packages needed to render MDX:
+Install these packages to render MDX with Next.js:
```bash filename="Terminal"
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
```
-
+## Configure `next.config.mjs`
+
+Update the `next.config.mjs` file at your project's root to configure it to use MDX:
+
+```js filename="next.config.mjs" switcher
+import createMDX from '@next/mdx'
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ // Configure `pageExtensions` to include markdown and MDX files
+ pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
+ // Optionally, add any other Next.js config below
+}
+
+const withMDX = createMDX({
+ // Add markdown plugins here, as desired
+})
+
+// Merge MDX config with Next.js config
+export default withMDX(nextConfig)
+```
-Create a `mdx-components.tsx` file at the root of your application (`src/` or the parent folder of `app/`):
+This allows `.md` and `.mdx` files to act as pages, routes, or imports in your application.
-> **Good to know**: `mdx-components.tsx` is required to use MDX with App Router and will not work without it.
+## Add a `mdx-components.tsx` file
+
+Create a `mdx-components.tsx` (or `.js`) file in the root of your project to define global MDX Components. For example, at the same level as `pages` or `app`, or inside `src` if applicable.
```tsx filename="mdx-components.tsx" switcher
import type { MDXComponents } from 'mdx/types'
@@ -62,32 +82,87 @@ export function useMDXComponents(components) {
}
```
+> **Good to know**:
+>
+> - `mdx-components.tsx` is **required** to use `@next/mdx` with App Router and will not work without it.
+> - Learn more about the [`mdx-components.tsx` file convention](/docs/app/api-reference/file-conventions/mdx-components).
+> - Learn how to [use custom styles and components](#using-custom-styles-and-components).
+
+## Rendering MDX
+
+You can render MDX using Next.js's file based routing or by importing MDX files into other pages.
+
+### Using file based routing
+
+When using file based routing, you can use MDX pages like any other page.
+
+
+
+In App Router apps, that includes being able to use [metadata](/docs/app/building-your-application/optimizing/metadata).
+
+Create a new MDX page within the `/app` directory:
+
+```txt
+ my-project
+ ├── app
+ │ └── mdx-page
+ │ └── page.(mdx/md)
+ |── mdx-components.(tsx/js)
+ └── package.json
+```
+
-Update the `next.config.js` file at your project's root to configure it to use MDX:
+
-```js filename="next.config.js"
-const withMDX = require('@next/mdx')()
+Create a new MDX page within the `/pages` directory:
-/** @type {import('next').NextConfig} */
-const nextConfig = {
- // Configure `pageExtensions` to include MDX files
- pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
- // Optionally, add any other Next.js config below
-}
+```txt
+ my-project
+ |── mdx-components.(tsx/js)
+ ├── pages
+ │ └── mdx-page.(mdx/md)
+ └── package.json
+```
-module.exports = withMDX(nextConfig)
+
+
+You can use MDX in these files, and even import React components, directly inside your MDX page:
+
+```mdx
+import { MyComponent } from 'my-component'
+
+# Welcome to my MDX page!
+
+This is some **bold** and _italics_ text.
+
+This is a list in markdown:
+
+- One
+- Two
+- Three
+
+Checkout my React component:
+
+
```
+Navigating to the `/mdx-page` route should display your rendered MDX page.
+
+### Using imports
+
-Then, create a new MDX page within the `/app` directory:
+Create a new page within the `/app` directory and an MDX file wherever you'd like:
```txt
- your-project
+ my-project
├── app
- │ └── my-mdx-page
- │ └── page.mdx
+ │ └── mdx-page
+ │ └── page.(tsx/js)
+ ├── markdown
+ │ └── welcome.(mdx/md)
+ |── mdx-components.(tsx/js)
└── package.json
```
@@ -95,21 +170,24 @@ Then, create a new MDX page within the `/app` directory:
-Then, create a new MDX page within the `/pages` directory:
+Create a new page within the `/pages` directory and an MDX file wherever you'd like:
```txt
- your-project
+ my-project
├── pages
- │ └── my-mdx-page.mdx
+ │ └── mdx-page.(tsx/js)
+ ├── markdown
+ │ └── welcome.(mdx/md)
+ |── mdx-components.(tsx/js)
└── package.json
```
-Now you can use markdown and import React components directly inside your MDX page:
+You can use MDX in these files, and even import React components, directly inside your MDX page:
-```mdx
-import { MyComponent } from 'my-components'
+```mdx filename="markdown/welcome.mdx" switcher
+import { MyComponent } from 'my-component'
# Welcome to my MDX page!
@@ -126,37 +204,172 @@ Checkout my React component:
```
-Navigating to the `/my-mdx-page` route should display your rendered MDX.
+Import the MDX file inside the page to display the content:
-## Remote MDX
+
-If your markdown or MDX files or content lives _somewhere else_, you can fetch it dynamically on the server. This is useful for content stored in a separate local folder, CMS, database, or anywhere else. A popular community package for this use is [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote#react-server-components-rsc--nextjs-app-directory-support).
+```tsx filename="app/mdx-page/page.tsx" switcher
+import Welcome from '@/markdown/welcome.mdx'
-> **Good to know**: Please proceed with caution. MDX compiles to JavaScript and is executed on the server. You should only fetch MDX content from a trusted source, otherwise this can lead to remote code execution (RCE).
+export default function Page() {
+ return
+}
+```
-The following example uses `next-mdx-remote`:
+```jsx filename="app/mdx-page/page.js" switcher
+import Welcome from '@/markdown/welcome.mdx'
+
+export default function Page() {
+ return
+}
+```
+
+
+
+
+
+```tsx filename="pages/mdx-page.tsx" switcher
+import Welcome from '@/markdown/welcome.mdx'
+
+export default function Page() {
+ return
+}
+```
+
+```jsx filename="pages/mdx-page.js" switcher
+import Welcome from '@/markdown/welcome.mdx'
+
+export default function Page() {
+ return
+}
+```
+
+
+
+Navigating to the `/mdx-page` route should display your rendered MDX page.
+
+## Using custom styles and components
+
+Markdown, when rendered, maps to native HTML elements. For example, writing the following markdown:
+
+```md
+## This is a heading
+
+This is a list in markdown:
+
+- One
+- Two
+- Three
+```
+
+Generates the following HTML:
+
+```html
+
This is a heading
+
+
This is a list in markdown:
+
+
+
One
+
Two
+
Three
+
+```
+
+To style your markdown, you can provide custom components that map to the generated HTML elements. Styles and components can be implemented globally, locally, and with shared layouts.
+
+### Global styles and components
+
+Adding styles and components in `mdx-components.tsx` will affect _all_ MDX files in your application.
+
+```tsx filename="mdx-components.tsx" switcher
+import type { MDXComponents } from 'mdx/types'
+import Image, { ImageProps } from 'next/image'
+
+// This file allows you to provide custom React components
+// to be used in MDX files. You can import and use any
+// React component you want, including inline styles,
+// components from other libraries, and more.
+
+export function useMDXComponents(components: MDXComponents): MDXComponents {
+ return {
+ // Allows customizing built-in components, e.g. to add styling.
+ h1: ({ children }) => (
+
{children}
+ ),
+ img: (props) => (
+
+ ),
+ ...components,
+ }
+}
+```
+
+```js filename="mdx-components.js" switcher
+import Image from 'next/image'
+
+// This file allows you to provide custom React components
+// to be used in MDX files. You can import and use any
+// React component you want, including inline styles,
+// components from other libraries, and more.
+
+export function useMDXComponents(components) {
+ return {
+ // Allows customizing built-in components, e.g. to add styling.
+ h1: ({ children }) => (
+
{children}
+ ),
+ img: (props) => (
+
+ ),
+ ...components,
+ }
+}
+```
+
+### Local styles and components
+
+You can apply local styles and components to specific pages by passing them into imported MDX components. These will merge with and override [global styles and components](#global-styles-and-components).
-```tsx filename="app/my-mdx-page-remote/page.tsx" switcher
-import { MDXRemote } from 'next-mdx-remote/rsc'
+```tsx filename="app/mdx-page/page.tsx" switcher
+import Welcome from '@/markdown/welcome.mdx'
-export default async function RemoteMdxPage() {
- // MDX text - can be from a local file, database, CMS, fetch, anywhere...
- const res = await fetch('https://...')
- const markdown = await res.text()
- return
+function CustomH1({ children }) {
+ return
{children}
+}
+
+const overrideComponents = {
+ h1: CustomH1,
+}
+
+export default function Page() {
+ return
}
```
-```jsx filename="app/my-mdx-page-remote/page.js" switcher
-import { MDXRemote } from 'next-mdx-remote/rsc'
+```jsx filename="app/mdx-page/page.js" switcher
+import Welcome from '@/markdown/welcome.mdx'
-export default async function RemoteMdxPage() {
- // MDX text - can be from a local file, database, CMS, fetch, anywhere...
- const res = await fetch('https://...')
- const markdown = await res.text()
- return
+function CustomH1({ children }) {
+ return
}
-export default function RemoteMdxPage({ mdxSource }: Props) {
- return
+const overrideComponents = {
+ h1: CustomH1,
}
-export async function getStaticProps() {
- // MDX text - can be from a local file, database, CMS, fetch, anywhere...
- const res = await fetch('https:...')
- const mdxText = await res.text()
- const mdxSource = await serialize(mdxText)
- return { props: { mdxSource } }
+export default function Page() {
+ return
}
```
-```jsx filename="pages/my-mdx-page-remote.js" switcher
-import { serialize } from 'next-mdx-remote/serialize'
-import { MDXRemote } from 'next-mdx-remote'
+```jsx filename="pages/mdx-page.js" switcher
+import Welcome from '@/markdown/welcome.mdx'
-export default function RemoteMdxPage({ mdxSource }) {
- return
+function CustomH1({ children }) {
+ return
{children}
}
-export async function getStaticProps() {
- // MDX text - can be from a local file, database, CMS, fetch, anywhere...
- const res = await fetch('https:...')
- const mdxText = await res.text()
- const mdxSource = await serialize(mdxText)
- return { props: { mdxSource } }
+const overrideComponents = {
+ h1: CustomH1,
+}
+
+export default function Page() {
+ return
}
```
-Navigating to the `/my-mdx-page-remote` route should display your rendered MDX.
-
-## Layouts
+### Shared layouts
To share a layout across MDX pages, you can use the [built-in layouts support](/docs/app/building-your-application/routing/pages-and-layouts#layouts) with the App Router.
-```tsx filename="app/my-mdx-page/layout.tsx" switcher
+```tsx filename="app/mdx-page/layout.tsx" switcher
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return
{children}
}
```
-```jsx filename="app/my-mdx-page/layout.js" switcher
+```jsx filename="app/mdx-page/layout.js" switcher
export default function MdxLayout({ children }) {
// Create any shared layout or styles here
return
{children}
@@ -261,6 +466,156 @@ export default function MDXPage({ children }) {
+### Using Tailwind typography plugin
+
+If you are using [Tailwind](https://tailwindcss.com) to style your application, using the [`@tailwindcss/typography` plugin](https://tailwindcss.com/docs/plugins#typography) will allow you to reuse your Tailwind configuration and styles in your markdown files.
+
+The plugin adds a set of `prose` classes that can be used to add typographic styles to content blocks that come from sources, like markdown.
+
+[Install Tailwind typography](https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#installation) and use with [shared layouts](#shared-layouts) to add the `prose` you want.
+
+
+
+```tsx filename="app/mdx-page/layout.tsx" switcher
+export default function MdxLayout({ children }: { children: React.ReactNode }) {
+ // Create any shared layout or styles here
+ return (
+
+ {children}
+
+ )
+}
+```
+
+```jsx filename="app/mdx-page/layout.js" switcher
+export default function MdxLayout({ children }) {
+ // Create any shared layout or styles here
+ return (
+
+ {children}
+
+ )
+}
+```
+
+
+
+
+
+To share a layout around MDX pages, create a layout component:
+
+```tsx filename="components/mdx-layout.tsx" switcher
+export default function MdxLayout({ children }: { children: React.ReactNode }) {
+ // Create any shared layout or styles here
+ return (
+
+ {children}
+
+ )
+}
+```
+
+```jsx filename="components/mdx-layout.js" switcher
+export default function MdxLayout({ children }) {
+ // Create any shared layout or styles here
+ return (
+
+ {children}
+
+ )
+}
+```
+
+Then, import the layout component into the MDX page, wrap the MDX content in the layout, and export it:
+
+```mdx
+import MdxLayout from '../components/mdx-layout'
+
+# Welcome to my MDX page!
+
+export default function MDXPage({ children }) {
+ return {children}
+
+}
+```
+
+
+
+## Frontmatter
+
+Frontmatter is a YAML like key/value pairing that can be used to store data about a page. `@next/mdx` does **not** support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as:
+
+- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter)
+- [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter)
+- [gray-matter](https://github.com/jonschlinkert/gray-matter)
+
+`@next/mdx` **does** allow you to use exports like any other JavaScript component:
+
+```mdx filename="content/blog-post.mdx" switcher
+export const metadata = {
+ author: 'John Doe',
+}
+
+# Blog post
+```
+
+Metadata can now be referenced outside of the MDX file:
+
+
+
+```tsx filename="app/blog/page.tsx" switcher
+import BlogPost, { metadata } from '@/content/blog-post.mdx'
+
+export default function Page() {
+ console.log('metadata': metadata)
+ //=> { author: 'John Doe' }
+ return
+}
+```
+
+```jsx filename="app/blog/page.js" switcher
+import BlogPost, { metadata } from '@/content/blog-post.mdx'
+
+export default function Page() {
+ console.log('metadata': metadata)
+ //=> { author: 'John Doe' }
+ return
+}
+```
+
+
+
+
+
+```tsx filename="pages/blog.tsx" switcher
+import BlogPost, { metadata } from '@/content/blog-post.mdx'
+
+export default function Page() {
+ console.log('metadata': metadata)
+ //=> { author: 'John Doe' }
+ return
+}
+```
+
+```jsx filename="pages/blog.js" switcher
+import BlogPost, { metadata } from '@/content/blog-post.mdx'
+
+export default function Page() {
+ console.log('metadata': metadata)
+ //=> { author: 'John Doe' }
+ return
+}
+```
+
+
+
+A common use case for this is when you want to iterate over a collection of MDX and extract data. For example, creating a blog index page from all blog posts. You can use packages like [Node's `fs` module](https://nodejs.org/api/fs.html) or [globby](https://www.npmjs.com/package/globby) to read a directory of posts and extract the metadata.
+
+> **Good to know**:
+>
+> - Using `fs`, `globby`, etc. can only be used server-side.
+> - View the [Portfolio Starter Kit](https://vercel.com/templates/next.js/portfolio-starter-kit) template for a complete working example.
+
## Remark and Rehype Plugins
You can optionally provide `remark` and `rehype` plugins to transform the MDX content.
@@ -276,7 +631,7 @@ import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure `pageExtensions`` to include MDX files
- pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
+ pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
}
@@ -292,111 +647,84 @@ const withMDX = createMDX({
export default withMDX(nextConfig)
```
-## Frontmatter
-
-Frontmatter is a YAML like key/value pairing that can be used to store data about a page. `@next/mdx` does **not** support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as:
-
-- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter)
-- [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter)
-- [gray-matter](https://github.com/jonschlinkert/gray-matter).
-
-To access page metadata with `@next/mdx`, you can export a metadata object from within the `.mdx` file:
+## Remote MDX
-```mdx
-export const metadata = {
- author: 'John Doe',
-}
+If your MDX files or content lives _somewhere else_, you can fetch it dynamically on the server. This is useful for content stored in a separate local folder, CMS, database, or anywhere else. A popular community package for this use is [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote#react-server-components-rsc--nextjs-app-directory-support).
-# My MDX page
-```
+> **Good to know**: Please proceed with caution. MDX compiles to JavaScript and is executed on the server. You should only fetch MDX content from a trusted source, otherwise this can lead to remote code execution (RCE).
-## Custom Elements
+The following example uses `next-mdx-remote`:
-One of the pleasant aspects of using markdown, is that it maps to native `HTML` elements, making writing fast, and intuitive:
+
-```md
-This is a list in markdown:
+```tsx filename="app/mdx-page-remote/page.tsx" switcher
+import { MDXRemote } from 'next-mdx-remote/rsc'
-- One
-- Two
-- Three
+export default async function RemoteMdxPage() {
+ // MDX text - can be from a local file, database, CMS, fetch, anywhere...
+ const res = await fetch('https://...')
+ const markdown = await res.text()
+ return
+}
```
-The above generates the following `HTML`:
-
-```html
-
This is a list in markdown:
+```jsx filename="app/mdx-page-remote/page.js" switcher
+import { MDXRemote } from 'next-mdx-remote/rsc'
-
-
One
-
Two
-
Three
-
+export default async function RemoteMdxPage() {
+ // MDX text - can be from a local file, database, CMS, fetch, anywhere...
+ const res = await fetch('https://...')
+ const markdown = await res.text()
+ return
+}
```
-When you want to style your own elements for a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements.
-
-
-
-To do this, open the `mdx-components.tsx` file at the root of your application and add custom elements:
-
-To do this, create a `mdx-components.tsx` file at the root of your application (the parent folder of `pages/` or `src/`) and add custom elements:
-
-
+```tsx filename="pages/mdx-page-remote.tsx" switcher
+import { serialize } from 'next-mdx-remote/serialize'
+import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
-```tsx filename="mdx-components.tsx" switcher
-import type { MDXComponents } from 'mdx/types'
-import Image, { ImageProps } from 'next/image'
+interface Props {
+ mdxSource: MDXRemoteSerializeResult
+}
-// This file allows you to provide custom React components
-// to be used in MDX files. You can import and use any
-// React component you want, including inline styles,
-// components from other libraries, and more.
+export default function RemoteMdxPage({ mdxSource }: Props) {
+ return
+}
-export function useMDXComponents(components: MDXComponents): MDXComponents {
- return {
- // Allows customizing built-in components, e.g. to add styling.
- h1: ({ children }) =>
{children}
,
- img: (props) => (
-
- ),
- ...components,
- }
+export async function getStaticProps() {
+ // MDX text - can be from a local file, database, CMS, fetch, anywhere...
+ const res = await fetch('https:...')
+ const mdxText = await res.text()
+ const mdxSource = await serialize(mdxText)
+ return { props: { mdxSource } }
}
```
-```js filename="mdx-components.js" switcher
-import Image from 'next/image'
+```jsx filename="pages/mdx-page-remote.js" switcher
+import { serialize } from 'next-mdx-remote/serialize'
+import { MDXRemote } from 'next-mdx-remote'
-// This file allows you to provide custom React components
-// to be used in MDX files. You can import and use any
-// React component you want, including inline styles,
-// components from other libraries, and more.
+export default function RemoteMdxPage({ mdxSource }) {
+ return
+}
-export function useMDXComponents(components) {
- return {
- // Allows customizing built-in components, e.g. to add styling.
- h1: ({ children }) =>
{children}
,
- img: (props) => (
-
- ),
- ...components,
- }
+export async function getStaticProps() {
+ // MDX text - can be from a local file, database, CMS, fetch, anywhere...
+ const res = await fetch('https:...')
+ const mdxText = await res.text()
+ const mdxSource = await serialize(mdxText)
+ return { props: { mdxSource } }
}
```
+
+
+Navigating to the `/mdx-page-remote` route should display your rendered MDX.
+
## Deep Dive: How do you transform markdown into HTML?
React does not natively understand markdown. The markdown plaintext needs to first be transformed into HTML. This can be accomplished with `remark` and `rehype`.
diff --git a/docs/02-app/01-building-your-application/08-testing/04-cypress.mdx b/docs/02-app/01-building-your-application/08-testing/04-cypress.mdx
index 0881068aef150..c8a515ded87d0 100644
--- a/docs/02-app/01-building-your-application/08-testing/04-cypress.mdx
+++ b/docs/02-app/01-building-your-application/08-testing/04-cypress.mdx
@@ -9,7 +9,7 @@ description: Learn how to set up Cypress with Next.js for End-to-End (E2E) and C
> **Warning:**
>
> - For **component testing**, Cypress currently does not support [Next.js version 14](https://github.com/cypress-io/cypress/issues/28185) and `async` Server Components. These issues are being tracked. For now, component testing works with Next.js version 13, and we recommend E2E testing for `async` Server Components.
-> - Cypress currently does not support [TypeScript version 5](https://github.com/cypress-io/cypress/issues/27731) with `moduleResolution:"bundler"`. This issue is being tracked.
+> - Cypress versions below 13.6.3 do not support [TypeScript version 5](https://github.com/cypress-io/cypress/issues/27731) with `moduleResolution:"bundler"`. However, this issue has been resolved in Cypress version 13.6.3 and later. [cypress v13.6.3](https://docs.cypress.io/guides/references/changelog#13-6-3)
diff --git a/docs/02-app/01-building-your-application/09-authentication/index.mdx b/docs/02-app/01-building-your-application/09-authentication/index.mdx
index c65b1f58b33fd..02d1b9b713f47 100644
--- a/docs/02-app/01-building-your-application/09-authentication/index.mdx
+++ b/docs/02-app/01-building-your-application/09-authentication/index.mdx
@@ -1,38 +1,447 @@
---
title: Authentication
-description: Learn how to implement authentication in Next.js, covering best practices, securing routes, authorization techniques, and session management.
+description: Learn how to implement authentication in your Next.js application.
---
-To implement authentication in Next.js, familiarize yourself with three foundational concepts:
+Understanding authentication is crucial for protecting your application's data. This page will guide you through how to use Next.js features to implement auth, as well as patterns so you can choose the right strategy for your application.
-- **[Authentication](#authentication)** verifies if the user is who they say they are. It requires the user to prove their identity with something they have, such as a username and password.
-- **[Session Management](#session-management)** tracks the user's state (e.g. logged in) across multiple requests.
-- **[Authorization](#authorization)** decides what parts of the application the user is allowed to access.
+Before starting, it helps to break down the process into three concepts:
-This page demonstrates how to use Next.js features to implement common authentication, authorization, and session management patterns so you can choose the best solutions based on your application's needs.
+1. **[Authentication](#authentication)**: Verifies if the user is who they say they are. It requires the user to prove their identity with something they have, such as a username and password.
+2. **[Session Management](#session-management)**: Tracks the user's auth state across requests.
+3. **[Authorization](#authorization)**: Decides what routes and data the user can access.
+
+This diagram shows the auth flow with React and Next.js features:
+
+
+
+The examples on this page will walk you through basic username and password auth for educational purposes. While you can implement a custom auth solution, for increased security and simplicity, we recommend using an authentication library. These offer built-in solutions for authentication, session management, and authorization, as well as additional features such as social logins, multi-factor authentication, and role-based access control. You can find a list in the [Auth Libraries](#auth-libraries) section.
## Authentication
-Authentication verifies a user's identity. This happens when a user logs in, either with a username and password or through a service like Google. It's all about confirming that users are really who they claim to be, protecting both the user's data and the application from unauthorized access or fraudulent activities.
+
+
+### Sign-up and login functionality
+
+You can use the [`
+ )
+}
+```
+
+```jsx filename="app/ui/signup-form.js" switcher
+import { signup } from '@/app/actions/auth'
+
+export function SignupForm() {
+ return (
+
+ )
+}
+```
+
+```tsx filename="app/actions/auth.tsx" switcher
+export async function signup(formData: FormData) {}
+```
+
+```jsx filename="app/actions/auth.js" switcher
+export async function signup(formData) {}
+```
+
+#### 2. Validate form fields on the server
+
+Use the Server Action to validate the form fields on the server. If your authentication provider doesn't provide form validation, you can use a schema validation library like [Zod](https://zod.dev/) or [Yup](https://github.com/jquense/yup).
+
+Normal validation practices apply here. For example, you should check that the user has entered a valid email address, a password that meets your security requirements, name is not empty, etc. Using Zod as an example, you can define a form schema with appropriate error messages:
+
+```ts filename="app/lib/definitions.ts" switcher
+import { z } from 'zod'
+
+export const SignupFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'Name must be at least 2 characters long.' })
+ .trim(),
+ email: z.string().email({ message: 'Please enter a valid email.' }).trim(),
+ password: z
+ .string()
+ .min(8, { message: 'Be at least 8 characters long' })
+ .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
+ .regex(/[0-9]/, { message: 'Contain at least one number.' })
+ .regex(/[^a-zA-Z0-9]/, {
+ message: 'Contain at least one special character.',
+ })
+ .trim(),
+})
+
+export type FormState =
+ | {
+ errors?: {
+ name?: string[]
+ email?: string[]
+ password?: string[]
+ }
+ message?: string
+ }
+ | undefined
+```
+
+```js filename="app/lib/definitions.js" switcher
+import { z } from 'zod'
+
+export const SignupFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'Name must be at least 2 characters long.' })
+ .trim(),
+ email: z.string().email({ message: 'Please enter a valid email.' }).trim(),
+ password: z
+ .string()
+ .min(8, { message: 'Be at least 8 characters long' })
+ .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
+ .regex(/[0-9]/, { message: 'Contain at least one number.' })
+ .regex(/[^a-zA-Z0-9]/, {
+ message: 'Contain at least one special character.',
+ })
+ .trim(),
+})
+```
+
+After validation, `return` early if any form fields do not match the schema to prevent unnecessary calls to your authentication provider's API or database:
+
+```ts filename="app/actions/auth.ts" switcher
+import { SignupFormSchema, FormState } from '@/app/lib/definitions'
+
+export async function signup(state: FormState, formData: FormData) {
+ // Validate form fields
+ const validatedFields = SignupFormSchema.safeParse({
+ name: formData.get('name'),
+ email: formData.get('email'),
+ password: formData.get('password'),
+ })
+
+ // If any form fields are invalid, return early
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Call the provider or db to create a user...
+}
+```
+
+```js filename="app/actions/auth.js" switcher
+import { SignupFormSchema } from '@/app/lib/definitions'
+
+export async function signup(formData: FormData) {
+ // Validate form fields
+ const validatedFields = SignupFormSchema.safeParse({
+ name: formData.get('name'),
+ email: formData.get('email'),
+ password: formData.get('password'),
+ })
+
+ // If any form fields are invalid, return early
+ if (!validatedFields.success) {
+ return {
+ errors: validatedFields.error.flatten().fieldErrors,
+ }
+ }
+
+ // Call the provider or db to create a user...
+}
+```
+
+Back in your ``, you can use React's `useFormState()` hook to display validation errors to the user:
+
+```tsx filename="app/ui/signup-form.tsx" switcher highlight={7,15,21,27-36}
+'use client'
+
+import { useFormState } from 'react-dom'
+import { signup } from '@/app/actions/auth'
+
+export function SignupForm() {
+ const [state, action] = useFormState(signup, undefined)
+
+ return (
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup-form.js" switcher highlight={7,15,21,27-36}
+'use client'
+
+import { useFormState } from 'react-dom'
+import { signup } from '@/app/actions/auth'
+
+export function SignupForm() {
+ const [state, action] = useFormState(signup, undefined)
+
+ return (
+
+ )
+}
+```
+
+You can also use the `useFormStatus()` hook to handle the pending state on form submission:
+
+```tsx filename="app/ui/signup-form.tsx" switcher
+'use client'
+
+import { useFormStatus, useFormState } from 'react-dom'
+
+// ...
+
+export function SignupButton() {
+ const { pending } = useFormStatus()
+
+ return (
+
+ )
+}
+```
+
+```jsx filename="app/ui/signup-form.js" switcher
+'use client'
+
+import { useFormStatus, useFormState } from 'react-dom'
+
+// ...
+
+export function SignupButton() {
+ const { pending } = useFormStatus()
+
+ return (
+
+ )
+}
+```
+
+> **Tip:** `useFormStatus()` must be called from a component that is rendered inside a `
-1. The user submits their credentials through a login form.
+Here are the steps to implement a sign-up and/or login form:
+
+1. The user submits their credentials through a form.
2. The form sends a request that is handled by an API route.
3. Upon successful verification, the process is completed, indicating the user's successful authentication.
4. If verification is unsuccessful, an error message is shown.
@@ -161,730 +570,1143 @@ export default async function handler(req, res) {
+## Session Management
+
+Session management ensures that the user's authenticated state is preserved across requests and sometimes devices. It involves creating, storing, refreshing, and deleting sessions or tokens.
+
+There are two types of sessions:
+
+1. **Stateless**: Session data (or a token) is stored in the browser's cookies. The cookie is sent with each request, allowing the session to be verified on the server. This method is simpler, but can be less secure if not implemented correctly.
+2. **Database**: Session data is stored in a database, with the user's browser only receiving the encrypted session ID. This method is more secure, but can be complex and use more server resources.
+
+> While you can use either method, or both, we recommend using session management library such as [iron-session](https://github.com/vvo/iron-session) or [Jose](https://github.com/panva/jose).
+
+### Stateless Sessions
+
-1. The user submits their credentials through a login form.
-2. The form calls a Server Action.
-3. Upon successful verification, the process is completed, indicating the user's successful authentication.
-4. If verification is unsuccessful, an error message is shown.
+To create and manage stateless sessions in your Next.js application, there are a few steps you need to follow:
-Consider a login form where users can input their credentials:
+1. Generate a secret key, which will be used to sign your session, and store it as an [environment variable](/docs/app/building-your-application/configuring/environment-variables).
+2. Write logic to encrypt/decrypt session data using a session management library.
+3. Save the session as a cookie using the Next.js [`cookies()`](/docs/app/api-reference/functions/cookies) API. The cookie should be set on the server, and include the [recommended options](#3-setting-the-cookie-recommended-options).
-```tsx filename="app/login/page.tsx" switcher
-import { authenticate } from '@/app/lib/actions'
+In addition to the above, consider adding functionality to [update (or refresh)](#updating-or-refreshing-sessions) the session when the user returns to the application, and [delete](#deleting-the-session) the session when the user logs out.
-export default function Page() {
- return (
-
- )
-}
+> **Tips:**
+>
+> - If you're using an [auth library](#auth-libraries), check if they handle sessions.
+
+#### 1. Generating a secret key
+
+There are a few ways you can generate secret key to sign your session. For example, you may choose to use the `openssl` command in your terminal:
+
+```bash filename="terminal"
+openssl rand -base64 32
```
-```jsx filename="app/login/page.jsx" switcher
-import { authenticate } from '@/app/lib/actions'
+This command generates a 32-character random string that you can use as your secret key and store in your [environment variables file](/docs/app/building-your-application/configuring/environment-variables):
-export default function Page() {
- return (
-
- )
-}
+```bash filename=".env"
+SESSION_SECRET=your_secret_key
```
-The form above has two input fields for capturing the user's email and password. On submission, it calls the `authenticate` Server Action.
+You can then reference this key in your session management logic:
-You can then call your Authentication Provider's API in the Server Action to handle authentication:
+```js filename="app/actions/session.js"
+const secretKey = process.env.SESSION_SECRET
+```
-```ts filename="app/lib/actions.ts" switcher
-'use server'
+#### 2. Encrypting and decrypting sessions
-import { signIn } from '@/auth'
+Next, you can use your preferred [session management library](#session-management-libraries) to encrypt and decrypt sessions. Continuing from the previous example, we'll use [Jose](https://www.npmjs.com/package/jose) (compatible with the [Edge Runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes)):
+
+```tsx filename="app/actions/session.ts" switcher
+import 'server-only'
+import { SignJWT, jwtVerify } from 'jose'
+import { SessionPayload } from '@/app/lib/definitions'
+
+const secretKey = process.env.SESSION_SECRET
+const encodedKey = new TextEncoder().encode(secretKey)
-export async function authenticate(_currentState: unknown, formData: FormData) {
+export async function encrypt(payload: SessionPayload) {
+ return new SignJWT(payload)
+ .setProtectedHeader({ alg: 'HS256' })
+ .setIssuedAt()
+ .setExpirationTime('7d')
+ .sign(encodedKey)
+}
+
+export async function decrypt(session: string | undefined = '') {
try {
- await signIn('credentials', formData)
+ const { payload } = await jwtVerify(session, encodedKey, {
+ algorithms: ['HS256'],
+ })
+ return payload
} catch (error) {
- if (error) {
- switch (error.type) {
- case 'CredentialsSignin':
- return 'Invalid credentials.'
- default:
- return 'Something went wrong.'
- }
- }
- throw error
+ console.log('Failed to verify session')
}
}
```
-```js filename="app/lib/actions.js" switcher
-'use server'
+```jsx filename="app/actions/session.js" switcher
+import 'server-only'
+import { SignJWT, jwtVerify } from 'jose'
-import { signIn } from '@/auth'
+const secretKey = process.env.SESSION_SECRET
+const encodedKey = new TextEncoder().encode(secretKey)
-export async function authenticate(_currentState, formData) {
+export async function encrypt(payload) {
+ return new SignJWT(payload)
+ .setProtectedHeader({ alg: 'HS256' })
+ .setIssuedAt()
+ .setExpirationTime('7d')
+ .sign(encodedKey)
+}
+
+export async function decrypt(session) {
try {
- await signIn('credentials', formData)
+ const { payload } = await jwtVerify(session, encodedKey, {
+ algorithms: ['HS256'],
+ })
+ return payload
} catch (error) {
- if (error) {
- switch (error.type) {
- case 'CredentialsSignin':
- return 'Invalid credentials.'
- default:
- return 'Something went wrong.'
- }
- }
- throw error
+ console.log('Failed to verify session')
}
}
```
-
+Above, we're also using React's [`server-only`](https://www.npmjs.com/package/server-only) package to ensure that your session management logic is only executed on the server.
-In this code, the `signIn` method checks the credentials against stored user data.
-After the authentication provider processes the credentials, there are two possible outcomes:
+> **Tips**:
+>
+> - The payload should contain the **minimum**, unique user data that'll be used in subsequent requests, such as the user's ID, role, etc. It should not contain personally identifiable information like phone number, email address, credit card information, etc, or sensitive data like passwords.
-- **Successful Authentication**: This outcome implies that the login was successful. Further actions, such as accessing protected routes and fetching user information, can then be initiated.
-- **Failed Authentication**: In cases where the credentials are incorrect or an error is encountered, the function returns a corresponding error message to indicate the authentication failure.
+#### 3. Setting the cookie (recommended options)
-
+To store the session in a cookie, use the Next.js [`cookies()`](/docs/app/api-reference/functions/cookies) API. The cookie should be set on the server, and include the recommended options:
-Finally, in your `login-form.tsx` component, you can use React's `useFormState` to call the Server Action and handle form errors, and use `useFormStatus` to handle the pending state of the form:
+- **HttpOnly**: Prevents client-side JavaScript from accessing the cookie.
+- **Secure**: Use https to send the cookie.
+- **SameSite**: Specify whether the cookie can be sent with cross-site requests.
+- **Max-Age or Expires**: Delete the cookie after a certain period.
+- **Path**: Define the URL path for the cookie.
-```tsx filename="app/login/page.tsx" switcher
-'use client'
+Please refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) for more information on each of these options.
-import { authenticate } from '@/app/lib/actions'
-import { useFormState, useFormStatus } from 'react-dom'
+Continuing from the previous example, here's how you'd save the encrypted session as a cookie:
-export default function Page() {
- const [errorMessage, dispatch] = useFormState(authenticate, undefined)
+```ts filename="app/actions/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
- return (
-
- )
+export async function createSession(userId: string) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
+ const session = await encrypt({ userId, expiresAt })
+
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
}
+```
-function LoginButton() {
- const { pending } = useFormStatus()
+```js filename="app/actions/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
- const handleClick = (event) => {
- if (pending) {
- event.preventDefault()
- }
- }
+export async function createSession(userId: string) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
+ const session = await encrypt({ userId, expiresAt })
- return (
-
- )
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
}
```
-```jsx filename="app/login/page.jsx" switcher
-'use client'
+Back in your Server Action, you can invoke the `createSession()` function after successfully creating the user or verifying their credentials, and use the [`redirect()`](/docs/app/building-your-application/routing/redirecting) API to redirect the user to the appropriate page:
-import { authenticate } from '@/app/lib/actions'
-import { useFormState, useFormStatus } from 'react-dom'
+```ts filename="app/actions/auth.ts" switcher
+import { createSession } from '@/app/lib/session'
-export default function Page() {
- const [errorMessage, dispatch] = useFormState(authenticate, undefined)
+export async function signup(state: FormState, formData: FormData) {
+ // Previous steps:
+ // 1. Validate form fields
+ // 2. Prepare data for insertion into database
+ // 3. Insert the user into the database or call an Library API
- return (
-
- )
+ // Current steps:
+ // 4. Create user session
+ await createSession(user.id)
+ // 5. Redirect user
+ redirect('/profile')
}
+```
-function LoginButton() {
- const { pending } = useFormStatus()
+```js filename="app/actions/auth.js" switcher
+import { createSession } from '@/app/lib/session'
- const handleClick = (event) => {
- if (pending) {
- event.preventDefault()
- }
- }
+export async function signup(state, formData) {
+ // Previous steps:
+ // 1. Validate form fields
+ // 2. Prepare data for insertion into database
+ // 3. Insert the user into the database or call an Library API
- return (
-
- )
+ // Current steps:
+ // 4. Create user session
+ await createSession(user.id)
+ // 5. Redirect user
+ redirect('/profile')
}
```
-
+> **Tips**:
+>
+> - **Cookies should be set on the server** to prevent client-side tampering.
+> - 🎥 Watch: Learn more about stateless sessions and authentication with Next.js → [YouTube (11 minutes)](https://www.youtube.com/watch?v=DJvM2lSPn6w).
-For a more streamlined authentication setup in Next.js projects, especially when offering multiple login methods, consider using a comprehensive [authentication solution](#examples).
+#### Updating (or refreshing) sessions
-## Authorization
+You can also extend the session's expiration time. This is useful for keeping the user logged in after they access the application again. For example:
-Once a user is authenticated, you'll need to ensure the user is allowed to visit certain routes, and perform operations such as mutating data with Server Actions and calling Route Handlers.
+```ts filename="app/actions/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
-### Protecting Routes with Middleware
+export async function updateSession() {
+ const session = cookies().get('session')?.value
+ const payload = await decrypt(session)
-[Middleware](/docs/app/building-your-application/routing/middleware) in Next.js helps you control who can access different parts of your website. This is important for keeping areas like the user dashboard protected while having other pages like marketing pages be public. It's recommended to apply Middleware across all routes and specify exclusions for public access.
+ if (!session || !payload) {
+ return null
+ }
-Here's how to implement Middleware for authentication in Next.js:
+ const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expires,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
-#### Setting Up Middleware:
+```js filename="app/actions/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
-- Create a `middleware.ts` or `.js` file in your project's root directory.
-- Include logic to authorize user access, such as checking for authentication tokens.
+eexport async function updateSession() {
+ const session = cookies().get('session').value
+ const payload = await decrypt(session)
-#### Defining Protected Routes:
+ if (!session || !payload) {
+ return null
+ }
-- Not all routes require authorization. Use the `matcher` option in your Middleware to specify any routes that do not require authorization checks.
+ const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expires,
+ sameSite: 'lax',
+ path: '/',
+ })
+}
+```
-#### Middleware Logic:
+> **Tips:**
+>
+> - If using an authentication library, check if they support refresh tokens.
-- Write logic to verify if a user is authenticated. Check user roles or permissions for route authorization.
+#### Deleting the session
-#### Handling Unauthorized Access:
+To delete the session, you can delete the cookie:
-- Redirect unauthorized users to a login or error page as appropriate.
+```ts filename="app/lib/session.ts" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
-Example Middleware file:
+export function deleteSession() {
+ cookies().delete('session')
+}
+```
-```ts filename="middleware.ts" switcher
-import type { NextRequest } from 'next/server'
+```js filename="app/lib/session.js" switcher
+import 'server-only'
+import { cookies } from 'next/headers'
-export function middleware(request: NextRequest) {
- const currentUser = request.cookies.get('currentUser')?.value
+export function deleteSession() {
+ cookies().delete('session')
+}
+```
- if (currentUser && !request.nextUrl.pathname.startsWith('/dashboard')) {
- return Response.redirect(new URL('/dashboard', request.url))
- }
+Then you can reuse the `deleteSession()` function in your application, for example, on logout:
- if (!currentUser && !request.nextUrl.pathname.startsWith('/login')) {
- return Response.redirect(new URL('/login', request.url))
- }
-}
+```ts filename="app/actions/auth.ts" switcher
+import { cookies } from 'next/headers'
+import { deleteSession } from '@/app/actions/session'
-export const config = {
- matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+export async function logout() {
+ deleteSession()
+ redirect('/login')
}
```
-```js filename="middleware.js" switcher
-export function middleware(request) {
- const currentUser = request.cookies.get('currentUser')?.value
+```js filename="app/actions/auth.js" switcher
+import { cookies } from 'next/headers'
+import { deleteSession } from '@/app/actions/session'
- if (currentUser && !request.nextUrl.pathname.startsWith('/dashboard')) {
- return Response.redirect(new URL('/dashboard', request.url))
- }
+export async function logout() {
+ deleteSession()
+ redirect('/login')
+}
+```
- if (!currentUser && !request.nextUrl.pathname.startsWith('/login')) {
- return Response.redirect(new URL('/login', request.url))
- }
+
+
+
+
+#### Setting and deleting cookies
+
+You can use [API Routes](/docs/pages/building-your-application/routing/api-routes) to set the session as a cookie on the server:
+
+```ts filename="pages/api/login.ts" switcher
+import { serialize } from 'cookie'
+import type { NextApiRequest, NextApiResponse } from 'next'
+
+export default function handler(req: NextApiRequest, res: NextApiResponse) {
+ const sessionData = req.body
+ const encryptedSessionData = encrypt(sessionData)
+
+ const cookie = serialize('session', encryptedSessionData, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ maxAge: 60 * 60 * 24 * 7, // One week
+ path: '/',
+ })
+ res.setHeader('Set-Cookie', cookie)
+ res.status(200).json({ message: 'Successfully set cookie!' })
}
+```
-export const config = {
- matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
+```js filename="pages/api/login.js" switcher
+import { serialize } from 'cookie'
+
+export default function handler(req, res) {
+ const sessionData = req.body
+ const encryptedSessionData = encrypt(sessionData)
+
+ const cookie = serialize('session', encryptedSessionData, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === 'production',
+ maxAge: 60 * 60 * 24 * 7, // One week
+ path: '/',
+ })
+ res.setHeader('Set-Cookie', cookie)
+ res.status(200).json({ message: 'Successfully set cookie!' })
}
```
-This example uses [`Response.redirect`](https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static) for handling redirects early in the request pipeline, making it efficient and centralizing access control.
+
+
+### Database Sessions
+
+To create and manage database sessions, you'll need to follow these steps:
+
+1. Create a table in your database to store session data (or check if your Auth Library handles this).
+2. Implement functionality to insert, update, and delete sessions.
+3. Encrypt the session ID before storing it in the user's browser, and ensure the database and cookie stay in sync (this is optional, but recommended for optimistic auth checks in [Middleware](#optimistic-checks-with-middleware-optional)).
-For specific redirection needs, the `redirect` function can be used in Server Components, Route Handlers, and Server Actions to provide more control. This is useful for role-based navigation or context-sensitive scenarios.
+Here's an example of how you can create a new database session, and encrypt the session ID before storing it in a cookie:
-```ts filename="app/page.tsx" switcher
-import { redirect } from 'next/navigation'
+```ts filename="app/actions/session.ts" switcher
+import cookies from 'next/headers'
+import { db } from '@/app/lib/db'
-export default function Page() {
- // Logic to determine if a redirect is needed
- const accessDenied = true
- if (accessDenied) {
- redirect('/login')
- }
+export async function createSession(id: number) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
- // Define other routes and logic
+ // 1. Create a session in the database
+ const data = await db
+ .insert(sessions)
+ .values({
+ userId: id,
+ expiresAt,
+ })
+ // Return the session ID
+ .returning({ id: sessions.id })
+
+ const sessionId = data[0].id
+
+ // 2. Encrypt the session ID
+ const session = await encrypt({ sessionId, expiresAt })
+
+ // 3. Store the session in cookies for optimistic auth checks
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
}
```
-```js filename="app/page.jsx" switcher
-import { redirect } from 'next/navigation'
+```js filename="app/actions/session.js" switcher
+import cookies from 'next/headers'
+import { db } from '@/app/lib/db'
-export default function Page() {
- // Logic to determine if a redirect is needed
- const accessDenied = true
- if (accessDenied) {
- redirect('/login')
- }
+export async function createSession(id) {
+ const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
+
+ // 1. Create a session in the database
+ const data = await db
+ .insert(sessions)
+ .values({
+ userId: id,
+ expiresAt,
+ })
+ // Return the session ID
+ .returning({ id: sessions.id })
- // Define other routes and logic
+ const sessionId = data[0].id
+
+ // 2. Encrypt the session ID
+ const session = await encrypt({ sessionId, expiresAt })
+
+ // 3. Store the session in cookies for optimistic auth checks
+ cookies().set('session', session, {
+ httpOnly: true,
+ secure: true,
+ expires: expiresAt,
+ sameSite: 'lax',
+ path: '/',
+ })
}
```
-
+> **Tips**:
+>
+> - For faster data retrieval, consider using a database like [Vercel Redis](https://vercel.com/docs/storage/vercel-kv). However, you can also keep the session data in your primary database, and combine data requests to reduce the number of queries.
+> - You may opt to use database sessions for more advanced use cases, such as keeping track of the last time a user logged in, or number of active devices. You can also give your users the ability to log out of all devices.
-After successful authentication, it's important to manage user navigation based on their roles. For example, an admin user might be redirected to an admin dashboard, while a regular user is sent to a different page. This is important for role-specific experiences and conditional navigation, such as prompting users to complete their profile if needed.
+After implementing session management, you'll need to add authorization logic to control what users can access and do within your application. Continue to the [Authorization](#authorization) section to learn more.
-When setting up authorization, it's important to ensure that the main security checks happen where your app accesses or changes data. While Middleware can be useful for initial validation, it should not be the sole line of defense in protecting your data. The bulk of security checks should be performed in the Data Access Layer (DAL).
+
-### Protecting API Routes
-
-API Routes in Next.js are essential for handling server-side logic and data management. It's crucial to secure these routes to ensure that only authorized users can access specific functionalities. This typically involves verifying the user's authentication status and their role-based permissions.
-
-Here's an example of securing an API Route:
+**Creating a Session on the Server**:
-```ts filename="pages/api/route.ts" switcher
+```ts filename="pages/api/create-session.ts" switcher
+import db from '../../lib/db'
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
- const session = await getSession(req)
-
- // Check if the user is authenticated
- if (!session) {
- res.status(401).json({
- error: 'User is not authenticated',
+ try {
+ const user = req.body
+ const sessionId = generateSessionId()
+ await db.insertSession({
+ sessionId,
+ userId: user.id,
+ createdAt: new Date(),
})
- return
- }
- // Check if the user has the 'admin' role
- if (session.user.role !== 'admin') {
- res.status(401).json({
- error: 'Unauthorized access: User does not have admin privileges.',
- })
- return
+ res.status(200).json({ sessionId })
+ } catch (error) {
+ res.status(500).json({ error: 'Internal Server Error' })
}
-
- // Proceed with the route for authorized users
- // ... implementation of the API Route
}
```
-```js filename="pages/api/route.js" switcher
-export default async function handler(req, res) {
- const session = await getSession(req)
+```js filename="pages/api/create-session.js" switcher
+import db from '../../lib/db'
- // Check if the user is authenticated
- if (!session) {
- res.status(401).json({
- error: 'User is not authenticated',
+export default async function handler(req, res) {
+ try {
+ const user = req.body
+ const sessionId = generateSessionId()
+ await db.insertSession({
+ sessionId,
+ userId: user.id,
+ createdAt: new Date(),
})
- return
- }
- // Check if the user has the 'admin' role
- if (session.user.role !== 'admin') {
- res.status(401).json({
- error: 'Unauthorized access: User does not have admin privileges.',
- })
- return
+ res.status(200).json({ sessionId })
+ } catch (error) {
+ res.status(500).json({ error: 'Internal Server Error' })
}
-
- // Proceed with the route for authorized users
- // ... implementation of the API Route
}
```
-This example demonstrates an API Route with a two-tier security check for authentication and authorization. It first checks for an active session, and then verifies if the logged-in user is an 'admin'. This approach ensures secure access, limited to authenticated and authorized users, maintaining robust security for request processing.
-
-
+## Authorization
-This approach, highlighted in [this security blog](/blog/security-nextjs-server-components-actions), advocates for consolidating all data access within a dedicated DAL. This strategy ensures consistent data access, minimizes authorization bugs, and simplifies maintenance. To ensure comprehensive security, consider the following key areas:
+Once a user is authenticated and a session is created, you can implement authorization to control what the user can access and do within your application.
-- Server Actions: Implement security checks in server-side processes, especially for sensitive operations.
-- Route Handlers: Manage incoming requests with security measures to ensure access is limited to authorized users.
-- Data Access Layer (DAL): Directly interacts with the database and is crucial for validating and authorizing data transactions. It's vital to perform critical checks within the DAL to secure data at its most crucial interaction point—access or modification.
+There are two main types of authorization checks:
-For a detailed guide on securing the DAL, including example code snippets and advanced security practices, refer to our [Data Access Layer section](/blog/security-nextjs-server-components-actions#data-access-layer) of the security guide.
+1. **Optimistic**: Checks if the user is authorized to access a route or perform an action using the session data stored in the cookie. These checks are useful for quick operations, such as showing/hiding UI elements or redirecting users based on permissions or roles.
+2. **Secure**: Checks if the user is authorized to access a route or perform an action using the session data stored in the database. These checks are more secure and are used for operations that require access to sensitive data or actions.
-### Protecting Server Actions
+For both cases, we recommend:
-It is important to treat [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) with the same security considerations as public-facing API endpoints. Verifying user authorization for each action is crucial. Implement checks within Server Actions to determine user permissions, such as restricting certain actions to admin users.
+- Creating a [Data Access Layer](#creating-a-data-access-layer-dal) to centralize your authorization logic
+- Using [Data Transfer Objects (DTO)](#using-data-transfer-objects-dto) to only return the necessary data
+- Optionally use [Middleware](#optimistic-checks-with-middleware-optional) to perform optimistic checks.
-In the example below, we check the user's role before allowing the action to proceed:
+### Optimistic checks with Middleware (Optional)
-```ts filename="app/lib/actions.ts" switcher
-'use server'
+There are some cases where you may want to use [Middleware](/docs/app/building-your-application/routing/middleware):
-// ...
+- To perform optimistic checks and redirect users based on their permissions. Since Middleware runs on every route, it's a good way to centralize your redirect logic and pre-filter unauthorized users.
+- To protect static routes that share data between users (e.g. content behind a paywall).
-export async function serverAction() {
- const session = await getSession()
- const userRole = session?.user?.role
+For example:
- // Check if user is authorized to perform the action
- if (userRole !== 'admin') {
- throw new Error('Unauthorized access: User does not have admin privileges.')
+```tsx filename="middleware.ts" switcher
+import { NextRequest, NextResponse } from 'next/server'
+import { decrypt } from '@/app/lib/session'
+import { cookies } from 'next/headers'
+
+// 1. Specify protected and public routes
+const protectedRoutes = ['/dashboard']
+const publicRoutes = ['/login', '/signup', '/']
+
+export default async function middleware(req: NextRequest) {
+ // 2. Check if the current route is protected or public
+ const path = req.nextUrl.pathname
+ const isProtectedRoute = protectedRoutes.includes(path)
+ const isPublicRoute = publicRoutes.includes(path)
+
+ // 3. Decrypt the session from the cookie
+ const cookie = cookies().get('session')?.value
+ const session = await decrypt(cookie)
+
+ // 5. Redirect to /login if the user is not authenticated
+ if (isProtectedRoute && !session?.userId) {
+ return NextResponse.redirect(new URL('/login', req.nextUrl))
}
- // Proceed with the action for authorized users
- // ... implementation of the action
+ // 6. Redirect to /dashboard if the user is authenticated
+ if (
+ isPublicRoute &&
+ session?.userId &&
+ !req.nextUrl.pathname.startsWith('/dashboard')
+ ) {
+ return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
+ }
+
+ return NextResponse.next()
+}
+
+// Routes Middleware should not run on
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
```
-```js filename="app/lib/actions.js" switcher
-'use server'
+```js filename="middleware.js" switcher
+import { NextResponse } from 'next/server'
+import { decrypt } from '@/app/lib/session'
+import { cookies } from 'next/headers'
-// ...
+// 1. Specify protected and public routes
+const protectedRoutes = ['/dashboard']
+const publicRoutes = ['/login', '/signup', '/']
-export async function serverAction() {
- const session = await getSession()
- const userRole = session?.user?.role
+export default async function middleware(req) {
+ // 2. Check if the current route is protected or public
+ const path = req.nextUrl.pathname
+ const isProtectedRoute = protectedRoutes.includes(path)
+ const isPublicRoute = publicRoutes.includes(path)
- // Check if user is authorized to perform the action
- if (userRole !== 'admin') {
- throw new Error('Unauthorized access: User does not have admin privileges.')
+ // 3. Decrypt the session from the cookie
+ const cookie = cookies().get('session')?.value
+ const session = await decrypt(cookie)
+
+ // 5. Redirect to /login if the user is not authenticated
+ if (isProtectedRoute && !session?.userId) {
+ return NextResponse.redirect(new URL('/login', req.nextUrl))
}
- // Proceed with the action for authorized users
- // ... implementation of the action
+ // 6. Redirect to /dashboard if the user is authenticated
+ if (
+ isPublicRoute &&
+ session?.userId &&
+ !req.nextUrl.pathname.startsWith('/dashboard')
+ ) {
+ return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
+ }
+
+ return NextResponse.next()
+}
+
+// Routes Middleware should not run on
+export const config = {
+ matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
```
-### Protecting Route Handlers
+However, since Middleware runs on every route, including [prefetched](/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching) routes, it's important use Middleware only to read the session from the cookie (optimistic checks), and avoid database checks or heavy computations to prevent performance issues.
-Route Handlers in Next.js play a vital role in managing incoming requests. Just like Server Actions, they should be secured to ensure that only authorized users can access certain functionalities. This often involves verifying the user's authentication status and their permissions.
+While Middleware can be useful for initial checks, it should not be your only line of defense in protecting your data. The majority of security checks should be performed as close as possible to your data source, see [Data Access Layer](#creating-a-data-access-layer-dal) for more information.
-Here's an example of securing a Route Handler:
+> **Tips**:
+>
+> - In Middleware, you can also read cookies using `req.cookies.get('session).value`.
+> - Middleware uses the [Edge Runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes), check if your Auth library and session management library are compatible.
+> - You can use the `matcher` property in the Middleware to specify which routes Middleware should run on. Although, for auth, it's recommended Middleware runs on all routes.
-```ts filename="app/api/route.ts" switcher
-export async function GET() {
- // User authentication and role verification
- const session = await getSession()
+
- // Check if the user is authenticated
- if (!session) {
- return new Response(null, { status: 401 }) // User is not authenticated
+### Creating a Data Access Layer (DAL)
+
+We recommend creating a Data Access Layer (DAL) to centralize your data requests and authorization logic. The DAL should include a function that verifies the user's session as they interact with your application.
+
+At the very least, the function should check if the session is valid, then redirect or return the user information needed to make further requests.
+
+For example, create a separate file for your DAL that includes a `verifySession()` function. Then use React's [cache](https://react.dev/reference/react/cache) to memoize the return value of the function during a React render pass:
+
+```tsx filename="app/lib/dal.ts" switcher
+import 'server-only'
+
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export const verifySession = cache(async () => {
+ const cookie = cookies().get('session')?.value
+ const session = await decrypt(cookie)
+
+ if (!session?.userId) {
+ redirect('/login')
}
- // Check if the user has the 'admin' role
- if (session.user.role !== 'admin') {
- return new Response(null, { status: 403 }) // User is authenticated but does not have the right permissions
+ return { isAuth: true, userId: session.userId }
+})
+```
+
+```js filename="app/lib/dal.js" switcher
+import 'server-only'
+
+import { cookies } from 'next/headers'
+import { decrypt } from '@/app/lib/session'
+
+export const verifySession = cache(async () => {
+ const cookie = cookies().get('session').value
+ const session = await decrypt(cookie)
+
+ if (!session.userId) {
+ redirect('/login')
}
- // Data fetching for authorized users
-}
+ return { isAuth: true, userId: session.userId }
+})
```
-```js filename="app/api/route.js" switcher
-export async function GET() {
- // User authentication and role verification
- const session = await getSession()
+You can then invoke the `verifySession()` function in your data requests and Server Actions before performing any operations:
- // Check if the user is authenticated
- if (!session) {
- return new Response(null, { status: 401 }) // User is not authenticated
+```tsx filename="app/lib/dal.ts" switcher
+// ...
+
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ try {
+ const data = await db.query.users.findMany({
+ where: eq(users.id, session.userId),
+ // Explicitly return the columns you need rather than the whole user object
+ columns: {
+ id: true,
+ name: true,
+ email: true,
+ },
+ })
+
+ const user = data[0]
+
+ return user
+ } catch (error) {
+ console.log('Failed to fetch user')
+ return null
}
+})
+```
- // Check if the user has the 'admin' role
- if (session.user.role !== 'admin') {
- return new Response(null, { status: 403 }) // User is authenticated but does not have the right permissions
+```jsx filename="app/lib/dal.js" switcher
+// ...
+
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
+
+ try {
+ const data = await db.query.users.findMany({
+ where: eq(users.id, session.userId),
+ // Explicitly return the columns you need rather than the whole user object
+ columns: {
+ id: true,
+ name: true,
+ email: true,
+ },
+ })
+
+ const user = data[0]
+
+ return user
+ } catch (error) {
+ console.log('Failed to fetch user')
+ return null
}
+})
+```
+
+> **Tip**:
+>
+> - A DAL can be used for runtime personalized data. However, for static routes that share data between users, data will be fetched at build time and not request time. Use [Middleware](#optimistic-checks-with-middleware-optional) to protect static routes.
+> - For secure checks, you can check if the session is valid by comparing the session ID with your database. Use React's [cache](https://react.dev/reference/react/cache) function to avoid unnecessary duplicate requests to the database during a render pass.
+> - You may wish to consolidate related data requests in a JavaScript class that runs `verifySession()` before any methods.
+
+### Using Data Transfer Objects (DTO)
+
+When retrieving data, it's recommended you return only the necessary data that will be used in your application, and not entire objects. For example, if you're fetching user data, you might only return the user's ID, name, and username, rather than the entire user object which could contain passwords, phone numbers, etc.
+
+However, if you have no control over the data structure, or are working in a team where you want to avoid whole objects being passed to the client, you can use strategies such as specifying what fields are ok to be exposed to the client.
+
+```tsx filename="app/lib/dto.ts" switcher
+import 'server-only'
+import { getUser } from '@/app/lib/dal'
+
+function canSeeUsername(viewer: User) {
+ return true
+}
+
+function canSeePhoneNumber(viewer: User, team: string) {
+ return viewer.isAdmin || team === viewer.team
+}
+
+export async function getProfileDTO(slug: string) {
+ const data = await db.query.users.findMany({
+ where: eq(users.slug, slug),
+ // Return specific columns here
+ })
+ const user = data[0]
+
+ const currentUser = await getUser(user.id)
- // Data fetching for authorized users
+ // Or return only what's specific to the query here
+ return {
+ username: canSeeUsername(currentUser) ? user.username : null,
+ phonenumber: canSeePhoneNumber(currentUser, user.team)
+ ? user.phonenumber
+ : null,
+ }
}
```
-This example demonstrates a Route Handler with a two-tier security check for authentication and authorization. It first checks for an active session, and then verifies if the logged-in user is an 'admin'. This approach ensures secure access, limited to authenticated and authorized users, maintaining robust security for request processing.
+```js filename="app/lib/dto.js" switcher
+import 'server-only'
+import { getUser } from '@/app/lib/dal'
-### Authorization Using Server Components
+function canSeeUsername(viewer) {
+ return true
+}
-[Server Components](/docs/app/building-your-application/rendering/server-components) in Next.js are designed for server-side execution and offer a secure environment for integrating complex logic like authorization. They enable direct access to back-end resources, optimizing performance for data-heavy tasks and enhancing security for sensitive operations.
+function canSeePhoneNumber(viewer, team) {
+ return viewer.isAdmin || team === viewer.team
+}
+
+export async function getProfileDTO(slug) {
+ const data = await db.query.users.findMany({
+ where: eq(users.slug, slug),
+ // Return specific columns here
+ })
+ const user = data[0]
+
+ const currentUser = await getUser(user.id)
-In Server Components, a common practice is to conditionally render UI elements based on the user's role. This approach enhances user experience and security by ensuring users only access content they are authorized to view.
+ // Or return only what's specific to the query here
+ return {
+ username: canSeeUsername(currentUser) ? user.username : null,
+ phonenumber: canSeePhoneNumber(currentUser, user.team)
+ ? user.phonenumber
+ : null,
+ }
+}
+```
-**Example:**
+By centralizing your data requests and authorization logic in a DAL and using DTOs, you can ensure that all data requests are secure and consistent, making it it easier to maintain, audit, and debug as your application scales.
+
+> **Good to know**:
+>
+> - There are a couple of different ways you can define a DTO, from using `toJSON()`, to individual functions like the example above, to JS classes. Since these are JavaScript patterns and not a React or Next.js feature, we recommend doing some research to find the best pattern for your application.
+> - Learn more about security best practices in our [Security in Next.js article](/blog/security-nextjs-server-components-actions).
+
+### Server Components
+
+You can do auth checks and use the [`redirect()`](/docs/app/api-reference/functions/redirect) API in [Server Components](/docs/app/building-your-application/rendering/server-components). This is useful for role-based access. For example, to conditionally render components based on the user's role:
```tsx filename="app/dashboard/page.tsx" switcher
-export default async function Dashboard() {
- const session = await getSession()
+import { verifySession } from '@/app/lib/dal'
+
+export default function Dashboard() {
+ const session = await verifySession()
const userRole = session?.user?.role // Assuming 'role' is part of the session object
if (userRole === 'admin') {
- return // Component for admin users
+ return
} else if (userRole === 'user') {
- return // Component for regular users
+ return
} else {
- return // Component shown for unauthorized access
+ redirect('/login')
}
}
```
```jsx filename="app/dashboard/page.jsx" switcher
+import { verifySession } from '@/app/lib/dal'
+
export default function Dashboard() {
- const session = await getSession()
- const userRole = session?.user?.role // Assuming 'role' is part of the session object
+ const session = await verifySession()
+ const userRole = session.role // Assuming 'role' is part of the session object
if (userRole === 'admin') {
- return // Component for admin users
+ return
} else if (userRole === 'user') {
- return // Component for regular users
+ return
} else {
- return // Component shown for unauthorized access
+ redirect('/login')
}
}
```
-In this example, the Dashboard component renders different UIs for 'admin', 'user', and unauthorized roles. This pattern ensures that each user interacts only with components appropriate to their role, enhancing both security and user experience.
+In the example, we use the `verifySession()` function from our DAL to check for 'admin', 'user', and unauthorized roles. This pattern ensures that each user interacts only with components appropriate to their role.
-
+#### Layouts and auth checks
-### Best Practices
+Due to [Partial Rendering](/docs/app/building-your-application/routing/linking-and-navigating#4-partial-rendering), be cautious when doing checks in [Layouts](/docs/app/building-your-application/routing/pages-and-layouts) as these don't re-render on navigation. Instead, you should do the checks close to your data source or the component that'll be conditionally rendered.
-- **Secure Session Management**: Prioritize the security of session data to prevent unauthorized access and data breaches. Use encryption and secure storage practices.
-- **Dynamic Role Management**: Use a flexible system for user roles to easily adjust to changes in permissions and roles, avoiding hardcoded roles.
-- **Security-First Approach**: In all aspects of authorization logic, prioritize security to safeguard user data and maintain the integrity of your application. This includes thorough testing and considering potential security vulnerabilities.
+For example, consider a shared layout that fetches the user data and displays the user image in a nav. Instead of doing the auth check in the layout, you should fetch the user data (`getUser()`) in the layout and do the auth check in your DAL.
-## Session Management
+```tsx filename="app/layout.tsx" switcher
+export default async function Layout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const user = await getUser();
+
+ return (
+ // ...
+ )
+}
+```
-Session management involves tracking and managing a user's interaction with the application over time, ensuring that their authenticated state is preserved across different parts of the application.
+```jsx filename="app/layout.js" switcher
+export default async function Layout({ children }) {
+ const user = await getUser();
-This prevents the need for repeated logins, enhancing both security and user convenience. There are two primary methods used for session management: cookie-based and database sessions.
+ return (
+ // ...
+ )
+}
+```
-### Cookie-Based Sessions
+This guarantees that wherever `getUser()` is called within your application, the auth check is performed.
-> **🎥 Watch:** Learn more about cookie-based sessions and authentication with Next.js → [YouTube (11 minutes)](https://www.youtube.com/watch?v=DJvM2lSPn6w).
+```ts filename="app/lib/dal.ts" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
-Cookie-based sessions manage user data by storing encrypted session information directly in browser cookies. Upon user login, this encrypted data is stored in the cookie. Each subsequent server request includes this cookie, minimizing the need for repeated server queries and enhancing client-side efficiency.
+ // Get user ID from session and fetch data
+})
+```
-However, this method requires careful encryption to protect sensitive data, as cookies are susceptible to client-side security risks. Encrypting session data in cookies is key to safeguarding user information from unauthorized access. It ensures that even if a cookie is stolen, the data inside remains unreadable.
+```js filename="app/lib/dal.js" switcher
+export const getUser = cache(async () => {
+ const session = await verifySession()
+ if (!session) return null
-Additionally, while individual cookies are limited in size (typically around 4KB), techniques like cookie-chunking can overcome this limitation by dividing large session data into multiple cookies.
+ // Get user ID from session and fetch data
+})
+```
-Setting a cookie in a Next.js project might look something like this:
+> **Good to know:**
+>
+> - A common pattern in SPAs is to `return null` in a layout or a top-level component if a user is not authorized. Since Next.js applications have multiple entry points, this pattern is **not recommended** since will not prevent nested layouts and pages and Server Actions from being accessed.
-**Setting a cookie on the server:**
+#### Server Actions
-
+Treat [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation.
-```ts filename="pages/api/login.ts" switcher
-import { serialize } from 'cookie'
-import type { NextApiRequest, NextApiResponse } from 'next'
+In the example below, we check the user's role before allowing the action to proceed:
-export default function handler(req: NextApiRequest, res: NextApiResponse) {
- const sessionData = req.body
- const encryptedSessionData = encrypt(sessionData)
+```ts filename="app/lib/actions.ts" switcher
+'use server'
+import { verifySession } from '@/app/lib/dal'
- const cookie = serialize('session', encryptedSessionData, {
- httpOnly: true,
- secure: process.env.NODE_ENV === 'production',
- maxAge: 60 * 60 * 24 * 7, // One week
- path: '/',
- })
- res.setHeader('Set-Cookie', cookie)
- res.status(200).json({ message: 'Successfully set cookie!' })
+export async function serverAction(formData: FormData) {
+ const session = await verifySession()
+ const userRole = session?.user?.role
+
+ // Return early if user is not authorized to perform the action
+ if (userRole !== 'admin') {
+ return null
+ }
+
+ // Proceed with the action for authorized users
}
```
-```js filename="pages/api/login.js" switcher
-import { serialize } from 'cookie'
+```js filename="app/lib/actions.js" switcher
+'use server'
+import { verifySession } from '@/app/lib/dal'
-export default function handler(req, res) {
- const sessionData = req.body
- const encryptedSessionData = encrypt(sessionData)
+export async function serverAction() {
+ const session = await verifySession()
+ const userRole = session.user.role
- const cookie = serialize('session', encryptedSessionData, {
- httpOnly: true,
- secure: process.env.NODE_ENV === 'production',
- maxAge: 60 * 60 * 24 * 7, // One week
- path: '/',
- })
- res.setHeader('Set-Cookie', cookie)
- res.status(200).json({ message: 'Successfully set cookie!' })
+ // Return early if user is not authorized to perform the action
+ if (userRole !== 'admin') {
+ return null
+ }
+
+ // Proceed with the action for authorized users
}
```
-
+#### Route Handlers
-
+Treat [Route Handlers](/docs/app/building-your-application/routing/route-handlers) with the same security considerations as public-facing API endpoints, and verify if the user is allowed to access the the Route Handler.
-```ts filename="app/actions.ts" switcher
-'use server'
+Here's an example of securing a Route Handler:
-import { cookies } from 'next/headers'
+```ts filename="app/api/route.ts" switcher
+import { verifySession } from '@/app/lib/dal'
-export async function handleLogin(sessionData) {
- const encryptedSessionData = encrypt(sessionData) // Encrypt your session data
- cookies().set('session', encryptedSessionData, {
- httpOnly: true,
- secure: process.env.NODE_ENV === 'production',
- maxAge: 60 * 60 * 24 * 7, // One week
- path: '/',
- })
- // Redirect or handle the response after setting the cookie
+export async function GET() {
+ // User authentication and role verification
+ const session = await verifySession()
+
+ // Check if the user is authenticated
+ if (!session) {
+ // User is not authenticated
+ return new Response(null, { status: 401 })
+ }
+
+ // Check if the user has the 'admin' role
+ if (session.user.role !== 'admin') {
+ // User is authenticated but does not have the right permissions
+ return new Response(null, { status: 403 })
+ }
+
+ // Continue for authorized users
}
```
-```js filename="app/actions.js" switcher
-'use server'
+```js filename="app/api/route.js" switcher
+import { verifySession } from '@/app/lib/dal'
-import { cookies } from 'next/headers'
+export async function GET() {
+ // User authentication and role verification
+ const session = await verifySession()
-export async function handleLogin(sessionData) {
- const encryptedSessionData = encrypt(sessionData) // Encrypt your session data
- cookies().set('session', encryptedSessionData, {
- httpOnly: true,
- secure: process.env.NODE_ENV === 'production',
- maxAge: 60 * 60 * 24 * 7, // One week
- path: '/',
- })
- // Redirect or handle the response after setting the cookie
+ // Check if the user is authenticated
+ if (!session) {
+ // User is not authenticated
+ return new Response(null, { status: 401 })
+ }
+
+ // Check if the user has the 'admin' role
+ if (session.user.role !== 'admin') {
+ // User is authenticated but does not have the right permissions
+ return new Response(null, { status: 403 })
+ }
+
+ // Continue for authorized users
}
```
-**Accessing the session data stored in the cookie in a server component:**
+The example above demonstrates a Route Handler with a two-tier security check. It first checks for an active session, and then verifies if the logged-in user is an 'admin'.
-```tsx filename="app/page.tsx" switcher
-import { cookies } from 'next/headers'
+## Context Providers
+
+Using context providers for auth work due to [interleaving](/docs/app/building-your-application/rendering/composition-patterns#interleaving-server-and-client-components). However, React `context` is not supported in Server Components, making them only applicable to Client Components.
+
+This works, however, any children Server Components will be rendered on the server first, and will not have access to the context provider’s session data:
-export async function getSessionData(req) {
- const encryptedSessionData = cookies().get('session')?.value
- return encryptedSessionData ? JSON.parse(decrypt(encryptedSessionData)) : null
+```tsx filename="app/layout.ts" switcher
+import { ContextProvider } from 'auth-lib'
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ )
}
```
-```jsx filename="app/page.jsx" switcher
-import { cookies } from 'next/headers'
+```tsx filename="app/layout.ts" switcher
+import { ContextProvider } from 'auth-lib'
-export async function getSessionData(req) {
- const encryptedSessionData = cookies().get('session')?.value
- return encryptedSessionData ? JSON.parse(decrypt(encryptedSessionData)) : null
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ )
}
```
-
+```tsx filename="app/ui/profile.ts switcher
+"use client";
-### Database Sessions
+import { useSession } from "auth-lib";
-Database session management involves storing session data on the server, with the user's browser only receiving a session ID. This ID references the session data stored server-side, without containing the data itself. This method enhances security, as it keeps sensitive session data away from the client-side environment, reducing the risk of exposure to client-side attacks. Database sessions are also more scalable, accommodating larger data storage needs.
+export default function Profile() {
+ const { userId } = useSession();
+ const { data } = useSWR(`/api/user/${userId}`, fetcher)
-However, this approach has its tradeoffs. It can increase performance overhead due to the need for database lookups at each user interaction. Strategies like session data caching can help mitigate this. Additionally, reliance on the database means that session management is as reliable as the database's performance and availability.
+ return (
+ // ...
+ );
+}
+```
-Here's a simplified example of implementing database sessions in a Next.js application:
+```jsx filename="app/ui/profile.js switcher
+"use client";
-**Creating a Session on the Server**:
+import { useSession } from "auth-lib";
+
+export default function Profile() {
+ const { userId } = useSession();
+ const { data } = useSWR(`/api/user/${userId}`, fetcher)
+
+ return (
+ // ...
+ );
+}
+```
+
+If session data is needed in Client Components (e.g. for client-side data fetching),use React’s [`taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue) API to prevent sensitive session data from being exposed to the client.
+
+
-```ts filename="pages/api/create-session.ts" switcher
-import db from '../../lib/db'
+### Creating a Data Access Layer (DAL)
+
+#### Protecting API Routes
+
+API Routes in Next.js are essential for handling server-side logic and data management. It's crucial to secure these routes to ensure that only authorized users can access specific functionalities. This typically involves verifying the user's authentication status and their role-based permissions.
+
+Here's an example of securing an API Route:
+
+```ts filename="pages/api/route.ts" switcher
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
- try {
- const user = req.body
- const sessionId = generateSessionId()
- await db.insertSession({
- sessionId,
- userId: user.id,
- createdAt: new Date(),
- })
+ const session = await getSession(req)
- res.status(200).json({ sessionId })
- } catch (error) {
- res.status(500).json({ error: 'Internal Server Error' })
+ // Check if the user is authenticated
+ if (!session) {
+ res.status(401).json({
+ error: 'User is not authenticated',
+ })
+ return
}
-}
-```
-```js filename="pages/api/create-session.js" switcher
-import db from '../../lib/db'
-
-export default async function handler(req, res) {
- try {
- const user = req.body
- const sessionId = generateSessionId()
- await db.insertSession({
- sessionId,
- userId: user.id,
- createdAt: new Date(),
+ // Check if the user has the 'admin' role
+ if (session.user.role !== 'admin') {
+ res.status(401).json({
+ error: 'Unauthorized access: User does not have admin privileges.',
})
-
- res.status(200).json({ sessionId })
- } catch (error) {
- res.status(500).json({ error: 'Internal Server Error' })
+ return
}
-}
-```
-
-
-
-
-
-```js
-import db from './lib/db'
-export async function createSession(user) {
- const sessionId = generateSessionId() // Generate a unique session ID
- await db.insertSession({ sessionId, userId: user.id, createdAt: new Date() })
- return sessionId
+ // Proceed with the route for authorized users
+ // ... implementation of the API Route
}
```
-**Retrieving a Session in Middleware or Server-Side Logic**:
+```js filename="pages/api/route.js" switcher
+export default async function handler(req, res) {
+ const session = await getSession(req)
-```js
-import { cookies } from 'next/headers'
-import db from './lib/db'
+ // Check if the user is authenticated
+ if (!session) {
+ res.status(401).json({
+ error: 'User is not authenticated',
+ })
+ return
+ }
+
+ // Check if the user has the 'admin' role
+ if (session.user.role !== 'admin') {
+ res.status(401).json({
+ error: 'Unauthorized access: User does not have admin privileges.',
+ })
+ return
+ }
-export async function getSession() {
- const sessionId = cookies().get('sessionId')?.value
- return sessionId ? await db.findSession(sessionId) : null
+ // Proceed with the route for authorized users
+ // ... implementation of the API Route
}
```
-
-
-### Selecting Session Management in Next.js
-
-Deciding between cookie-based and database sessions in Next.js depends on your application's needs. Cookie-based sessions are simpler and suit smaller applications with lower server load but may offer less security. Database sessions, while more complex, provide better security and scalability, ideal for larger, data-sensitive applications.
-
-With [authentication solutions](#examples) such as [NextAuth.js](https://authjs.dev/guides/upgrade-to-v5), session management becomes more efficient, using either cookies or database storage. This automation simplifies the development process, but it's important to understand the session management method used by your chosen solution. Ensure it aligns with your application's security and performance requirements.
+This example demonstrates an API Route with a two-tier security check for authentication and authorization. It first checks for an active session, and then verifies if the logged-in user is an 'admin'. This approach ensures secure access, limited to authenticated and authorized users, maintaining robust security for request processing.
-Regardless of your choice, prioritize security in your session management strategy. For cookie-based sessions, using secure and HTTP-only cookies is crucial to protect session data. For database sessions, regular backups and secure handling of session data are essential. Implementing session expiry and cleanup mechanisms is vital in both approaches to prevent unauthorized access and maintain application performance and reliability.
+
-## Examples
+## Resources
-Here are authentication solutions compatible with Next.js, please refer to the quickstart guides below to learn how to configure them in your Next.js application:
+Now that you've learned about authentication in Next.js, here are Next.js-compatible libraries and resources to help you implement secure authentication and session management:
-{/* TODO: Change link to authjs.dev when new documentation is ready */}
+### Auth Libraries
- [Auth0](https://auth0.com/docs/quickstart/webapp/nextjs/01-login)
- [Clerk](https://clerk.com/docs/quickstarts/nextjs)
- [Kinde](https://kinde.com/docs/developer-tools/nextjs-sdk)
- [Lucia](https://lucia-auth.com/getting-started/nextjs-app)
-- [NextAuth.js](https://authjs.dev/guides/upgrade-to-v5)
+- [NextAuth.js](https://authjs.dev/getting-started/installation?framework=next.js)
- [Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)
- [Stytch](https://stytch.com/docs/guides/quickstarts/nextjs)
+
+## Session Management Libraries
+
- [Iron Session](https://github.com/vvo/iron-session)
+- [Jose](https://github.com/panva/jose)
## Further Reading
To continue learning about authentication and security, check out the following resources:
+- [How to think about security in Next.js](/blog/security-nextjs-server-components-actions)
- [Understanding XSS Attacks](https://vercel.com/guides/understanding-xss-attacks)
- [Understanding CSRF Attacks](https://vercel.com/guides/understanding-csrf-attacks)
+- [The Copenhagen Book](https://thecopenhagenbook.com/)
diff --git a/docs/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx b/docs/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx
index b55321180cc8e..c3137be5bd895 100644
--- a/docs/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx
+++ b/docs/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx
@@ -83,9 +83,6 @@ The easiest way to generate an icon is to use the [`ImageResponse`](/docs/app/ap
```tsx filename="app/icon.tsx" switcher
import { ImageResponse } from 'next/og'
-// Route segment config
-export const runtime = 'edge'
-
// Image metadata
export const size = {
width: 32,
@@ -126,9 +123,6 @@ export default function Icon() {
```jsx filename="app/icon.js" switcher
import { ImageResponse } from 'next/og'
-// Route segment config
-export const runtime = 'edge'
-
// Image metadata
export const size = {
width: 32,
@@ -258,25 +252,6 @@ export default function Icon() {}
`icon` and `apple-icon` are specialized [Route Handlers](/docs/app/building-your-application/routing/route-handlers) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
-| Option | Type | Default |
-| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------- |
-| [`dynamic`](/docs/app/api-reference/file-conventions/route-segment-config#dynamic) | `'auto' \| 'force-dynamic' \| 'error' \| 'force-static'` | `'auto'` |
-| [`revalidate`](/docs/app/api-reference/file-conventions/route-segment-config#revalidate) | `false \| 'force-cache' \| 0 \| number` | `false` |
-| [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config#runtime) | `'nodejs' \| 'edge'` | `'nodejs'` |
-| [`preferredRegion`](/docs/app/api-reference/file-conventions/route-segment-config#preferredregion) | `'auto' \| 'global' \| 'home' \| string \| string[]` | `'auto'` |
-
-```tsx filename="app/icon.tsx" switcher
-export const runtime = 'edge'
-
-export default function Icon() {}
-```
-
-```jsx filename="app/icon.js" switcher
-export const runtime = 'edge'
-
-export default function Icon() {}
-```
-
## Version History
| Version | Changes |
diff --git a/docs/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx b/docs/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx
index fb6a439454333..9905874b54d44 100644
--- a/docs/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx
+++ b/docs/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx
@@ -92,9 +92,6 @@ The easiest way to generate an image is to use the [ImageResponse](/docs/app/api
```tsx filename="app/about/opengraph-image.tsx" switcher
import { ImageResponse } from 'next/og'
-// Route segment config
-export const runtime = 'edge'
-
// Image metadata
export const alt = 'About Acme'
export const size = {
@@ -149,9 +146,6 @@ export default async function Image() {
```jsx filename="app/about/opengraph-image.js" switcher
import { ImageResponse } from 'next/og'
-// Route segment config
-export const runtime = 'edge'
-
// Image metadata
export const alt = 'About Acme'
export const size = {
@@ -313,25 +307,6 @@ export default function Image() {}
`opengraph-image` and `twitter-image` are specialized [Route Handlers](/docs/app/building-your-application/routing/route-handlers) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
-| Option | Type | Default |
-| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ---------- |
-| [`dynamic`](/docs/app/api-reference/file-conventions/route-segment-config#dynamic) | `'auto' \| 'force-dynamic' \| 'error' \| 'force-static'` | `'auto'` |
-| [`revalidate`](/docs/app/api-reference/file-conventions/route-segment-config#revalidate) | `false \| 'force-cache' \| 0 \| number` | `false` |
-| [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config#runtime) | `'nodejs' \| 'edge'` | `'nodejs'` |
-| [`preferredRegion`](/docs/app/api-reference/file-conventions/route-segment-config#preferredregion) | `'auto' \| 'global' \| 'home' \| string \| string[]` | `'auto'` |
-
-```tsx filename="app/opengraph-image.tsx" switcher
-export const runtime = 'edge'
-
-export default function Image() {}
-```
-
-```jsx filename="app/opengraph-image.js" switcher
-export const runtime = 'edge'
-
-export default function Image() {}
-```
-
### Examples
#### Using external data
@@ -344,8 +319,6 @@ This example uses the `params` object and external data to generate the image.
```tsx filename="app/posts/[slug]/opengraph-image.tsx" switcher
import { ImageResponse } from 'next/og'
-export const runtime = 'edge'
-
export const alt = 'About Acme'
export const size = {
width: 1200,
@@ -384,8 +357,6 @@ export default async function Image({ params }: { params: { slug: string } }) {
```jsx filename="app/posts/[slug]/opengraph-image.js" switcher
import { ImageResponse } from 'next/og'
-export const runtime = 'edge'
-
export const alt = 'About Acme'
export const size = {
width: 1200,
diff --git a/docs/02-app/02-api-reference/02-file-conventions/mdx-components.mdx b/docs/02-app/02-api-reference/02-file-conventions/mdx-components.mdx
new file mode 100644
index 0000000000000..5ebd8b817f9e5
--- /dev/null
+++ b/docs/02-app/02-api-reference/02-file-conventions/mdx-components.mdx
@@ -0,0 +1,71 @@
+---
+title: mdx-components.js
+description: API reference for the mdx-components.js file.
+related:
+ title: Learn more about MDX Components
+ links:
+ - app/building-your-application/configuring/mdx
+---
+
+The `mdx-components.js|tsx` file is **required** to use [`@next/mdx` with App Router](/docs/app/building-your-application/configuring/mdx) and will not work without it. Additionally, you can use it to [customize styles](/docs/app/building-your-application/configuring/mdx#using-custom-styles-and-components).
+
+Use the file `mdx-components.tsx` (or `.js`) in the root of your project to define MDX Components. For example, at the same level as `pages` or `app`, or inside `src` if applicable.
+
+```tsx filename="mdx-components.tsx" switcher
+import type { MDXComponents } from 'mdx/types'
+
+export function useMDXComponents(components: MDXComponents): MDXComponents {
+ return {
+ ...components,
+ }
+}
+```
+
+```js filename="mdx-components.js" switcher
+export function useMDXComponents(components) {
+ return {
+ ...components,
+ }
+}
+```
+
+## Exports
+
+### `useMDXComponents` function
+
+The file must export a single function, either as a default export or named `useMDXComponents`.
+
+```tsx filename="mdx-components.tsx" switcher
+import type { MDXComponents } from 'mdx/types'
+
+export function useMDXComponents(components: MDXComponents): MDXComponents {
+ return {
+ ...components,
+ }
+}
+```
+
+```js filename="mdx-components.js" switcher
+export function useMDXComponents(components) {
+ return {
+ ...components,
+ }
+}
+```
+
+## Params
+
+### `components`
+
+When defining MDX Components, the export function accepts a single parameter, `components`. This parameter is an instance of `MDXComponents`.
+
+- The key is the name of the HTML element to override.
+- The value is the component to render instead.
+
+> **Good to know**: Remember to pass all other components (i.e. `...components`) that do not have overrides.
+
+## Version History
+
+| Version | Changes |
+| --------- | -------------------- |
+| `v13.1.2` | MDX Components added |
diff --git a/docs/02-app/02-api-reference/02-file-conventions/middleware.mdx b/docs/02-app/02-api-reference/02-file-conventions/middleware.mdx
index 6202f49fa0946..802fbbe01f008 100644
--- a/docs/02-app/02-api-reference/02-file-conventions/middleware.mdx
+++ b/docs/02-app/02-api-reference/02-file-conventions/middleware.mdx
@@ -1,6 +1,6 @@
---
title: middleware.js
-description: API reference for the middleware.js file.
+description: API reference for the middleware.js file.
related:
title: Learn more about Middleware
links:
diff --git a/docs/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx b/docs/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx
index 4f6f58161e207..42bbade1e1d78 100644
--- a/docs/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx
+++ b/docs/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx
@@ -15,34 +15,6 @@ The Route Segment options allows you to configure the behavior of a [Page](/docs
| [`preferredRegion`](#preferredregion) | `'auto' \| 'global' \| 'home' \| string \| string[]` | `'auto'` |
| [`maxDuration`](#maxduration) | `number` | Set by deployment platform |
-```tsx filename="layout.tsx | page.tsx | route.ts" switcher
-export const dynamic = 'auto'
-export const dynamicParams = true
-export const revalidate = false
-export const fetchCache = 'auto'
-export const runtime = 'nodejs'
-export const preferredRegion = 'auto'
-export const maxDuration = 5
-
-export default function MyComponent() {}
-```
-
-```jsx filename="layout.js | page.js | route.js" switcher
-export const dynamic = 'auto'
-export const dynamicParams = true
-export const revalidate = false
-export const fetchCache = 'auto'
-export const runtime = 'nodejs'
-export const preferredRegion = 'auto'
-export const maxDuration = 5
-
-export default function MyComponent() {}
-```
-
-> **Good to know**:
->
-> - The values of the config options currently need be statically analyzable. For example `revalidate = 600` is valid, but `revalidate = 60 * 10` is not.
-
## Options
### `dynamic`
@@ -62,7 +34,11 @@ export const dynamic = 'auto'
> **Good to know**: The new model in the `app` directory favors granular caching control at the `fetch` request level over the binary all-or-nothing model of `getServerSideProps` and `getStaticProps` at the page-level in the `pages` directory. The `dynamic` option is a way to opt back in to the previous model as a convenience and provides a simpler migration path.
- **`'auto'`** (default): The default option to cache as much as possible without preventing any components from opting into dynamic behavior.
-- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to `getServerSideProps()` in the `pages` directory.
+- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/building-your-application/rendering/server-components#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to:
+
+ - `getServerSideProps()` in the `pages` directory.
+ - Setting the option of every `fetch()` request in a layout or page to `{ cache: 'no-store', next: { revalidate: 0 } }`.
+ - Setting the segment config to `export const fetchCache = 'force-no-store'`
- **`'error'`**: Force static rendering and cache the data of a layout or page by causing an error if any components use [dynamic functions](/docs/app/building-your-application/rendering/server-components#dynamic-functions) or uncached data. This option is equivalent to:
- `getStaticProps()` in the `pages` directory.
@@ -114,7 +90,10 @@ export const revalidate = false
- **`0`**: Ensure a layout or page is always [dynamically rendered](/docs/app/building-your-application/rendering/server-components#dynamic-rendering) even if no dynamic functions or uncached data fetches are discovered. This option changes the default of `fetch` requests that do not set a `cache` option to `'no-store'` but leaves `fetch` requests that opt into `'force-cache'` or use a positive `revalidate` as is.
- **`number`**: (in seconds) Set the default revalidation frequency of a layout or page to `n` seconds.
-> **Good to know**: The `revalidate` option is only available when using the [Node.js Runtime](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#nodejs-runtime). This means using the `revalidate` option with `runtime = 'edge'` will not work.
+> **Good to know**:
+>
+> - The revalidate value needs to be statically analyzable. For example `revalidate = 600` is valid, but `revalidate = 60 * 10` is not.
+> - The revalidate value is not available when using `runtime = 'edge'`.
#### Revalidation Frequency
@@ -164,6 +143,8 @@ export const fetchCache = 'auto'
### `runtime`
+We recommend using the Node.js runtime for rendering your application, and the Edge runtime for Middleware (only supported option).
+
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const runtime = 'nodejs'
// 'nodejs' | 'edge'
@@ -177,7 +158,7 @@ export const runtime = 'nodejs'
- **`'nodejs'`** (default)
- **`'edge'`**
-Learn more about the [Edge and Node.js runtimes](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes).
+Learn more about the [different runtimes](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes).
### `preferredRegion`
diff --git a/docs/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx b/docs/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx
index 74103b6be4c14..2ed2d08f2bee6 100644
--- a/docs/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx
+++ b/docs/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx
@@ -194,7 +194,8 @@ These metrics work in all browsers that support the [User Timing API](https://ca
## Usage on Vercel
-[Vercel Speed Insights](https://vercel.com/docs/concepts/speed-insights) are automatically configured on Vercel deployments, and don't require the use of `useReportWebVitals`. This hook is useful in local development, or if you're using a different analytics service.
+[Vercel Speed Insights](https://vercel.com/docs/speed-insights/quickstart) does not `useReportWebVitals`, but `@vercel/speed-insights` package instead.
+`useReportWebVitals` hook is useful in local development, or if you're using a different service for collecting Web Vitals.
## Sending results to external systems
diff --git a/docs/02-app/02-api-reference/05-next-config-js/images.mdx b/docs/02-app/02-api-reference/05-next-config-js/images.mdx
index 8c296d7c4d71f..b0f14494ca4ae 100644
--- a/docs/02-app/02-api-reference/05-next-config-js/images.mdx
+++ b/docs/02-app/02-api-reference/05-next-config-js/images.mdx
@@ -61,10 +61,11 @@ To learn more about configuring the behavior of the built-in [Image Optimization
- [Gumlet](#gumlet)
- [ImageEngine](#imageengine)
- [Imgix](#imgix)
-- [Thumbor](#thumbor)
+- [PixelBin](#pixelbin)
- [Sanity](#sanity)
- [Sirv](#sirv)
- [Supabase](#supabase)
+- [Thumbor](#thumbor)
### Akamai
@@ -172,13 +173,16 @@ export default function imgixLoader({ src, width, quality }) {
}
```
-### Thumbor
+### PixelBin
```js
-// Docs: https://thumbor.readthedocs.io/en/latest/
-export default function thumborLoader({ src, width, quality }) {
- const params = [`${width}x0`, `filters:quality(${quality || 75})`]
- return `https://example.com${params.join('/')}${src}`
+// Doc (Resize): https://www.pixelbin.io/docs/transformations/basic/resize/#width-w
+// Doc (Optimise): https://www.pixelbin.io/docs/optimizations/quality/#image-quality-when-delivering
+// Doc (Auto Format Delivery): https://www.pixelbin.io/docs/optimizations/format/#automatic-format-selection-with-f_auto-url-parameter
+export default function pixelBinLoader({ src, width, quality }) {
+ const name = ''
+ const opt = `t.resize(w:${width})~t.compress(q:${quality || 75})`
+ return `https://cdn.pixelbin.io/v2/${name}/${opt}/${src}?f_auto=true`
}
```
@@ -225,3 +229,13 @@ export default function supabaseLoader({ src, width, quality }) {
return url.href
}
```
+
+### Thumbor
+
+```js
+// Docs: https://thumbor.readthedocs.io/en/latest/
+export default function thumborLoader({ src, width, quality }) {
+ const params = [`${width}x0`, `filters:quality(${quality || 75})`]
+ return `https://example.com${params.join('/')}${src}`
+}
+```
diff --git a/docs/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx b/docs/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx
index d2265fd86c174..18235c40c8b33 100644
--- a/docs/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx
+++ b/docs/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx
@@ -1,6 +1,6 @@
---
title: optimizePackageImports
-description: API Reference for optmizedPackageImports Next.js Config Option
+description: API Reference for optimizePackageImports Next.js Config Option
---
{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
diff --git a/docs/02-app/02-api-reference/07-edge.mdx b/docs/02-app/02-api-reference/07-edge.mdx
index 3bed7bfb2bbb0..c4a7f0fc27fcb 100644
--- a/docs/02-app/02-api-reference/07-edge.mdx
+++ b/docs/02-app/02-api-reference/07-edge.mdx
@@ -5,7 +5,7 @@ description: API Reference for the Edge Runtime.
{/* The content of this doc is shared between the app and pages router. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
-The Next.js Edge Runtime is based on standard Web APIs, it supports the following APIs:
+The Next.js Edge Runtime is used for Middleware and supports the following APIs:
## Network APIs
@@ -146,11 +146,10 @@ The following JavaScript language features are disabled, and **will not work:**
| [`WebAssembly.instantiate`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) | Compiles and instantiates a WebAssembly module from a buffer source |
In rare cases, your code could contain (or import) some dynamic code evaluation statements which _can not be reached at runtime_ and which can not be removed by treeshaking.
-You can relax the check to allow specific files with your Middleware or Edge API Route exported configuration:
+You can relax the check to allow specific files with your Middleware configuration:
-```javascript
+```javascript filename="middleware.ts"
export const config = {
- runtime: 'edge', // for Edge API Routes only
unstable_allowDynamic: [
// allows a single file
'/lib/utilities.js',
diff --git a/docs/02-app/02-api-reference/08-next-cli.mdx b/docs/02-app/02-api-reference/08-next-cli.mdx
index 185c91e45396b..e33a96e7648c4 100644
--- a/docs/02-app/02-api-reference/08-next-cli.mdx
+++ b/docs/02-app/02-api-reference/08-next-cli.mdx
@@ -357,48 +357,46 @@ The output should look like this:
```bash filename="Terminal"
Usage: next lint [directory] [options]
-Runs ESLint for all files in the `/src`, `/app`, `/pages`, `/components`, and `/lib`
-directories. It also provides a guided setup to install any required dependencies if ESLint
-is not already configured in your application.
+Runs ESLint for all files in the `/src`, `/app`, `/pages`, `/components`, and `/lib` directories. It also
+provides a guided setup to install any required dependencies if ESLint is not already configured in your
+application.
Arguments:
- [directory] A base directory on which to lint the application.
- If no directory is provided, the current
- directory will be used.
+ [directory] A base directory on which to lint the application.
+ If no directory is provided, the current directory
+ will be used.
Options:
- -d, --dir, Include directory, or directories, to run ESLint.
- --file, Include file, or files, to run ESLint.
- --ext, [exts...] Specify JavaScript file extensions. (default:
- [".js", ".mjs", ".cjs", ".jsx", ".ts", ".mts", ".cts", ".ts
-x"])
- -c, --config, Uses this configuration file, overriding all other
- configuration options.
- --resolve-plugins-relative-to, Specify a directory where plugins should be
- resolved from.
- --strict Creates a `.eslintrc.json` file using the Next.js
- strict configuration.
- --rulesdir, Uses additional rules from this directory(s).
- --fix Automatically fix linting issues.
- --fix-type Specify the types of fixes to apply (e.g., problem,
- suggestion, layout).
- --ignore-path Specify a file to ignore.
- --no-ignore Disables the `--ignore-path` option.
- --quiet Reports errors only.
- --max-warnings [maxWarnings] Specify the number of warnings before triggering a
- non-zero exit code. (default: -1)
- -o, --output-file, Specify a file to write report to.
- -f, --format, Uses a specifc output format.
- --no-inline-config Prevents comments from changing config or rules.
- --report-unused-disable-directives Adds reprted errors for unused eslint-disable
- directives.
- --no-cache Disables caching.
- --cache-location, Specify a location for cache.
- --cache-strategy, [cacheStrategy] Specify a strategy to use for detecting changed
- files in the cache. (default: "metadata")
- --error-on-unmatched-pattern Reports errors when any file patterns are
- unmatched.
- -h, --help Displays this message.
+ -d, --dir, Include directory, or directories, to run ESLint.
+ --file, Include file, or files, to run ESLint.
+ --ext, [exts...] Specify JavaScript file extensions. (default:
+ [".js", ".mjs", ".cjs", ".jsx", ".ts", ".mts", ".cts", ".tsx"])
+ -c, --config, Uses this configuration file, overriding all other
+ configuration options.
+ --resolve-plugins-relative-to, Specify a directory where plugins should be resolved
+ from.
+ --strict Creates a `.eslintrc.json` file using the Next.js
+ strict configuration.
+ --rulesdir, Uses additional rules from this directory(s).
+ --fix Automatically fix linting issues.
+ --fix-type Specify the types of fixes to apply (e.g., problem,
+ suggestion, layout).
+ --ignore-path Specify a file to ignore.
+ --no-ignore Disables the `--ignore-path` option.
+ --quiet Reports errors only.
+ --max-warnings [maxWarnings] Specify the number of warnings before triggering a
+ non-zero exit code. (default: -1)
+ -o, --output-file, Specify a file to write report to.
+ -f, --format, Uses a specifc output format.
+ --no-inline-config Prevents comments from changing config or rules.
+ --report-unused-disable-directives-severity Specify severity level for unused eslint-disable
+ directives. (choices: "error", "off", "warn")
+ --no-cache Disables caching.
+ --cache-location, Specify a location for cache.
+ --cache-strategy, [cacheStrategy] Specify a strategy to use for detecting changed files
+ in the cache. (default: "metadata")
+ --error-on-unmatched-pattern Reports errors when any file patterns are unmatched.
+ -h, --help Displays this message.
```
If you have other directories that you would like to lint, you can specify them using the `--dir` flag:
diff --git a/docs/02-app/index.mdx b/docs/02-app/index.mdx
index 8419eb0f1ad6a..fe278243420a7 100644
--- a/docs/02-app/index.mdx
+++ b/docs/02-app/index.mdx
@@ -41,6 +41,7 @@ Here are some common authentication solutions that support the App Router:
- [Auth0](https://github.com/auth0/nextjs-auth0#app-router)
- [Stytch](https://stytch.com/docs/example-apps/frontend/nextjs)
- [Kinde](https://kinde.com/docs/developer-tools/nextjs-sdk/)
+- [WorkOS](https://workos.com/docs/user-management)
- Or manually handling sessions or JWTs
### How can I set cookies?
diff --git a/docs/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx b/docs/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx
index 05393f1d7cf62..50dde821c6ef7 100644
--- a/docs/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx
+++ b/docs/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx
@@ -58,7 +58,7 @@ The difference between **catch-all** and **optional catch-all** segments is that
| Route | Example URL | `params` |
| --------------------------- | ------------- | --------------------------- |
-| `pages/shop/[[...slug]].js` | `/shop` | `{ slug: [] }` |
+| `pages/shop/[[...slug]].js` | `/shop` | `{ slug: undefined }` |
| `pages/shop/[[...slug]].js` | `/shop/a` | `{ slug: ['a'] }` |
| `pages/shop/[[...slug]].js` | `/shop/a/b` | `{ slug: ['a', 'b'] }` |
| `pages/shop/[[...slug]].js` | `/shop/a/b/c` | `{ slug: ['a', 'b', 'c'] }` |
diff --git a/docs/03-pages/01-building-your-application/01-routing/10-internationalization.mdx b/docs/03-pages/01-building-your-application/01-routing/10-internationalization.mdx
index 9fb076881fcab..2d321603e652b 100644
--- a/docs/03-pages/01-building-your-application/01-routing/10-internationalization.mdx
+++ b/docs/03-pages/01-building-your-application/01-routing/10-internationalization.mdx
@@ -279,7 +279,7 @@ export default function IndexPage(props) {
## Leveraging the `NEXT_LOCALE` cookie
-Next.js supports overriding the accept-language header with a `NEXT_LOCALE=the-locale` cookie. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie when redirecting from `/` to the correct locale location.
+Next.js allows setting a `NEXT_LOCALE=the-locale` cookie, which takes priority over the accept-language header. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie when redirecting from `/` to the correct locale location.
For example, if a user prefers the locale `fr` in their accept-language header but a `NEXT_LOCALE=en` cookie is set the `en` locale when visiting `/` the user will be redirected to the `en` locale location until the cookie is removed or expired.
diff --git a/docs/03-pages/01-building-your-application/06-configuring/13-debugging.mdx b/docs/03-pages/01-building-your-application/06-configuring/13-debugging.mdx
index 3821c7dee3313..915fd735209c7 100644
--- a/docs/03-pages/01-building-your-application/06-configuring/13-debugging.mdx
+++ b/docs/03-pages/01-building-your-application/06-configuring/13-debugging.mdx
@@ -29,13 +29,17 @@ Create a file named `.vscode/launch.json` at the root of your project with the f
},
{
"name": "Next.js: debug full stack",
- "type": "node-terminal",
+ "type": "node",
"request": "launch",
- "command": "npm run dev",
+ "program": "${workspaceFolder}/node_modules/.bin/next",
+ "runtimeArgs": ["--inspect"],
+ "skipFiles": ["/**"],
"serverReadyAction": {
+ "action": "debugWithEdge",
+ "killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
- "action": "debugWithChrome"
+ "webRoot": "${workspaceFolder}"
}
}
]
diff --git a/docs/03-pages/02-api-reference/03-next-config-js/optimizePackageImports.mdx b/docs/03-pages/02-api-reference/03-next-config-js/optimizePackageImports.mdx
index 1aa47380bd8ff..ba2785f70a37a 100644
--- a/docs/03-pages/02-api-reference/03-next-config-js/optimizePackageImports.mdx
+++ b/docs/03-pages/02-api-reference/03-next-config-js/optimizePackageImports.mdx
@@ -1,7 +1,7 @@
---
title: optimizePackageImports
-description: API Reference for optmizedPackageImports Next.js Config Option
-source: app/api-reference/next-config-js/compress
+description: API Reference for optimizePackageImports Next.js Config Option
+source: app/api-reference/next-config-js/optimizePackageImports
---
{/* DO NOT EDIT. The content of this doc is generated from the source above. To edit the content of this page, navigate to the source page in your editor. You can use the `Content` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
diff --git a/docs/04-architecture/turbopack.mdx b/docs/04-architecture/turbopack.mdx
index 3d771592276cb..e36d9ba360e3f 100644
--- a/docs/04-architecture/turbopack.mdx
+++ b/docs/04-architecture/turbopack.mdx
@@ -54,3 +54,9 @@ These features are currently not supported:
- We are currently not planning to support Yarn PnP in Next.js with Turbopack.
- [`experimental.urlImports`](/docs/app/api-reference/next-config-js/urlImports)
- We are currently not planning to support `experimental.urlImports` in Next.js with Turbopack.
+
+## Generating Trace Files
+
+Trace files allow the Next.js team to investigate and improve performance metrics and memory usage. To generate a trace file, append `NEXT_TURBOPACK_TRACING=1` to the `next dev --turbo` command, this will generate a `.next/trace.log` file.
+
+When reporting issues related to Turbopack performance and memory usage, please include the trace file in your [GitHub](https://github.com/vercel/next.js) issue.
diff --git a/errors/edge-dynamic-code-evaluation.mdx b/errors/edge-dynamic-code-evaluation.mdx
index bb842f4181ddd..68d60f62a3752 100644
--- a/errors/edge-dynamic-code-evaluation.mdx
+++ b/errors/edge-dynamic-code-evaluation.mdx
@@ -1,10 +1,10 @@
---
-title: Dynamic code evaluation is not available in Middlewares or Edge API Routes
+title: Dynamic code evaluation is not available in Middleware
---
## Why This Error Occurred
-`eval()`, `new Function()` or compiling WASM binaries dynamically is not allowed in Middlewares or Edge API Routes.
+`eval()`, `new Function()` or compiling WASM binaries dynamically is not allowed in Middleware.
Specifically, the following APIs are not supported:
- `eval()`
@@ -31,11 +31,10 @@ export default async function middleware() {
```
In rare cases, your code could contain (or import) some dynamic code evaluation statements which _can not be reached at runtime_ and which can not be removed by tree-shaking.
-You can relax the check to allow specific files with your Middleware or Edge API Route exported [configuration](/docs/pages/api-reference/edge#unsupported-apis):
+You can relax the check to allow specific files with your Middleware [configuration](/docs/pages/api-reference/edge#unsupported-apis):
```tsx filename="pages/api/example.ts"
export const config = {
- runtime: 'edge', // for Edge API Routes only
unstable_allowDynamic: [
'/lib/utilities.js', // allows a single file
'/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module
diff --git a/examples/cms-sanity/.eslintignore b/examples/cms-sanity/.eslintignore
new file mode 100644
index 0000000000000..30764a1a839e2
--- /dev/null
+++ b/examples/cms-sanity/.eslintignore
@@ -0,0 +1,2 @@
+# Ignoring generated files
+./sanity.types.ts
diff --git a/examples/cms-sanity/.prettierignore b/examples/cms-sanity/.prettierignore
new file mode 100644
index 0000000000000..1509c4cdb7a18
--- /dev/null
+++ b/examples/cms-sanity/.prettierignore
@@ -0,0 +1,3 @@
+# Ignoring generated files
+./sanity.types.ts
+./schema.json
diff --git a/examples/cms-sanity/README.md b/examples/cms-sanity/README.md
index dd4009406773b..b3ff9a5a42fda 100644
--- a/examples/cms-sanity/README.md
+++ b/examples/cms-sanity/README.md
@@ -9,6 +9,7 @@ The Studio connects to Sanity Content Lake, which gives you hosted content APIs
## Features
- A performant, static blog with editable posts, authors, and site settings
+- TypeScript setup with [Sanity TypeGen](https://www.sanity.io/docs/sanity-typegen)
- A native and customizable authoring environment, accessible on `yourblog.com/studio`
- Real-time and collaborative content editing with fine-grained revision history
- Side-by-side instant content preview that works across your whole site
@@ -44,6 +45,12 @@ yarn create next-app --example cms-sanity next-sanity-blog
pnpm create next-app --example cms-sanity next-sanity-blog
```
+Whenever you edit a GROQ query you update the TypeScript types by running:
+
+```bash
+npm run typegen
+```
+
# Configuration
- [Step 1. Set up the environment](#step-1-set-up-the-environment)
diff --git a/examples/cms-sanity/app/(blog)/avatar.tsx b/examples/cms-sanity/app/(blog)/avatar.tsx
index 5e928c04ba8f3..f0942804aca9e 100644
--- a/examples/cms-sanity/app/(blog)/avatar.tsx
+++ b/examples/cms-sanity/app/(blog)/avatar.tsx
@@ -1,9 +1,14 @@
import { Image } from "next-sanity/image";
-import { Author } from "@/sanity/lib/queries";
+import type { Author } from "@/sanity.types";
import { urlForImage } from "@/sanity/lib/utils";
-export default function Avatar({ name, picture }: Author) {
+interface Props {
+ name: string;
+ picture: Exclude | null;
+}
+
+export default function Avatar({ name, picture }: Props) {
return (
{picture?.asset?._ref ? (
diff --git a/examples/cms-sanity/app/(blog)/layout.tsx b/examples/cms-sanity/app/(blog)/layout.tsx
index 9001e0f7e1089..7d934be6368c7 100644
--- a/examples/cms-sanity/app/(blog)/layout.tsx
+++ b/examples/cms-sanity/app/(blog)/layout.tsx
@@ -1,8 +1,12 @@
import "../globals.css";
import { SpeedInsights } from "@vercel/speed-insights/next";
-import { Metadata } from "next";
-import { PortableTextBlock, VisualEditing, toPlainText } from "next-sanity";
+import type { Metadata } from "next";
+import {
+ VisualEditing,
+ toPlainText,
+ type PortableTextBlock,
+} from "next-sanity";
import { Inter } from "next/font/google";
import { draftMode } from "next/headers";
import { Suspense } from "react";
@@ -10,13 +14,14 @@ import { Suspense } from "react";
import AlertBanner from "./alert-banner";
import PortableText from "./portable-text";
+import type { SettingsQueryResult } from "@/sanity.types";
import * as demo from "@/sanity/lib/demo";
import { sanityFetch } from "@/sanity/lib/fetch";
-import { SettingsQueryResponse, settingsQuery } from "@/sanity/lib/queries";
+import { settingsQuery } from "@/sanity/lib/queries";
import { resolveOpenGraphImage } from "@/sanity/lib/utils";
export async function generateMetadata(): Promise {
- const settings = await sanityFetch({
+ const settings = await sanityFetch({
query: settingsQuery,
// Metadata should never contain stega
stega: false,
@@ -53,10 +58,10 @@ const inter = Inter({
});
async function Footer() {
- const data = await sanityFetch({
+ const data = await sanityFetch({
query: settingsQuery,
});
- const footer = data?.footer || ([] as PortableTextBlock[]);
+ const footer = data?.footer || [];
return (