diff --git a/crates/next-core/src/next_edge/context.rs b/crates/next-core/src/next_edge/context.rs
index 7810e6cee125ff..4372d5184e4a30 100644
--- a/crates/next-core/src/next_edge/context.rs
+++ b/crates/next-core/src/next_edge/context.rs
@@ -241,7 +241,7 @@ pub async fn get_edge_chunking_context_with_client_assets(
)
.asset_base_path(Some(asset_prefix))
.default_url_behavior(UrlBehavior {
- suffix: AssetSuffix::Inferred,
+ suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
static_suffix: css_url_suffix.to_resolved().await?,
})
.minify_type(if *turbo_minify.await? {
@@ -329,7 +329,7 @@ pub async fn get_edge_chunking_context(
)
.default_url_behavior(UrlBehavior {
suffix: AssetSuffix::Inferred,
- static_suffix: css_url_suffix,
+ static_suffix: ResolvedVc::cell(None),
})
// Since one can't read files in edge directly, any asset need to be fetched
// instead. This special blob url is handled by the custom fetch
diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs
index 9094190edc1223..6dcedde7e5b6a6 100644
--- a/crates/next-core/src/next_server/context.rs
+++ b/crates/next-core/src/next_server/context.rs
@@ -1053,7 +1053,7 @@ pub async fn get_server_chunking_context_with_client_assets(
)
.default_url_behavior(UrlBehavior {
suffix: AssetSuffix::Inferred,
- static_suffix: css_url_suffix,
+ static_suffix: ResolvedVc::cell(None),
})
.minify_type(if *minify.await? {
MinifyType::Minify {
@@ -1152,7 +1152,7 @@ pub async fn get_server_chunking_context(
)
.default_url_behavior(UrlBehavior {
suffix: AssetSuffix::Inferred,
- static_suffix: css_url_suffix,
+ static_suffix: ResolvedVc::cell(None),
})
.minify_type(if *minify.await? {
MinifyType::Minify {
diff --git a/examples/with-docker-export-output/.dockerignore b/examples/with-docker-export-output/.dockerignore
new file mode 100644
index 00000000000000..5565861bf35092
--- /dev/null
+++ b/examples/with-docker-export-output/.dockerignore
@@ -0,0 +1,129 @@
+############################################################
+# Production-ready .dockerignore for a Next.js (Vercel-style) app
+# Keeps Docker builds fast, lean, and free of development files.
+############################################################
+
+# Dependencies (installed inside Docker, never copied)
+node_modules/
+.pnpm-store/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Next.js build outputs (always generated during `next build`)
+.next/
+out/
+dist/
+build/
+.vercel/
+
+# Tests and testing output (not needed in production images)
+coverage/
+.nyc_output/
+__tests__/
+__mocks__/
+jest/
+cypress/
+cypress/screenshots/
+cypress/videos/
+playwright-report/
+test-results/
+.vitest/
+*.test.ts
+*.test.tsx
+*.test.js
+*.test.jsx
+*.spec.ts
+*.spec.tsx
+*.spec.js
+*.spec.jsx
+
+
+# Local development and editor files
+.git/
+.gitignore
+.gitattributes
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+*.log
+
+# Environment variables (only commit template files)
+.env
+.env*.local
+.env.development
+.env.test
+.env.production.local
+
+# Docker configuration files (not needed inside build context)
+Dockerfile*
+.dockerignore
+docker-compose*.yml
+
+# Documentation
+*.md
+docs/
+
+# CI/CD configuration files
+.github/
+.gitlab-ci.yml
+.travis.yml
+.circleci/
+Jenkinsfile
+
+# Cache directories and temporary data
+.cache/
+.parcel-cache/
+.eslintcache
+.stylelintcache
+.turbo/
+.tmp/
+.temp/
+
+# TypeScript build metadata
+*.tsbuildinfo
+.tsbuildinfo
+
+# Sensitive or unnecessary configuration files
+*.pem
+.editorconfig
+.prettierrc*
+.eslintrc*
+.stylelintrc*
+.babelrc*
+*.iml
+*.ipr
+*.iws
+
+# OS-specific junk
+.DS_Store
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+Desktop.ini
+
+# AI/ML tool metadata and configs
+.cursor/
+.cursorrules
+.copilot/
+.copilotignore
+.github/copilot/
+.gemini/
+.anthropic/
+.kiro
+.claude
+
+# AI-generated temp files
+*.aider*
+*.copilot*
+*.chatgpt*
+*.claude*
+*.gemini*
+*.openai*
+*.anthropic*
diff --git a/examples/with-docker-export-output/.gitignore b/examples/with-docker-export-output/.gitignore
new file mode 100644
index 00000000000000..8777267507c0ee
--- /dev/null
+++ b/examples/with-docker-export-output/.gitignore
@@ -0,0 +1,40 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/examples/with-docker-export-output/Dockerfile b/examples/with-docker-export-output/Dockerfile
new file mode 100644
index 00000000000000..bcc13fd76f2bfe
--- /dev/null
+++ b/examples/with-docker-export-output/Dockerfile
@@ -0,0 +1,96 @@
+# ============================================
+# Stage 1: Dependencies Installation Stage
+# ============================================
+
+# IMPORTANT: Node.js and Nginxinc Image Version Maintenance
+# This Dockerfile uses Node.js 24.13.0-slim and Nginxinc image with alpine3.22, which was the latest LTS version at the time of writing.
+# To ensure security and compatibility, regularly validate and update the NODE_VERSION ARG and NGINXINC_IMAGE_TAG to the latest LTS versions.
+
+ARG NODE_VERSION=24.13.0-slim
+ARG NGINXINC_IMAGE_TAG=alpine3.22
+
+FROM node:${NODE_VERSION} AS dependencies
+
+# Set the working directory
+WORKDIR /app
+
+# Copy package-related files first to leverage Docker's caching mechanism
+COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
+
+# Install project dependencies with frozen lockfile for reproducible builds
+RUN --mount=type=cache,target=/root/.npm \
+ --mount=type=cache,target=/usr/local/share/.cache/yarn \
+ --mount=type=cache,target=/root/.local/share/pnpm/store \
+ if [ -f package-lock.json ]; then \
+ npm ci --no-audit --no-fund; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn install --frozen-lockfile --production=false; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm install --frozen-lockfile; \
+ else \
+ echo "No lockfile found." && exit 1; \
+ fi
+
+# ============================================
+# Stage 2: Build Next.js Application
+# ============================================
+
+FROM node:${NODE_VERSION} AS builder
+
+# Set the working directory
+WORKDIR /app
+
+# Copy project dependencies from dependencies stage
+COPY --from=dependencies /app/node_modules ./node_modules
+
+# Copy application source code
+COPY . .
+
+ENV NODE_ENV=production
+
+# Next.js collects completely anonymous telemetry data about general usage.
+# Learn more here: https://nextjs.org/telemetry
+# Uncomment the following line in case you want to disable telemetry during the build.
+# ENV NEXT_TELEMETRY_DISABLED=1
+
+# Build Next.js application
+RUN --mount=type=cache,target=/app/.next/cache \
+ if [ -f package-lock.json ]; then \
+ npm run build; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn build; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm build; \
+ else \
+ echo "No lockfile found." && exit 1; \
+ fi
+
+# =========================================
+# Stage 3: Serve Static Files with Nginx
+# =========================================
+
+FROM nginxinc/nginx-unprivileged:${NGINXINC_IMAGE_TAG} AS runner
+
+# Set the working directory
+WORKDIR /app
+
+# Next.js collects completely anonymous telemetry data about general usage.
+# Learn more here: https://nextjs.org/telemetry
+# Uncomment the following line in case you want to disable telemetry during the run time.
+# ENV NEXT_TELEMETRY_DISABLED=1
+
+# Copy custom Nginx config
+COPY nginx.conf /etc/nginx/nginx.conf
+
+# Copy the static build output from the build stage to Nginx's default HTML serving directory
+COPY --from=builder /app/out /usr/share/nginx/html
+
+# Non-root user for security best practices
+USER nginx
+
+# Expose port 8080 to allow HTTP traffic
+EXPOSE 8080
+
+# Start Nginx directly with custom config
+ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
+CMD ["-g", "daemon off;"]
diff --git a/examples/with-docker-export-output/Dockerfile.serve b/examples/with-docker-export-output/Dockerfile.serve
new file mode 100644
index 00000000000000..0913b6ef0bc2e3
--- /dev/null
+++ b/examples/with-docker-export-output/Dockerfile.serve
@@ -0,0 +1,97 @@
+# =========================================
+# Stage 1: Install Dependencies
+# =========================================
+
+# IMPORTANT: Node.js Version Maintenance
+# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing.
+# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version.
+ARG NODE_VERSION=24.13.0-slim
+
+FROM node:${NODE_VERSION} AS dependencies
+
+# Set the working directory
+WORKDIR /app
+
+# Copy package-related files first to leverage Docker's caching mechanism
+COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
+
+# Install project dependencies with frozen lockfile for reproducible builds
+RUN --mount=type=cache,target=/root/.npm \
+ --mount=type=cache,target=/usr/local/share/.cache/yarn \
+ --mount=type=cache,target=/root/.local/share/pnpm/store \
+ if [ -f package-lock.json ]; then \
+ npm ci --no-audit --no-fund; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn install --frozen-lockfile --production=false; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm install --frozen-lockfile; \
+ else \
+ echo "No lockfile found." && exit 1; \
+ fi
+
+# ============================================
+# Stage 2: Build Next.js Application
+# ============================================
+
+FROM node:${NODE_VERSION} AS builder
+
+# Set the working directory
+WORKDIR /app
+
+# Copy project dependencies from dependencies stage
+COPY --from=dependencies /app/node_modules ./node_modules
+
+# Copy application source code
+COPY . .
+
+ENV NODE_ENV=production
+
+# Next.js collects completely anonymous telemetry data about general usage.
+# Learn more here: https://nextjs.org/telemetry
+# Uncomment the following line in case you want to disable telemetry during the build.
+# ENV NEXT_TELEMETRY_DISABLED=1
+
+# Build Next.js application
+# Cache mount speeds up subsequent builds by persisting Next.js build cache
+RUN --mount=type=cache,target=/app/.next/cache \
+ if [ -f package-lock.json ]; then \
+ npm run build; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn build; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm build; \
+ else \
+ echo "No lockfile found." && exit 1; \
+ fi
+
+# =========================================
+# Stage 3: Serve Static Files with serve
+# =========================================
+
+FROM node:${NODE_VERSION} AS runner
+
+# Set the working directory
+WORKDIR /app
+
+# Install serve globally (pinned version for reproducibility, update to the latest version as needed)
+# Cache npm global installs to speed up builds
+RUN --mount=type=cache,target=/root/.npm \
+ npm install -g serve@14.2.5
+
+# Set the port for serve (default is 3000)
+ENV PORT=3000
+
+# Copy the static build output from the build stage
+COPY --from=builder --chown=node:node /app/out ./out
+
+# Use the built-in non-root user for security best practices
+USER node
+
+# Expose port 3000 to allow HTTP traffic
+EXPOSE 3000
+
+# Start serve to serve static files
+# -s: serve single-page application (SPA) mode
+# -l: listen on specified port
+# -n: no clipper (don't show file listing)
+CMD ["serve", "-s", "out", "-l", "3000", "-n"]
diff --git a/examples/with-docker-export-output/README.md b/examples/with-docker-export-output/README.md
new file mode 100644
index 00000000000000..07f25a3ce6cba1
--- /dev/null
+++ b/examples/with-docker-export-output/README.md
@@ -0,0 +1,222 @@
+# Next.js Docker Example
+
+A production-ready example demonstrating how to Dockerize Next.js applications using **static export** mode. This example showcases two different approaches for serving static Next.js sites: **Nginx** and **serve** package.
+
+## Features
+
+- ✅ Multi-stage Docker build for optimal image size
+- ✅ Static export: Fully static HTML/CSS/JavaScript site
+- ✅ Two serving options: Nginx (production-grade) and serve (simple Node.js server)
+- ✅ Security best practices (non-root user)
+- ✅ Slim/Alpine Linux base images for optimal compatibility and smaller size
+- ✅ BuildKit cache mounts for faster builds
+- ✅ Production-ready configuration with optimized Nginx settings
+- ✅ Docker Compose support for easy deployment
+
+## Prerequisites
+
+- [Docker](https://docs.docker.com/get-docker/)
+- [Node.js 20+](https://nodejs.org/) (for local development)
+
+## Quick Start with Docker
+
+### Option 1: Using Nginx (Recommended for Production)
+
+**Nginx** is ideal when you need:
+
+- Production-grade web server
+- Maximum performance and efficiency
+- Advanced caching and compression
+- Smaller Docker images (~50MB)
+- Industry-standard web server
+
+#### Using Docker Compose
+
+```bash
+docker compose up nextjs-static-export --build
+```
+
+**Access:** [http://localhost:8080](http://localhost:8080)
+
+#### Using Docker Build
+
+```bash
+docker build -t nextjs-static-export .
+docker run -p 8080:8080 nextjs-static-export
+```
+
+**Access:** [http://localhost:8080](http://localhost:8080)
+
+### Option 2: Using serve Package
+
+**serve** is ideal when you need:
+
+- Simple Node.js-based static file server
+- Quick development/testing deployments
+- Familiar Node.js ecosystem
+- Easy customization
+
+#### Using Docker Compose
+
+```bash
+# OR run with serve npm package
+docker compose up nextjs-static-export-with-serve --build
+```
+
+**Access:** [http://localhost:3000](http://localhost:3000)
+
+#### Using Docker Build
+
+```bash
+docker build -t nextjs-static-export-serve -f Dockerfile.serve .
+docker run -p 3000:3000 nextjs-static-export-serve
+```
+
+**Access:** [http://localhost:3000](http://localhost:3000)
+
+## Project Structure
+
+```
+nextjs-docker/
+├── app/ # Next.js App Router directory
+│ ├── layout.tsx # Root layout with metadata
+│ ├── page.tsx # Home page with example content
+│ └── globals.css # Global styles with Tailwind CSS v4
+├── public/ # Static assets
+│ └── next.svg # Next.js logo
+├── Dockerfile # Nginx-based Dockerfile (port 8080)
+├── Dockerfile.serve # serve-based Dockerfile (port 3000)
+├── compose.yml # Docker Compose with both services
+├── nginx.conf # Nginx configuration for static export
+├── next.config.ts # Next.js configuration (static export mode)
+├── postcss.config.js # PostCSS configuration for Tailwind CSS
+├── tsconfig.json # TypeScript configuration
+├── package.json # Dependencies and scripts
+└── README.md # This file
+```
+
+## Configuration
+
+### Next.js Static Export Mode
+
+The `next.config.ts` file is configured with `output: "export"`:
+
+```typescript
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ output: "export",
+ images: {
+ unoptimized: true, // Required for static export
+ },
+};
+
+export default nextConfig;
+```
+
+**Static export mode** generates a fully static HTML/CSS/JavaScript site that can be served by any static hosting service or web server. When enabled, Next.js generates an `out` directory containing all static files. This results in:
+
+- Smaller Docker image (~80MB with Nginx, ~350MB with serve)
+- Faster deployments (no Node.js runtime needed for Nginx)
+- Better CDN compatibility (pure static files)
+- Lower resource usage (Nginx uses minimal memory)
+
+**Note:** Static export has limitations:
+
+- No server-side rendering (SSR)
+- No API routes
+- Images must be unoptimized or use external image optimization
+
+Learn more about [Next.js static export](https://nextjs.org/docs/app/api-reference/next-config-js/output#static-export) in the official documentation.
+
+### Dockerfile Highlights
+
+#### Nginx-based (`Dockerfile`)
+
+- **Multi-stage build**: Separates dependency installation (`dependencies`), build (`builder`), and runtime (`runner`) stages
+- **Nginx server**: Uses `nginxinc/nginx-unprivileged:alpine3.22` for serving static files (~50MB final image)
+- **BuildKit cache mounts**: Speeds up builds by caching package manager stores and Next.js build cache
+- **Non-root user**: Runs as `nginx` user for security
+- **Production Nginx config**: Optimized with gzip compression, caching headers, and security best practices
+- **Port**: 8080
+
+#### serve-based (`Dockerfile.serve`)
+
+- **Multi-stage build**: Separates dependency installation (`dependencies`), build (`builder`), and runtime (`runner`) stages
+- **Node.js runtime**: Uses `node:24.13.0-slim` for running serve package
+- **serve package**: Uses `serve@14.2.5` for serving static files
+- **BuildKit cache mounts**: Speeds up builds by caching package manager stores and Next.js build cache
+- **Non-root user**: Runs as `node` user for security
+- **SPA mode**: Configured with single-page application support
+- **Port**: 3000
+
+**Node.js version maintenance**: Uses Node.js 24.13.0-slim (latest LTS at time of writing). Update the `NODE_VERSION` ARG to the latest LTS version for security updates.
+
+**Nginx image maintenance**: Uses `nginxinc/nginx-unprivileged:alpine3.22`. Update the `NGINXINC_IMAGE_TAG` ARG to the latest version for security updates.
+
+**Why Node.js slim image tag?**: The slim variant provides optimal compatibility with npm packages and native dependencies while maintaining a smaller image size (~226MB). Slim uses glibc (standard Linux), ensuring better compatibility than Alpine's musl libc, which can cause issues with some npm packages. This makes it ideal for public examples where reliability and compatibility are priorities.
+
+**When to use Alpine?**: Consider using `node:24.11.1-alpine` instead if:
+
+- **Image size is critical**: Alpine images are typically ~100MB smaller than slim variants (~110MB base vs ~226MB)
+- **Your dependencies are compatible**: Your npm packages don't require native binaries that depend on glibc
+- **You've tested thoroughly**: You've verified all your dependencies work correctly with musl libc
+- **Security-focused deployments**: Alpine's minimal attack surface can be beneficial for security-sensitive applications
+
+To switch to Alpine, simply change the `NODE_VERSION` ARG in the Dockerfile to `24.11.1-alpine`.
+
+**⚠️ Important - Version Maintenance**:
+
+- **Node.js**: This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. To ensure security and stay up-to-date, regularly check and update the `NODE_VERSION` ARG in the Dockerfile to the latest Node.js LTS version. Check the latest version at [Node.js official website](https://nodejs.org/) and browse available Node.js images on [Docker Hub](https://hub.docker.com/_/node).
+
+- **Nginx**: The Nginx Dockerfile uses `nginxinc/nginx-unprivileged:alpine3.22`. Regularly check and update the `NGINXINC_IMAGE_TAG` ARG to the latest version. Browse available Nginx images on [Docker Hub](https://hub.docker.com/r/nginxinc/nginx-unprivileged).
+
+- **serve package**: The serve Dockerfile uses `serve@14.2.5`. Update to the latest version as needed for bug fixes and features.
+
+### Package Manager Support
+
+Both Dockerfiles support multiple package managers:
+
+- **npm** (via `package-lock.json`)
+- **yarn** (via `yarn.lock`)
+- **pnpm** (via `pnpm-lock.yaml`)
+
+The Dockerfiles automatically detect which lockfile is present and use the appropriate package manager.
+
+## Deployment
+
+This example can be deployed to any container-based platform:
+
+- Google Cloud Run
+- AWS ECS/Fargate
+- Azure Container Instances
+- DigitalOcean App Platform
+- Any Kubernetes cluster
+- Vercel (for static export)
+
+### Choosing Between Nginx and serve
+
+**Use Nginx (`Dockerfile`)** when:
+
+- Deploying to production
+- Maximum performance is required
+- You need advanced caching and compression
+- Image size is a concern (~50MB vs ~300MB)
+- You want industry-standard web server
+
+**Use serve (`Dockerfile.serve`)** when:
+
+- Quick development/testing deployments
+- You prefer Node.js ecosystem
+- You need easy customization
+- Image size is not a concern
+
+## Learn More
+
+- [Next.js Documentation](https://nextjs.org/docs) - Comprehensive Next.js documentation
+- [Next.js Templates](https://vercel.com/templates?framework=next.js) - Browse and deploy Next.js templates
+- [Next.js Examples](https://github.com/vercel/next.js/tree/canary/examples) - Discover boilerplate example projects
+- [Deploy to Vercel](https://vercel.com/new) - Instantly deploy your Next.js site
+- [Learn Docker](https://docs.docker.com/get-started/) - Get started with Docker fundamentals, containerization, and deployment
+- [Docker Documentation](https://docs.docker.com/) - Comprehensive Docker documentation and reference guides
+- [React.js Docker Guide](https://docs.docker.com/language/nodejs/) - Official Docker guide for React.js applications following best practices for containerization
diff --git a/examples/with-docker-export-output/app/favicon.ico b/examples/with-docker-export-output/app/favicon.ico
new file mode 100644
index 00000000000000..718d6fea4835ec
Binary files /dev/null and b/examples/with-docker-export-output/app/favicon.ico differ
diff --git a/examples/with-docker-export-output/app/globals.css b/examples/with-docker-export-output/app/globals.css
new file mode 100644
index 00000000000000..a2dc41ecee5ec4
--- /dev/null
+++ b/examples/with-docker-export-output/app/globals.css
@@ -0,0 +1,26 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
diff --git a/examples/with-docker-export-output/app/layout.tsx b/examples/with-docker-export-output/app/layout.tsx
new file mode 100644
index 00000000000000..6f1a7d54cd53f1
--- /dev/null
+++ b/examples/with-docker-export-output/app/layout.tsx
@@ -0,0 +1,57 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+
+const geistSans = Geist({
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+ title: "Next.js Docker Example - Standalone & Static Export",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode and static export.",
+ keywords: [
+ "Next.js",
+ "Docker",
+ "standalone mode",
+ "static export",
+ "containerization",
+ "React",
+ "Node.js",
+ "Nginx",
+ ],
+ openGraph: {
+ title: "Next.js Docker Example - Standalone & Static Export",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode and static export.",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "Next.js Docker Example - Standalone & Static Export",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode and static export.",
+ },
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/examples/with-docker-export-output/app/page.tsx b/examples/with-docker-export-output/app/page.tsx
new file mode 100644
index 00000000000000..3c7a7096231ad0
--- /dev/null
+++ b/examples/with-docker-export-output/app/page.tsx
@@ -0,0 +1,235 @@
+import Link from "next/link";
+import Image from "next/image";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+ Static Export Mode
+
+
+ This example showcases Next.js static export mode, which creates a
+ fully static site served by Nginx. Perfect for blogs, docs, and
+ marketing sites.
+
+
+ Multi-stage Docker build for optimal image size
+ Production-ready Nginx configuration
+ Security best practices (non-root user)
+ BuildKit cache mounts for faster builds
+
+ Gzip compression & static asset caching with Nginx configuration
+
+
+
+
+
+
+ Quick Start
+
+
+
+
Build the image:
+
+ docker build -t nextjs-static-export .
+
+
+
+
Run the container:
+
+ docker run -p 8080:8080 nextjs-static-export
+
+
+
+
+ Or use Docker Compose:
+
+
+ docker compose up
+
+
+
+ Access at:
+
+ http://localhost:8080
+
+
+
+
+
+
+
+
+ Next.js Resources
+
+
+
+
+ Documentation →
+
+
+ Find in-depth information about Next.js features and API.
+
+
+
+
+
+ Templates →
+
+
+ Browse and deploy Next.js templates to get started quickly!
+
+
+
+
+
+ Examples →
+
+
+ Discover and deploy boilerplate example Next.js projects.
+
+
+
+
+
+ Deploy →
+
+
+ Instantly deploy your Next.js site to a public URL with Vercel.
+
+
+
+
+
+
+
+ Docker Resources
+
+
+
+
+ Learn Docker →
+
+
+ Get started with Docker! Learn fundamentals, containerization,
+ and deployment.
+
+
+
+
+ Docker Docs →
+
+
+ Comprehensive Docker documentation and reference guides.
+
+
+
+
+
+ React.js Guide →
+
+
+ Official Docker guide for React.js applications following best
+ practices for containerization.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/with-docker-export-output/compose.yml b/examples/with-docker-export-output/compose.yml
new file mode 100644
index 00000000000000..7f1945f0f41aa6
--- /dev/null
+++ b/examples/with-docker-export-output/compose.yml
@@ -0,0 +1,27 @@
+services:
+ # Nginx service (use with: docker compose up nextjs-static-export --build)
+ nextjs-static-export:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: nextjs-static-export-image
+ container_name: nextjs-static-export-container
+ environment:
+ NODE_ENV: production
+ ports:
+ - "8080:8080"
+ restart: unless-stopped
+
+ # Serve variant (use with: docker compose up nextjs-static-export-with-serve --build)
+ nextjs-static-export-with-serve:
+ build:
+ context: .
+ dockerfile: Dockerfile.serve
+ image: nextjs-static-export-serve-image
+ container_name: nextjs-static-export-serve-container
+ environment:
+ NODE_ENV: production
+ PORT: 3000
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
diff --git a/examples/with-docker-export-output/next-env.d.ts b/examples/with-docker-export-output/next-env.d.ts
new file mode 100644
index 00000000000000..9edff1c7cacb3b
--- /dev/null
+++ b/examples/with-docker-export-output/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+import "./.next/types/routes.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/examples/with-docker-export-output/next.config.ts b/examples/with-docker-export-output/next.config.ts
new file mode 100644
index 00000000000000..00c940ebfa835e
--- /dev/null
+++ b/examples/with-docker-export-output/next.config.ts
@@ -0,0 +1,10 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ output: "export",
+ images: {
+ unoptimized: true,
+ },
+};
+
+export default nextConfig;
diff --git a/examples/with-docker-export-output/nginx.conf b/examples/with-docker-export-output/nginx.conf
new file mode 100644
index 00000000000000..febdc0ad91eebc
--- /dev/null
+++ b/examples/with-docker-export-output/nginx.conf
@@ -0,0 +1,65 @@
+# Minimal Nginx config for static Next.js app
+worker_processes 1;
+
+# Store PID in /tmp (always writable)
+pid /tmp/nginx.pid;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ # Disable logging to avoid permission issues
+ access_log off;
+ error_log /dev/stderr;
+
+ # Optimize static file serving
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+
+ # Gzip compression
+ gzip on;
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+ gzip_min_length 256;
+
+ server {
+ listen 8080;
+ server_name localhost;
+
+ # Serve static files
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Handle Next.js static export routing
+ # See: https://nextjs.org/docs/app/guides/static-exports#deploying
+ location / {
+ try_files $uri $uri.html $uri/ =404;
+ }
+
+ # This is necessary when `trailingSlash: false` (default).
+ # You can omit this when `trailingSlash: true` in next.config.
+ # Handles nested routes like /blog/post -> /blog/post.html
+ location ~ ^/(.+)/$ {
+ rewrite ^/(.+)/$ /$1.html break;
+ }
+
+ # Serve Next.js static assets
+ location ~ ^/_next/ {
+ try_files $uri =404;
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # Optional 404 handling
+ error_page 404 /404.html;
+ location = /404.html {
+ internal;
+ }
+ }
+}
+
diff --git a/examples/with-docker-export-output/package.json b/examples/with-docker-export-output/package.json
new file mode 100644
index 00000000000000..a1adcd8adc5c0f
--- /dev/null
+++ b/examples/with-docker-export-output/package.json
@@ -0,0 +1,21 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "next": "latest",
+ "react": "^19",
+ "react-dom": "^19"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+}
diff --git a/examples/with-docker-export-output/pnpm-lock.yaml b/examples/with-docker-export-output/pnpm-lock.yaml
new file mode 100644
index 00000000000000..b8987f54e39a85
--- /dev/null
+++ b/examples/with-docker-export-output/pnpm-lock.yaml
@@ -0,0 +1,956 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ next:
+ specifier: latest
+ version: 16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ react:
+ specifier: ^19
+ version: 19.2.3
+ react-dom:
+ specifier: ^19
+ version: 19.2.3(react@19.2.3)
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.18
+ '@types/node':
+ specifier: ^20
+ version: 20.19.30
+ '@types/react':
+ specifier: ^19
+ version: 19.2.9
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.2.3(@types/react@19.2.9)
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.18
+ typescript:
+ specifier: ^5
+ version: 5.9.3
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@next/env@16.1.4':
+ resolution: {integrity: sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==}
+
+ '@next/swc-darwin-arm64@16.1.4':
+ resolution: {integrity: sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@16.1.4':
+ resolution: {integrity: sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@16.1.4':
+ resolution: {integrity: sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@16.1.4':
+ resolution: {integrity: sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@16.1.4':
+ resolution: {integrity: sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@16.1.4':
+ resolution: {integrity: sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@16.1.4':
+ resolution: {integrity: sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@16.1.4':
+ resolution: {integrity: sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tailwindcss/node@4.1.18':
+ resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.18':
+ resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.18':
+ resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
+
+ '@types/node@20.19.30':
+ resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.9':
+ resolution: {integrity: sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==}
+
+ baseline-browser-mapping@2.9.17:
+ resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==}
+ hasBin: true
+
+ caniuse-lite@1.0.30001765:
+ resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ enhanced-resolve@5.18.4:
+ resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
+ engines: {node: '>=10.13.0'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next@16.1.4:
+ resolution: {integrity: sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.2.3:
+ resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
+ peerDependencies:
+ react: ^19.2.3
+
+ react@19.2.3:
+ resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
+ engines: {node: '>=0.10.0'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.0.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.8.1
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@next/env@16.1.4': {}
+
+ '@next/swc-darwin-arm64@16.1.4':
+ optional: true
+
+ '@next/swc-darwin-x64@16.1.4':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@16.1.4':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@16.1.4':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@16.1.4':
+ optional: true
+
+ '@next/swc-linux-x64-musl@16.1.4':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@16.1.4':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@16.1.4':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.4
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/postcss@4.1.18':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ postcss: 8.5.6
+ tailwindcss: 4.1.18
+
+ '@types/node@20.19.30':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/react-dom@19.2.3(@types/react@19.2.9)':
+ dependencies:
+ '@types/react': 19.2.9
+
+ '@types/react@19.2.9':
+ dependencies:
+ csstype: 3.2.3
+
+ baseline-browser-mapping@2.9.17: {}
+
+ caniuse-lite@1.0.30001765: {}
+
+ client-only@0.0.1: {}
+
+ csstype@3.2.3: {}
+
+ detect-libc@2.1.2: {}
+
+ enhanced-resolve@5.18.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ graceful-fs@4.2.11: {}
+
+ jiti@2.6.1: {}
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ nanoid@3.3.11: {}
+
+ next@16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ '@next/env': 16.1.4
+ '@swc/helpers': 0.5.15
+ baseline-browser-mapping: 2.9.17
+ caniuse-lite: 1.0.30001765
+ postcss: 8.4.31
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ styled-jsx: 5.1.6(react@19.2.3)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.1.4
+ '@next/swc-darwin-x64': 16.1.4
+ '@next/swc-linux-arm64-gnu': 16.1.4
+ '@next/swc-linux-arm64-musl': 16.1.4
+ '@next/swc-linux-x64-gnu': 16.1.4
+ '@next/swc-linux-x64-musl': 16.1.4
+ '@next/swc-win32-arm64-msvc': 16.1.4
+ '@next/swc-win32-x64-msvc': 16.1.4
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ picocolors@1.1.1: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@19.2.3(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ scheduler: 0.27.0
+
+ react@19.2.3: {}
+
+ scheduler@0.27.0: {}
+
+ semver@7.7.3:
+ optional: true
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.3
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.5
+ '@img/sharp-darwin-x64': 0.34.5
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ '@img/sharp-linux-arm': 0.34.5
+ '@img/sharp-linux-arm64': 0.34.5
+ '@img/sharp-linux-ppc64': 0.34.5
+ '@img/sharp-linux-riscv64': 0.34.5
+ '@img/sharp-linux-s390x': 0.34.5
+ '@img/sharp-linux-x64': 0.34.5
+ '@img/sharp-linuxmusl-arm64': 0.34.5
+ '@img/sharp-linuxmusl-x64': 0.34.5
+ '@img/sharp-wasm32': 0.34.5
+ '@img/sharp-win32-arm64': 0.34.5
+ '@img/sharp-win32-ia32': 0.34.5
+ '@img/sharp-win32-x64': 0.34.5
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ styled-jsx@5.1.6(react@19.2.3):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.3
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
diff --git a/examples/with-docker-export-output/postcss.config.js b/examples/with-docker-export-output/postcss.config.js
new file mode 100644
index 00000000000000..483f378543c055
--- /dev/null
+++ b/examples/with-docker-export-output/postcss.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
diff --git a/examples/with-docker-export-output/public/next.svg b/examples/with-docker-export-output/public/next.svg
new file mode 100644
index 00000000000000..5174b28c565c28
--- /dev/null
+++ b/examples/with-docker-export-output/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/with-docker-export-output/tsconfig.json b/examples/with-docker-export-output/tsconfig.json
new file mode 100644
index 00000000000000..c124ed94be3320
--- /dev/null
+++ b/examples/with-docker-export-output/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "incremental": true,
+ "module": "esnext",
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/with-docker/.dockerignore b/examples/with-docker/.dockerignore
index c5500558baf2e2..5565861bf35092 100644
--- a/examples/with-docker/.dockerignore
+++ b/examples/with-docker/.dockerignore
@@ -1,7 +1,129 @@
-Dockerfile
+############################################################
+# Production-ready .dockerignore for a Next.js (Vercel-style) app
+# Keeps Docker builds fast, lean, and free of development files.
+############################################################
+
+# Dependencies (installed inside Docker, never copied)
+node_modules/
+.pnpm-store/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Next.js build outputs (always generated during `next build`)
+.next/
+out/
+dist/
+build/
+.vercel/
+
+# Tests and testing output (not needed in production images)
+coverage/
+.nyc_output/
+__tests__/
+__mocks__/
+jest/
+cypress/
+cypress/screenshots/
+cypress/videos/
+playwright-report/
+test-results/
+.vitest/
+*.test.ts
+*.test.tsx
+*.test.js
+*.test.jsx
+*.spec.ts
+*.spec.tsx
+*.spec.js
+*.spec.jsx
+
+
+# Local development and editor files
+.git/
+.gitignore
+.gitattributes
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+*.log
+
+# Environment variables (only commit template files)
+.env
+.env*.local
+.env.development
+.env.test
+.env.production.local
+
+# Docker configuration files (not needed inside build context)
+Dockerfile*
.dockerignore
-node_modules
-npm-debug.log
-README.md
-.next
-.git
+docker-compose*.yml
+
+# Documentation
+*.md
+docs/
+
+# CI/CD configuration files
+.github/
+.gitlab-ci.yml
+.travis.yml
+.circleci/
+Jenkinsfile
+
+# Cache directories and temporary data
+.cache/
+.parcel-cache/
+.eslintcache
+.stylelintcache
+.turbo/
+.tmp/
+.temp/
+
+# TypeScript build metadata
+*.tsbuildinfo
+.tsbuildinfo
+
+# Sensitive or unnecessary configuration files
+*.pem
+.editorconfig
+.prettierrc*
+.eslintrc*
+.stylelintrc*
+.babelrc*
+*.iml
+*.ipr
+*.iws
+
+# OS-specific junk
+.DS_Store
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+Desktop.ini
+
+# AI/ML tool metadata and configs
+.cursor/
+.cursorrules
+.copilot/
+.copilotignore
+.github/copilot/
+.gemini/
+.anthropic/
+.kiro
+.claude
+
+# AI-generated temp files
+*.aider*
+*.copilot*
+*.chatgpt*
+*.claude*
+*.gemini*
+*.openai*
+*.anthropic*
diff --git a/examples/with-docker/Dockerfile b/examples/with-docker/Dockerfile
index 2ddc8576a9af18..420ce8ecfaa88e 100644
--- a/examples/with-docker/Dockerfile
+++ b/examples/with-docker/Dockerfile
@@ -1,66 +1,97 @@
-# syntax=docker.io/docker/dockerfile:1
+# ============================================
+# Stage 1: Dependencies Installation Stage
+# ============================================
-FROM node:20-alpine AS base
+# IMPORTANT: Node.js Version Maintenance
+# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing.
+# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version.
+ARG NODE_VERSION=24.13.0-slim
-# Install dependencies only when needed
-FROM base AS deps
-# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
-RUN apk add --no-cache libc6-compat
+FROM node:${NODE_VERSION} AS dependencies
+
+# Set working directory
WORKDIR /app
-# Install dependencies based on the preferred package manager
+# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
-RUN \
- if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
- elif [ -f package-lock.json ]; then npm ci; \
- elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
- else echo "Lockfile not found." && exit 1; \
+
+# Install project dependencies with frozen lockfile for reproducible builds
+RUN --mount=type=cache,target=/root/.npm \
+ --mount=type=cache,target=/usr/local/share/.cache/yarn \
+ --mount=type=cache,target=/root/.local/share/pnpm/store \
+ if [ -f package-lock.json ]; then \
+ npm ci --no-audit --no-fund; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn install --frozen-lockfile --production=false; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm install --frozen-lockfile; \
+ else \
+ echo "No lockfile found." && exit 1; \
fi
+# ============================================
+# Stage 2: Build Next.js application in standalone mode
+# ============================================
+
+FROM node:${NODE_VERSION} AS builder
-# Rebuild the source code only when needed
-FROM base AS builder
+# Set working directory
WORKDIR /app
-COPY --from=deps /app/node_modules ./node_modules
+
+# Copy project dependencies from dependencies stage
+COPY --from=dependencies /app/node_modules ./node_modules
+
+# Copy application source code
COPY . .
+ENV NODE_ENV=production
+
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
-RUN \
- if [ -f yarn.lock ]; then yarn run build; \
- elif [ -f package-lock.json ]; then npm run build; \
- elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
- else echo "Lockfile not found." && exit 1; \
+# Build Next.js application
+RUN --mount=type=cache,target=/app/.next/cache \
+ if [ -f package-lock.json ]; then \
+ npm run build; \
+ elif [ -f yarn.lock ]; then \
+ corepack enable yarn && yarn build; \
+ elif [ -f pnpm-lock.yaml ]; then \
+ corepack enable pnpm && pnpm build; \
+ else \
+ echo "No lockfile found." && exit 1; \
fi
-# Production image, copy all the files and run next
-FROM base AS runner
+# ============================================
+# Stage 3: Run Next.js application
+# ============================================
+
+FROM node:${NODE_VERSION} AS runner
+
+# Set working directory
WORKDIR /app
+# Set production environment variables
ENV NODE_ENV=production
-# Uncomment the following line in case you want to disable telemetry during runtime.
-# ENV NEXT_TELEMETRY_DISABLED=1
-
-RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
-COPY --from=builder /app/public ./public
+# Next.js collects completely anonymous telemetry data about general usage.
+# Learn more here: https://nextjs.org/telemetry
+# Uncomment the following line in case you want to disable telemetry during the run time.
+# ENV NEXT_TELEMETRY_DISABLED=1
-# Automatically leverage output traces to reduce image size
-# https://nextjs.org/docs/advanced-features/output-file-tracing
-COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
-COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+# Copy production assets
+COPY --from=builder --chown=node:node /app/public ./public
+COPY --from=builder --chown=node:node /app/.next/standalone ./
+COPY --from=builder --chown=node:node /app/.next/static ./.next/static
-USER nextjs
+# Switch to non-root user for security best practices
+USER node
+# Expose port 3000 to allow HTTP traffic
EXPOSE 3000
-ENV PORT=3000
-
-# server.js is created by next build from the standalone output
-# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
-ENV HOSTNAME="0.0.0.0"
+# Start Next.js standalone server
CMD ["node", "server.js"]
diff --git a/examples/with-docker/Dockerfile.bun b/examples/with-docker/Dockerfile.bun
index 5061193f9155de..7c0d14674b6341 100644
--- a/examples/with-docker/Dockerfile.bun
+++ b/examples/with-docker/Dockerfile.bun
@@ -1,54 +1,76 @@
-# -----------------------------------------------------------------------------
+# ============================================
+# Stage 1: Dependencies Installation Stage
+# ============================================
+
# This Dockerfile.bun is specifically configured for projects using Bun
# For npm/pnpm or yarn, refer to the Dockerfile instead
-# -----------------------------------------------------------------------------
-# Use Bun's official image
-FROM oven/bun:1 AS base
+FROM oven/bun:1 AS dependencies
+# Set working directory
WORKDIR /app
-# Install dependencies with bun
-FROM base AS deps
+# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json bun.lock* ./
-RUN bun install --no-save --frozen-lockfile
-# Rebuild the source code only when needed
-FROM base AS builder
+# Install project dependencies with frozen lockfile for reproducible builds
+RUN --mount=type=cache,target=/root/.bun/install/cache \
+ bun install --no-save --frozen-lockfile
+
+# ============================================
+# Stage 2: Build Next.js application in standalone mode
+# ============================================
+
+FROM oven/bun:1 AS builder
+
+# Set working directory
WORKDIR /app
-COPY --from=deps /app/node_modules ./node_modules
+
+# Copy project dependencies from dependencies stage
+COPY --from=dependencies /app/node_modules ./node_modules
+
+# Copy application source code
COPY . .
+ENV NODE_ENV=production
+
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
+# Build Next.js application
RUN bun run build
-# Production image, copy all the files and run next
-FROM base AS runner
-WORKDIR /app
+# ============================================
+# Stage 3: Run Next.js application
+# ============================================
-# Uncomment the following line in case you want to disable telemetry during runtime.
-# ENV NEXT_TELEMETRY_DISABLED=1
+FROM oven/bun:1 AS runner
-ENV NODE_ENV=production \
- PORT=3000 \
- HOSTNAME="0.0.0.0"
+# Set working directory
+WORKDIR /app
-RUN groupadd --system --gid 1001 nodejs && \
- useradd --system --uid 1001 --no-log-init -g nodejs nextjs
+# Set production environment variables
+ENV NODE_ENV=production
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
-COPY --from=builder /app/public ./public
+# Next.js collects completely anonymous telemetry data about general usage.
+# Learn more here: https://nextjs.org/telemetry
+# Uncomment the following line in case you want to disable telemetry during the run time.
+# ENV NEXT_TELEMETRY_DISABLED=1
-# Automatically leverage output traces to reduce image size
-# https://nextjs.org/docs/advanced-features/output-file-tracing
-COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
-COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+# Copy production assets
+COPY --from=builder --chown=bun:bun /app/public ./public
+COPY --from=builder --chown=bun:bun /app/.next/standalone ./
+COPY --from=builder --chown=bun:bun /app/.next/static ./.next/static
-USER nextjs
+# Switch to non-root user for security best practices
+USER bun
+# Expose port 3000 to allow HTTP traffic
EXPOSE 3000
-CMD ["bun", "./server.js"]
+# Start Next.js standalone server with Bun
+CMD ["bun", "server.js"]
diff --git a/examples/with-docker/README.md b/examples/with-docker/README.md
index 592b41773b4dd4..cca3ecaff158db 100644
--- a/examples/with-docker/README.md
+++ b/examples/with-docker/README.md
@@ -1,83 +1,192 @@
-# With Docker
+# Next.js Docker Example - Standalone Mode
-This examples shows how to use Docker with Next.js based on the [deployment documentation](https://nextjs.org/docs/deployment#docker-image). Additionally, it contains instructions for deploying to Google Cloud Run. However, you can use any container-based deployment host.
+A production-ready example demonstrating how to Dockerize Next.js applications using **standalone mode**. This example showcases best practices for containerizing Next.js apps with Docker.
-## How to use
+## Features
-Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), [pnpm](https://pnpm.io) or [bun](https://bun.sh/docs/cli/bun-create) to bootstrap the example:
+- ✅ Multi-stage Docker build for optimal image size
+- ✅ Next.js standalone mode for minimal production builds
+- ✅ Security best practices (non-root user)
+- ✅ Slim Linux base image for optimal compatibility and smaller size
+- ✅ BuildKit cache mounts for faster builds
+- ✅ Production-ready configuration
+
+## Prerequisites
+
+- [Docker](https://docs.docker.com/get-docker/)
+- [Node.js 20+](https://nodejs.org/)
+
+## Quick Start with Docker
+
+### Using Docker Compose
+
+The `compose.yml` includes both Node.js and Bun configurations. Run one service at a time to avoid port conflicts.
+
+**Node.js:**
```bash
-npx create-next-app --example with-docker nextjs-docker
+# Run with Node.js
+docker compose up nextjs-standalone --build
```
+**Bun:**
+
```bash
-yarn create next-app --example with-docker nextjs-docker
+# OR run with Bun
+docker compose up nextjs-standalone-with-bun --build
```
+**Stop the application:**
+
```bash
-pnpm create next-app --example with-docker nextjs-docker
+docker compose down
```
+### Using Docker Build
+
+**Node.js:**
+
```bash
-bun create next-app --example with-docker nextjs-docker
+# Build the image
+docker build -t nextjs-standalone-image .
+
+# Run the container
+docker run -p 3000:3000 nextjs-standalone-image
```
-## Using Docker
+**Bun:**
-1. [Install Docker](https://docs.docker.com/get-docker/) on your machine.
-1. Build your container:
- ```bash
- # For npm, pnpm or yarn
- docker build -t nextjs-docker .
-
- # For bun
- docker build -f Dockerfile.bun -t nextjs-docker .
- ```
-1. Run your container: `docker run -p 3000:3000 nextjs-docker`.
+```bash
+# Build the image
+docker build -f Dockerfile.bun -t nextjs-standalone-bun-image .
-You can view your images created with `docker images`.
+# Run the container
+docker run -p 3000:3000 nextjs-standalone-bun-image
+```
+
+**Open your browser:** Navigate to [http://localhost:3000](http://localhost:3000)
### In existing projects
-To add Docker support, copy [`Dockerfile`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile) to the project root. If using Bun, copy [`Dockerfile.bun`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile.bun) instead. Then add the following to next.config.js:
+To add Docker support to your existing Next.js project:
+
+1. Copy the [`Dockerfile`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile) (or [`Dockerfile.bun`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile.bun) for Bun) to your project root.
+2. Copy the [`.dockerignore`](https://github.com/vercel/next.js/blob/canary/examples/with-docker/.dockerignore) to your project root.
+3. Add the following to your `next.config.js` (or `next.config.ts`):
```js
// next.config.js
module.exports = {
- // ... rest of the configuration.
output: "standalone",
};
```
This will build the project as a standalone app inside the Docker image.
-## Deploying to Google Cloud Run
+## Project Structure
-1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) so you can use `gcloud` on the command line.
-1. Run `gcloud auth login` to log in to your account.
-1. [Create a new project](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) in Google Cloud Run (e.g. `nextjs-docker`). Ensure billing is turned on.
-1. Build your container image using Cloud Build: `gcloud builds submit --tag gcr.io/PROJECT-ID/helloworld --project PROJECT-ID`. This will also enable Cloud Build for your project.
-1. Deploy to Cloud Run: `gcloud run deploy --image gcr.io/PROJECT-ID/helloworld --project PROJECT-ID --platform managed --allow-unauthenticated`. Choose a region of your choice.
+```
+nextjs-docker/
+├── app/ # Next.js App Router directory
+│ ├── layout.tsx # Root layout with metadata
+│ ├── page.tsx # Home page with example content
+│ └── globals.css # Global styles with Tailwind CSS v4
+├── public/ # Static assets
+│ └── next.svg # Next.js logo
+├── Dockerfile # Multi-stage Docker configuration (Node.js)
+├── Dockerfile.bun # Multi-stage Docker configuration (Bun)
+├── compose.yml # Docker Compose configuration (Node.js & Bun services)
+├── next.config.ts # Next.js configuration (standalone mode)
+├── postcss.config.js # PostCSS configuration for Tailwind CSS
+├── tsconfig.json # TypeScript configuration
+├── package.json # Dependencies and scripts
+└── README.md # This file
+```
- - You will be prompted for the service name: press Enter to accept the default name, `helloworld`.
- - You will be prompted for [region](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#follow-cloud-run): select the region of your choice, for example `us-central1`.
+## Configuration
-## Running Locally
+### Next.js Standalone Mode
-First, run the development server:
+The `next.config.ts` file is configured with `output: "standalone"`:
-```bash
-npm run dev
-# or
-yarn dev
-# or
-bun run dev
+```typescript
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ output: "standalone",
+};
+
+export default nextConfig;
```
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+The standalone output mode creates a minimal, self-contained production build optimized for containerized deployments. When enabled, Next.js generates a `.next/standalone` directory containing only the essential files needed to run your application, excluding unnecessary dependencies and files. This results in significantly smaller Docker images and faster container startup times.
+
+Learn more about [Next.js standalone output](https://nextjs.org/docs/pages/api-reference/next-config-js/output#standalone) in the official documentation.
+
+### Dockerfile Highlights (Node.js)
+
+- **Multi-stage build**: Separates dependency installation (`dependencies`), build (`builder`), and runtime (`runner`) stages
+- **Slim Linux**: Uses `slim` image tag for optimal compatibility and smaller image size
+- **BuildKit cache mounts**: Speeds up builds by caching package manager stores (`/root/.npm`, `/usr/local/share/.cache/yarn`, `/root/.local/share/pnpm/store`) and Next.js build cache (`/app/.next/cache`)
+- **Non-root user**: Runs as `node` user for security
+- **Optimized layers**: Leverages Docker layer caching effectively
+- **Standalone output**: Copies only the necessary files from `.next/standalone` and `.next/static`
+- **Node.js version maintenance**: Uses Node.js 24.13.0-slim (latest LTS at time of writing). Update the `NODE_VERSION` ARG to the latest LTS version for security updates.
+
+### Dockerfile.bun Highlights (Bun)
+
+- **Multi-stage build**: Same three-stage pattern optimized for Bun
+- **Official Bun image**: Uses `oven/bun:1` for optimal Bun performance
+- **Non-root user**: Runs as built-in `bun` user for security
+- **Frozen lockfile**: Uses `bun.lock` for reproducible builds
+- **Standalone output**: Same optimized output as the Node.js version
+
+**Why Node.js slim image tag?**: The slim variant provides optimal compatibility with npm packages and native dependencies while maintaining a smaller image size (~226MB). Slim uses glibc (standard Linux), ensuring better compatibility than Alpine's musl libc, which can cause issues with some npm packages. This makes it ideal for public examples where reliability and compatibility are priorities.
-You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
+**When to use Alpine?**: Consider using `node:24.11.1-alpine` instead if:
+
+- **Image size is critical**: Alpine images are typically ~100MB smaller than slim variants (~110MB base vs ~226MB)
+- **Your dependencies are compatible**: Your npm packages don't require native binaries that depend on glibc
+- **You've tested thoroughly**: You've verified all your dependencies work correctly with musl libc
+- **Security-focused deployments**: Alpine's minimal attack surface can be beneficial for security-sensitive applications
+
+To switch to Alpine, simply change the `NODE_VERSION` ARG in the Dockerfile to `24.11.1-alpine`.
+
+**⚠️ Important - Node.js Version Maintenance**: This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. To ensure security and stay up-to-date, regularly check and update the `NODE_VERSION` ARG in the Dockerfile to the latest Node.js LTS version. Check the latest version at [Nodejs official website](https://nodejs.org/) and browse available Node.js images on [Docker Hub](https://hub.docker.com/_/node).
+
+## Deployment
+
+This example can be deployed to any container-based platform:
+
+- Google Cloud Run
+- AWS ECS/Fargate
+- Azure Container Instances
+- DigitalOcean App Platform
+- Any Kubernetes cluster
+
+### Deploying to Google Cloud Run
+
+1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install) so you can use `gcloud` on the command line.
+2. Run `gcloud auth login` to log in to your account.
+3. [Create a new project](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) in Google Cloud Run (e.g. `nextjs-docker`). Ensure billing is turned on.
+4. Build your container image using Cloud Build:
+ ```bash
+ gcloud builds submit --tag gcr.io/PROJECT-ID/nextjs-docker --project PROJECT-ID
+ ```
+ This will also enable Cloud Build for your project.
+5. Deploy to Cloud Run:
+ ```bash
+ gcloud run deploy --image gcr.io/PROJECT-ID/nextjs-docker --project PROJECT-ID --platform managed --allow-unauthenticated
+ ```
+ - You will be prompted for the service name: press Enter to accept the default name, `nextjs-docker`.
+ - You will be prompted for [region](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#follow-cloud-run): select the region of your choice, for example `us-central1`.
-[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
+## Learn More
-The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
+- [Next.js Documentation](https://nextjs.org/docs) - Comprehensive Next.js documentation
+- [Next.js Templates](https://vercel.com/templates?framework=next.js) - Browse and deploy Next.js templates
+- [Next.js Examples](https://github.com/vercel/next.js/tree/canary/examples) - Discover boilerplate example projects
+- [Deploy to Vercel](https://vercel.com/new) - Instantly deploy your Next.js site
+- [Learn Docker](https://docs.docker.com/get-started/) - Get started with Docker fundamentals, containerization, and deployment
+- [Docker Documentation](https://docs.docker.com/) - Comprehensive Docker documentation and reference guides
+- [React.js Docker Guide](https://docs.docker.com/language/nodejs/) - Official Docker guide for React.js applications following best practices for containerization
diff --git a/examples/with-docker/app/favicon.ico b/examples/with-docker/app/favicon.ico
new file mode 100644
index 00000000000000..718d6fea4835ec
Binary files /dev/null and b/examples/with-docker/app/favicon.ico differ
diff --git a/examples/with-docker/app/globals.css b/examples/with-docker/app/globals.css
new file mode 100644
index 00000000000000..a2dc41ecee5ec4
--- /dev/null
+++ b/examples/with-docker/app/globals.css
@@ -0,0 +1,26 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
diff --git a/examples/with-docker/app/layout.tsx b/examples/with-docker/app/layout.tsx
new file mode 100644
index 00000000000000..941dc478a5a40a
--- /dev/null
+++ b/examples/with-docker/app/layout.tsx
@@ -0,0 +1,55 @@
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+
+const geistSans = Geist({
+ variable: "--font-geist-sans",
+ subsets: ["latin"],
+});
+
+const geistMono = Geist_Mono({
+ variable: "--font-geist-mono",
+ subsets: ["latin"],
+});
+
+export const metadata: Metadata = {
+ title: "Next.js Docker Example - Standalone Mode",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode.",
+ keywords: [
+ "Next.js",
+ "Docker",
+ "standalone mode",
+ "containerization",
+ "React",
+ "Node.js",
+ ],
+ openGraph: {
+ title: "Next.js Docker Example - Standalone Mode",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode.",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "Next.js Docker Example - Standalone Mode",
+ description:
+ "A production-ready example demonstrating how to Dockerize Next.js applications using standalone mode.",
+ },
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/examples/with-docker/app/page.tsx b/examples/with-docker/app/page.tsx
new file mode 100644
index 00000000000000..10cf3b5ee7b58b
--- /dev/null
+++ b/examples/with-docker/app/page.tsx
@@ -0,0 +1,232 @@
+import Link from "next/link";
+import Image from "next/image";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+ Standalone Mode
+
+
+ This example showcases Next.js standalone output mode, which
+ creates a minimal production build optimized for Docker
+ containers.
+
+
+ Multi-stage Docker build for optimal image size
+ Production-ready configuration
+ Security best practices (non-root user)
+ BuildKit cache mounts for faster builds
+
+
+
+
+
+ Quick Start
+
+
+
+
Build the image:
+
+ docker build -t nextjs-standalone-image .
+
+
+
+
Run the container:
+
+ docker run -p 3000:3000 nextjs-standalone-image
+
+
+
+
+ Or use Docker Compose:
+
+
+ docker compose up
+
+
+
+ Access at:
+
+ http://localhost:3000
+
+
+
+
+
+
+
+
+ Next.js Resources
+
+
+
+
+ Documentation →
+
+
+ Find in-depth information about Next.js features and API.
+
+
+
+
+
+ Templates →
+
+
+ Browse and deploy Next.js templates to get started quickly!
+
+
+
+
+
+ Examples →
+
+
+ Discover and deploy boilerplate example Next.js projects.
+
+
+
+
+
+ Deploy →
+
+
+ Instantly deploy your Next.js site to a public URL with Vercel.
+
+
+
+
+
+
+
+ Docker Resources
+
+
+
+
+ Learn Docker →
+
+
+ Get started with Docker! Learn fundamentals, containerization,
+ and deployment.
+
+
+
+
+ Docker Docs →
+
+
+ Comprehensive Docker documentation and reference guides.
+
+
+
+
+
+ React.js Guide →
+
+
+ Official Docker guide for React.js applications following best
+ practices for containerization.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/with-docker/compose.yml b/examples/with-docker/compose.yml
new file mode 100644
index 00000000000000..eaee904aef980e
--- /dev/null
+++ b/examples/with-docker/compose.yml
@@ -0,0 +1,28 @@
+services:
+ # Node.js service (use with: docker compose up nextjs-standalone --build)
+ nextjs-standalone:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: nextjs-standalone-image
+ container_name: nextjs-standalone-container
+ environment:
+ NODE_ENV: production
+ PORT: "3000"
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
+
+ # Bun service (use with: docker compose up nextjs-standalone-with-bun --build)
+ nextjs-standalone-with-bun:
+ build:
+ context: .
+ dockerfile: Dockerfile.bun
+ image: nextjs-standalone-bun-image
+ container_name: nextjs-standalone-bun-container
+ environment:
+ NODE_ENV: production
+ PORT: "3000"
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
diff --git a/examples/with-docker/next.config.js b/examples/with-docker/next.config.js
deleted file mode 100644
index b9f289635e5ba1..00000000000000
--- a/examples/with-docker/next.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-module.exports = {
- output: "standalone",
-};
diff --git a/examples/with-docker/next.config.ts b/examples/with-docker/next.config.ts
new file mode 100644
index 00000000000000..8744023224e410
--- /dev/null
+++ b/examples/with-docker/next.config.ts
@@ -0,0 +1,5 @@
+const nextConfig = {
+ output: "standalone",
+};
+
+export default nextConfig;
diff --git a/examples/with-docker/package.json b/examples/with-docker/package.json
index 2726338dbee30c..a1adcd8adc5c0f 100644
--- a/examples/with-docker/package.json
+++ b/examples/with-docker/package.json
@@ -2,11 +2,20 @@
"private": true,
"scripts": {
"dev": "next dev",
- "build": "next build"
+ "build": "next build",
+ "start": "next start"
},
"dependencies": {
"next": "latest",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react": "^19",
+ "react-dom": "^19"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}
diff --git a/examples/with-docker/pages/_app.js b/examples/with-docker/pages/_app.js
deleted file mode 100644
index 2fc3e070091985..00000000000000
--- a/examples/with-docker/pages/_app.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import "../styles/globals.css";
-
-function MyApp({ Component, pageProps }) {
- return ;
-}
-
-export default MyApp;
diff --git a/examples/with-docker/pages/api/hello.js b/examples/with-docker/pages/api/hello.js
deleted file mode 100644
index ba6cf4ffc4782e..00000000000000
--- a/examples/with-docker/pages/api/hello.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-
-export default function hello(req, res) {
- res.status(200).json({ name: "John Doe" });
-}
diff --git a/examples/with-docker/pages/index.js b/examples/with-docker/pages/index.js
deleted file mode 100644
index 93d4fbbb6423f0..00000000000000
--- a/examples/with-docker/pages/index.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import Head from "next/head";
-import styles from "../styles/Home.module.css";
-
-export default function Home() {
- return (
-
-
-
Create Next App
-
-
-
-
-
- Welcome to Next.js on Docker!
-
-
-
- Get started by editing{" "}
- pages/index.js
-
-
-
-
-
-
-
- );
-}
diff --git a/examples/with-docker/pnpm-lock.yaml b/examples/with-docker/pnpm-lock.yaml
new file mode 100644
index 00000000000000..b8987f54e39a85
--- /dev/null
+++ b/examples/with-docker/pnpm-lock.yaml
@@ -0,0 +1,956 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ next:
+ specifier: latest
+ version: 16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ react:
+ specifier: ^19
+ version: 19.2.3
+ react-dom:
+ specifier: ^19
+ version: 19.2.3(react@19.2.3)
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.18
+ '@types/node':
+ specifier: ^20
+ version: 20.19.30
+ '@types/react':
+ specifier: ^19
+ version: 19.2.9
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.2.3(@types/react@19.2.9)
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.18
+ typescript:
+ specifier: ^5
+ version: 5.9.3
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@next/env@16.1.4':
+ resolution: {integrity: sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==}
+
+ '@next/swc-darwin-arm64@16.1.4':
+ resolution: {integrity: sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@16.1.4':
+ resolution: {integrity: sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@16.1.4':
+ resolution: {integrity: sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@16.1.4':
+ resolution: {integrity: sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@16.1.4':
+ resolution: {integrity: sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@16.1.4':
+ resolution: {integrity: sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@16.1.4':
+ resolution: {integrity: sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@16.1.4':
+ resolution: {integrity: sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tailwindcss/node@4.1.18':
+ resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.18':
+ resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.18':
+ resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
+
+ '@types/node@20.19.30':
+ resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.9':
+ resolution: {integrity: sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==}
+
+ baseline-browser-mapping@2.9.17:
+ resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==}
+ hasBin: true
+
+ caniuse-lite@1.0.30001765:
+ resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ enhanced-resolve@5.18.4:
+ resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
+ engines: {node: '>=10.13.0'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next@16.1.4:
+ resolution: {integrity: sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ react-dom@19.2.3:
+ resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
+ peerDependencies:
+ react: ^19.2.3
+
+ react@19.2.3:
+ resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
+ engines: {node: '>=0.10.0'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.0.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.8.1
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@next/env@16.1.4': {}
+
+ '@next/swc-darwin-arm64@16.1.4':
+ optional: true
+
+ '@next/swc-darwin-x64@16.1.4':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@16.1.4':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@16.1.4':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@16.1.4':
+ optional: true
+
+ '@next/swc-linux-x64-musl@16.1.4':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@16.1.4':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@16.1.4':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.4
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/postcss@4.1.18':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ postcss: 8.5.6
+ tailwindcss: 4.1.18
+
+ '@types/node@20.19.30':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/react-dom@19.2.3(@types/react@19.2.9)':
+ dependencies:
+ '@types/react': 19.2.9
+
+ '@types/react@19.2.9':
+ dependencies:
+ csstype: 3.2.3
+
+ baseline-browser-mapping@2.9.17: {}
+
+ caniuse-lite@1.0.30001765: {}
+
+ client-only@0.0.1: {}
+
+ csstype@3.2.3: {}
+
+ detect-libc@2.1.2: {}
+
+ enhanced-resolve@5.18.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ graceful-fs@4.2.11: {}
+
+ jiti@2.6.1: {}
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ nanoid@3.3.11: {}
+
+ next@16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ '@next/env': 16.1.4
+ '@swc/helpers': 0.5.15
+ baseline-browser-mapping: 2.9.17
+ caniuse-lite: 1.0.30001765
+ postcss: 8.4.31
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ styled-jsx: 5.1.6(react@19.2.3)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.1.4
+ '@next/swc-darwin-x64': 16.1.4
+ '@next/swc-linux-arm64-gnu': 16.1.4
+ '@next/swc-linux-arm64-musl': 16.1.4
+ '@next/swc-linux-x64-gnu': 16.1.4
+ '@next/swc-linux-x64-musl': 16.1.4
+ '@next/swc-win32-arm64-msvc': 16.1.4
+ '@next/swc-win32-x64-msvc': 16.1.4
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ picocolors@1.1.1: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ react-dom@19.2.3(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ scheduler: 0.27.0
+
+ react@19.2.3: {}
+
+ scheduler@0.27.0: {}
+
+ semver@7.7.3:
+ optional: true
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.3
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.5
+ '@img/sharp-darwin-x64': 0.34.5
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ '@img/sharp-linux-arm': 0.34.5
+ '@img/sharp-linux-arm64': 0.34.5
+ '@img/sharp-linux-ppc64': 0.34.5
+ '@img/sharp-linux-riscv64': 0.34.5
+ '@img/sharp-linux-s390x': 0.34.5
+ '@img/sharp-linux-x64': 0.34.5
+ '@img/sharp-linuxmusl-arm64': 0.34.5
+ '@img/sharp-linuxmusl-x64': 0.34.5
+ '@img/sharp-wasm32': 0.34.5
+ '@img/sharp-win32-arm64': 0.34.5
+ '@img/sharp-win32-ia32': 0.34.5
+ '@img/sharp-win32-x64': 0.34.5
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ styled-jsx@5.1.6(react@19.2.3):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.3
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
diff --git a/examples/with-docker/postcss.config.js b/examples/with-docker/postcss.config.js
new file mode 100644
index 00000000000000..483f378543c055
--- /dev/null
+++ b/examples/with-docker/postcss.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
diff --git a/examples/with-docker/public/favicon.ico b/examples/with-docker/public/favicon.ico
deleted file mode 100644
index 4965832f2c9b06..00000000000000
Binary files a/examples/with-docker/public/favicon.ico and /dev/null differ
diff --git a/examples/with-docker/public/next.svg b/examples/with-docker/public/next.svg
new file mode 100644
index 00000000000000..5174b28c565c28
--- /dev/null
+++ b/examples/with-docker/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/with-docker/public/vercel.svg b/examples/with-docker/public/vercel.svg
deleted file mode 100644
index fbf0e25a651c28..00000000000000
--- a/examples/with-docker/public/vercel.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/examples/with-docker/styles/Home.module.css b/examples/with-docker/styles/Home.module.css
deleted file mode 100644
index 78b997f2bf9976..00000000000000
--- a/examples/with-docker/styles/Home.module.css
+++ /dev/null
@@ -1,131 +0,0 @@
-.container {
- min-height: 100vh;
- padding: 0 0.5rem;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-}
-
-.main {
- padding: 5rem 0;
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-}
-
-.footer {
- width: 100%;
- height: 100px;
- border-top: 1px solid #eaeaea;
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.footer img {
- margin-left: 0.5rem;
-}
-
-.footer a {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-.title a {
- color: #0070f3;
- text-decoration: none;
-}
-
-.title a:hover,
-.title a:focus,
-.title a:active {
- text-decoration: underline;
-}
-
-.title {
- margin: 0;
- line-height: 1.15;
- font-size: 4rem;
-}
-
-.title,
-.description {
- text-align: center;
-}
-
-.description {
- line-height: 1.5;
- font-size: 1.5rem;
-}
-
-.code {
- background: #fafafa;
- border-radius: 5px;
- padding: 0.75rem;
- font-size: 1.1rem;
- font-family:
- Menlo,
- Monaco,
- Lucida Console,
- Liberation Mono,
- DejaVu Sans Mono,
- Bitstream Vera Sans Mono,
- Courier New,
- monospace;
-}
-
-.grid {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: wrap;
- max-width: 800px;
- margin-top: 3rem;
-}
-
-.card {
- margin: 1rem;
- flex-basis: 45%;
- padding: 1.5rem;
- text-align: left;
- color: inherit;
- text-decoration: none;
- border: 1px solid #eaeaea;
- border-radius: 10px;
- transition:
- color 0.15s ease,
- border-color 0.15s ease;
-}
-
-.card:hover,
-.card:focus,
-.card:active {
- color: #0070f3;
- border-color: #0070f3;
-}
-
-.card h3 {
- margin: 0 0 1rem 0;
- font-size: 1.5rem;
-}
-
-.card p {
- margin: 0;
- font-size: 1.25rem;
- line-height: 1.5;
-}
-
-.logo {
- height: 1em;
-}
-
-@media (max-width: 600px) {
- .grid {
- width: 100%;
- flex-direction: column;
- }
-}
diff --git a/examples/with-docker/styles/globals.css b/examples/with-docker/styles/globals.css
deleted file mode 100644
index 51a2a4eaacd86e..00000000000000
--- a/examples/with-docker/styles/globals.css
+++ /dev/null
@@ -1,26 +0,0 @@
-html,
-body {
- padding: 0;
- margin: 0;
- font-family:
- -apple-system,
- BlinkMacSystemFont,
- Segoe UI,
- Roboto,
- Oxygen,
- Ubuntu,
- Cantarell,
- Fira Sans,
- Droid Sans,
- Helvetica Neue,
- sans-serif;
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-* {
- box-sizing: border-box;
-}
diff --git a/examples/with-docker/tsconfig.json b/examples/with-docker/tsconfig.json
new file mode 100644
index 00000000000000..c124ed94be3320
--- /dev/null
+++ b/examples/with-docker/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "incremental": true,
+ "module": "esnext",
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/lerna.json b/lerna.json
index e7a2e2f6867bbc..63883e99f069c0 100644
--- a/lerna.json
+++ b/lerna.json
@@ -15,5 +15,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "16.2.0-canary.52"
+ "version": "16.2.0-canary.53"
}
\ No newline at end of file
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index dc51bfbcddcc57..e1cf938b1d2103 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index a4a5b72034b251..6373ed74a6e9b8 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
@@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
- "@next/eslint-plugin-next": "16.2.0-canary.52",
+ "@next/eslint-plugin-next": "16.2.0-canary.53",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json
index 74055261c2d6ed..d42579f9eefc8b 100644
--- a/packages/eslint-plugin-internal/package.json
+++ b/packages/eslint-plugin-internal/package.json
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index 15c16f1cbfb668..e61ac0fe4a5ba0 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/packages/font/package.json b/packages/font/package.json
index b5971f0ef79bba..736b3ee0f910f2 100644
--- a/packages/font/package.json
+++ b/packages/font/package.json
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 0e4be7a41f6887..cab299f9cb2478 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index bd125f804d45c1..eceb78b7b4ece3 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index e149952b26f439..9a5c50eb82797c 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index d0192f8478537d..65f7ff84ff74ac 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index 618ac1cb544220..2528c1708e216b 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index e5d358a5813e50..534b180671c75f 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index a251cb0711ab84..a05747b88fedd2 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json
index 0540427903b15f..6b4e66de21635a 100644
--- a/packages/next-routing/package.json
+++ b/packages/next-routing/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/routing",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"keywords": [
"react",
"next",
diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json
index 4343d61131351d..2e6475852d8a72 100644
--- a/packages/next-rspack/package.json
+++ b/packages/next-rspack/package.json
@@ -1,6 +1,6 @@
{
"name": "next-rspack",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-rspack"
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index 56bbfe6ebc245e..5d2dd1386181e8 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"private": true,
"files": [
"native/"
diff --git a/packages/next/errors.json b/packages/next/errors.json
index ae0f468a1d22d9..9ae79417b82e4a 100644
--- a/packages/next/errors.json
+++ b/packages/next/errors.json
@@ -1065,5 +1065,6 @@
"1064": "createServerParamsForRoute should not be called in client contexts.",
"1065": "createServerPathnameForMetadata should not be called in client contexts.",
"1066": "createServerSearchParamsForServerPage should not be called in a client validation.",
- "1067": "The Next.js unhandled rejection filter is being installed more than once. This is a bug in Next.js."
+ "1067": "The Next.js unhandled rejection filter is being installed more than once. This is a bug in Next.js.",
+ "1068": "Expected workStore to be initialized"
}
diff --git a/packages/next/package.json b/packages/next/package.json
index 6a98255b874f93..51865cb81a0cb3 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -97,7 +97,7 @@
]
},
"dependencies": {
- "@next/env": "16.2.0-canary.52",
+ "@next/env": "16.2.0-canary.53",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
@@ -162,11 +162,11 @@
"@modelcontextprotocol/sdk": "1.18.1",
"@mswjs/interceptors": "0.23.0",
"@napi-rs/triples": "1.2.0",
- "@next/font": "16.2.0-canary.52",
- "@next/polyfill-module": "16.2.0-canary.52",
- "@next/polyfill-nomodule": "16.2.0-canary.52",
- "@next/react-refresh-utils": "16.2.0-canary.52",
- "@next/swc": "16.2.0-canary.52",
+ "@next/font": "16.2.0-canary.53",
+ "@next/polyfill-module": "16.2.0-canary.53",
+ "@next/polyfill-nomodule": "16.2.0-canary.53",
+ "@next/react-refresh-utils": "16.2.0-canary.53",
+ "@next/swc": "16.2.0-canary.53",
"@opentelemetry/api": "1.6.0",
"@playwright/test": "1.51.1",
"@rspack/core": "1.6.7",
diff --git a/packages/next/src/build/define-env.ts b/packages/next/src/build/define-env.ts
index 749bef67db4684..de2abc235a3944 100644
--- a/packages/next/src/build/define-env.ts
+++ b/packages/next/src/build/define-env.ts
@@ -172,6 +172,8 @@ export function getDefineEnv({
),
'process.env.__NEXT_PPR': isPPREnabled,
'process.env.__NEXT_CACHE_COMPONENTS': isCacheComponentsEnabled,
+ 'process.env.__NEXT_INSTANT_NAV_TOGGLE':
+ !!config.experimental.instantNavigationDevToolsToggle,
'process.env.__NEXT_USE_CACHE': isUseCacheEnabled,
...(config.experimental?.useSkewCookie || !config.deploymentId
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index d7c7cd3ba57a2d..cfa8b63779da36 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -840,15 +840,21 @@ export function createStaticWorker(
isolatedMemory: true,
enableWorkerThreads: config.experimental.workerThreads,
exposedMethods: staticWorkerExposedMethods,
- forkOptions: process.env.NEXT_CPU_PROF
- ? {
- env: {
- NEXT_CPU_PROF: '1',
- NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
- __NEXT_PRIVATE_CPU_PROFILE: 'build-static-worker',
- },
- }
- : undefined,
+ forkOptions: {
+ env: {
+ ...(process.env.NEXT_CPU_PROF
+ ? {
+ NEXT_CPU_PROF: '1',
+ NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
+ __NEXT_PRIVATE_CPU_PROFILE: 'build-static-worker',
+ }
+ : undefined),
+ // worker.ts copies this value into globalThis.NEXT_CLIENT_ASSET_SUFFIX
+ __NEXT_PRERENDER_CLIENT_ASSET_SUFFIX: config.deploymentId
+ ? `?dpl=${config.deploymentId}`
+ : '',
+ },
+ },
}) as StaticWorker
}
@@ -2068,6 +2074,7 @@ export default async function build(
pprConfig: config.experimental.ppr,
cacheLifeProfiles: config.cacheLife,
buildId,
+ deploymentId: config.deploymentId,
sriEnabled,
cacheMaxMemorySize: config.cacheMaxMemorySize,
})
@@ -2290,6 +2297,7 @@ export default async function build(
pprConfig: config.experimental.ppr,
cacheLifeProfiles: config.cacheLife,
buildId,
+ deploymentId: config.deploymentId,
sriEnabled,
})
}
diff --git a/packages/next/src/build/segment-config/app/app-segment-config.ts b/packages/next/src/build/segment-config/app/app-segment-config.ts
index 88cb34a560abf7..9f54ef15f941b3 100644
--- a/packages/next/src/build/segment-config/app/app-segment-config.ts
+++ b/packages/next/src/build/segment-config/app/app-segment-config.ts
@@ -46,10 +46,8 @@ const InstantConfigSchema = z.union([
z.literal(false),
])
-export type InstantConfig = InstantConfigStatic | InstantConfigRuntime | false
-export type InstantConfigForTypeCheckInternal =
- | __GenericInstantConfig
- | InstantConfig
+export type Instant = InstantConfigStatic | InstantConfigRuntime | false
+export type InstantConfigForTypeCheckInternal = __GenericInstantConfig | Instant
// the __GenericPrefetch type is used to avoid type widening issues with
// our choice to make exports the medium for programming a Next.js application
// With exports the type is controlled by the module and all we can do is assert on it
@@ -233,7 +231,7 @@ export type AppSegmentConfig = {
/**
* How this segment should be prefetched.
*/
- unstable_instant?: InstantConfig
+ unstable_instant?: Instant
/**
* The preferred region for the page.
diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts
index 296ff9cfa2b3be..16877ca6a37463 100644
--- a/packages/next/src/build/utils.ts
+++ b/packages/next/src/build/utils.ts
@@ -757,6 +757,7 @@ export async function isPageStatic({
cacheLifeProfiles,
pprConfig,
buildId,
+ deploymentId,
sriEnabled,
}: {
dir: string
@@ -783,6 +784,7 @@ export async function isPageStatic({
nextConfigOutput: 'standalone' | 'export' | undefined
pprConfig: ExperimentalPPRConfig | undefined
buildId: string
+ deploymentId: string
sriEnabled: boolean
}): Promise {
// Skip page data collection for synthetic _global-error routes
@@ -836,6 +838,7 @@ export async function isPageStatic({
name: edgeInfo.name,
useCache: true,
distDir,
+ deploymentId,
})
const mod = (
await runtime.context._ENTRIES[`middleware_${edgeInfo.name}`]
diff --git a/packages/next/src/build/worker.ts b/packages/next/src/build/worker.ts
index 87a55a2ff9cc03..837c00a7242ad1 100644
--- a/packages/next/src/build/worker.ts
+++ b/packages/next/src/build/worker.ts
@@ -2,6 +2,10 @@ import '../server/require-hook'
// Import cpu-profile to start profiling early if enabled
import '../server/lib/cpu-profile'
+// Set the global asset suffix for Turbopack compiled code to use during prerendering
+;(globalThis as any).NEXT_CLIENT_ASSET_SUFFIX =
+ process.env.__NEXT_PRERENDER_CLIENT_ASSET_SUFFIX
+
export {
getDefinedNamedExports,
hasCustomGetInitialProps,
diff --git a/packages/next/src/client/app-bootstrap.ts b/packages/next/src/client/app-bootstrap.ts
index b48bd03d71e7bb..21959a7f81ff3e 100644
--- a/packages/next/src/client/app-bootstrap.ts
+++ b/packages/next/src/client/app-bootstrap.ts
@@ -76,34 +76,29 @@ export function appBootstrap(hydrate: (assetPrefix: string) => void) {
}
}
- // Instant Navigation Testing API: If the server returned a partial static
- // shell (indicated by the __next_instant_test global injected into the
- // HTML), skip hydration. The response doesn't include the full Flight data
- // stream. When the test framework deletes the cookie, the CookieStore
- // change event triggers a page reload.
+ // Instant Navigation Testing: When the cookie is set, set up a
+ // CookieStore listener that auto-reloads when the cookie is cleared.
+ // This is shared infrastructure for both the dev tools toggle and
+ // external test frameworks.
if (process.env.__NEXT_EXPOSE_TESTING_API) {
- if (self.__next_instant_test) {
- const NEXT_INSTANT_TEST_COOKIE = 'next-instant-navigation-testing'
- if (
- typeof cookieStore !== 'undefined' &&
- document.cookie.includes(NEXT_INSTANT_TEST_COOKIE + '=')
- ) {
- // Cookie is still set. Wait for the test framework to delete it,
- // then reload to get the full response.
- cookieStore.addEventListener('change', (event: CookieChangeEvent) => {
- for (const cookie of event.deleted) {
- if (cookie.name === NEXT_INSTANT_TEST_COOKIE) {
- window.location.reload()
- return
- }
+ const NEXT_INSTANT_TEST_COOKIE = 'next-instant-navigation-testing'
+ if (
+ document.cookie.includes(NEXT_INSTANT_TEST_COOKIE + '=') &&
+ typeof cookieStore !== 'undefined'
+ ) {
+ cookieStore.addEventListener('change', (event: CookieChangeEvent) => {
+ for (const cookie of event.deleted) {
+ if (cookie.name === NEXT_INSTANT_TEST_COOKIE) {
+ window.location.reload()
+ return
}
- })
- } else {
- // Cookie is already gone (or not accessible). Refresh immediately
- // to get the full response.
- window.location.reload()
- }
- return
+ }
+ })
+ } else if (self.__next_instant_test) {
+ // The server returned a static shell but we couldn't set up the
+ // cookie listener (document.cookie is empty or cookieStore is
+ // unavailable). Reload immediately to get the full response.
+ window.location.reload()
}
}
diff --git a/packages/next/src/client/app-next-dev.ts b/packages/next/src/client/app-next-dev.ts
index e5bf5baf299684..fdea7de73aac3e 100644
--- a/packages/next/src/client/app-next-dev.ts
+++ b/packages/next/src/client/app-next-dev.ts
@@ -11,11 +11,28 @@ import { isRecoverableError } from './react-client-callbacks/on-recoverable-erro
const instrumentationHooks = require('../lib/require-instrumentation-client')
appBootstrap((assetPrefix) => {
+ const enableCacheIndicator = process.env.__NEXT_CACHE_COMPONENTS
+
+ // Instant Navigation Mode: When the server returned a partial static shell
+ // (indicated by the __next_instant_test global), skip app hydration and only
+ // render the dev overlay so the developer can toggle the mode off. Hydration
+ // would fail because the static shell response doesn't include the full
+ // Flight data stream.
+ if (process.env.__NEXT_EXPOSE_TESTING_API) {
+ if (self.__next_instant_test) {
+ renderAppDevOverlay(
+ getOwnerStack,
+ isRecoverableError,
+ enableCacheIndicator
+ )
+ return
+ }
+ }
+
const { hydrate } = require('./app-index') as typeof import('./app-index')
try {
hydrate(instrumentationHooks, assetPrefix)
} finally {
- const enableCacheIndicator = process.env.__NEXT_CACHE_COMPONENTS
renderAppDevOverlay(getOwnerStack, isRecoverableError, enableCacheIndicator)
}
})
diff --git a/packages/next/src/client/app-next-turbopack.ts b/packages/next/src/client/app-next-turbopack.ts
index cb21888fb4e33b..607cd37805a3fb 100644
--- a/packages/next/src/client/app-next-turbopack.ts
+++ b/packages/next/src/client/app-next-turbopack.ts
@@ -9,6 +9,28 @@ window.next.turbopack = true
const instrumentationHooks = require('../lib/require-instrumentation-client')
appBootstrap((assetPrefix) => {
+ // Instant Navigation Mode: The server returned a partial static shell.
+ // Skip hydration — the response doesn't include the full Flight data
+ // stream. In dev mode, still render the dev overlay so the developer can
+ // toggle the mode off.
+ if (process.env.__NEXT_EXPOSE_TESTING_API) {
+ if (self.__next_instant_test) {
+ if (process.env.__NEXT_DEV_SERVER) {
+ const enableCacheIndicator = process.env.__NEXT_CACHE_COMPONENTS
+ const { getOwnerStack } =
+ require('../next-devtools/userspace/app/errors/stitched-error') as typeof import('../next-devtools/userspace/app/errors/stitched-error')
+ const { renderAppDevOverlay } =
+ require('next/dist/compiled/next-devtools') as typeof import('next/dist/compiled/next-devtools')
+ renderAppDevOverlay(
+ getOwnerStack,
+ isRecoverableError,
+ enableCacheIndicator
+ )
+ }
+ return
+ }
+ }
+
const { hydrate } = require('./app-index') as typeof import('./app-index')
try {
hydrate(instrumentationHooks, assetPrefix)
diff --git a/packages/next/src/client/app-next.ts b/packages/next/src/client/app-next.ts
index 7747df02c14911..7338cf9cbf7dd1 100644
--- a/packages/next/src/client/app-next.ts
+++ b/packages/next/src/client/app-next.ts
@@ -8,6 +8,15 @@ const instrumentationHooks =
require('../lib/require-instrumentation-client')
appBootstrap((assetPrefix) => {
+ // Instant Navigation Mode: The server returned a partial static shell.
+ // Skip hydration — the response doesn't include the full Flight data
+ // stream. The cookie listener in app-bootstrap handles the reload.
+ if (process.env.__NEXT_EXPOSE_TESTING_API) {
+ if (self.__next_instant_test) {
+ return
+ }
+ }
+
const { hydrate } = require('./app-index') as typeof import('./app-index')
// Include app-router and layout-router in the main chunk
// eslint-disable-next-line @next/internal/typechecked-require -- Why not relative imports?
diff --git a/packages/next/src/client/components/client-page.tsx b/packages/next/src/client/components/client-page.tsx
index ca13434a451b6b..2409925ad51f35 100644
--- a/packages/next/src/client/components/client-page.tsx
+++ b/packages/next/src/client/components/client-page.tsx
@@ -1,8 +1,6 @@
'use client'
import type { ParsedUrlQuery } from 'querystring'
-import { InvariantError } from '../../shared/lib/invariant-error'
-
import type { Params } from '../../server/request/params'
import { LayoutRouterContext } from '../../shared/lib/app-router-context.shared-runtime'
import { use } from 'react'
@@ -49,27 +47,16 @@ export function ClientPageRoot({
}
if (typeof window === 'undefined') {
- const { workAsyncStorage } =
- require('../../server/app-render/work-async-storage.external') as typeof import('../../server/app-render/work-async-storage.external')
-
let clientSearchParams: Promise
let clientParams: Promise
- // We are going to instrument the searchParams prop with tracking for the
- // appropriate context. We wrap differently in prerendering vs rendering
- const store = workAsyncStorage.getStore()
- if (!store) {
- throw new InvariantError(
- 'Expected workStore to exist when handling searchParams in a client Page.'
- )
- }
const { createSearchParamsFromClient } =
require('../../server/request/search-params') as typeof import('../../server/request/search-params')
- clientSearchParams = createSearchParamsFromClient(searchParams, store)
+ clientSearchParams = createSearchParamsFromClient(searchParams)
const { createParamsFromClient } =
require('../../server/request/params') as typeof import('../../server/request/params')
- clientParams = createParamsFromClient(params, store)
+ clientParams = createParamsFromClient(params)
return
} else {
diff --git a/packages/next/src/client/components/client-segment.tsx b/packages/next/src/client/components/client-segment.tsx
index fff9788731aadd..7adb0a51fdbc2d 100644
--- a/packages/next/src/client/components/client-segment.tsx
+++ b/packages/next/src/client/components/client-segment.tsx
@@ -1,7 +1,5 @@
'use client'
-import { InvariantError } from '../../shared/lib/invariant-error'
-
import type { Params } from '../../server/request/params'
import { LayoutRouterContext } from '../../shared/lib/app-router-context.shared-runtime'
import { use } from 'react'
@@ -38,22 +36,9 @@ export function ClientSegmentRoot({
}
if (typeof window === 'undefined') {
- const { workAsyncStorage } =
- require('../../server/app-render/work-async-storage.external') as typeof import('../../server/app-render/work-async-storage.external')
-
- let clientParams: Promise
- // We are going to instrument the searchParams prop with tracking for the
- // appropriate context. We wrap differently in prerendering vs rendering
- const store = workAsyncStorage.getStore()
- if (!store) {
- throw new InvariantError(
- 'Expected workStore to exist when handling params in a client segment such as a Layout or Template.'
- )
- }
-
const { createParamsFromClient } =
require('../../server/request/params') as typeof import('../../server/request/params')
- clientParams = createParamsFromClient(params, store)
+ const clientParams: Promise = createParamsFromClient(params)
return
} else {
diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts
index 67a69b01783593..684b49ea3823e9 100644
--- a/packages/next/src/export/worker.ts
+++ b/packages/next/src/export/worker.ts
@@ -343,14 +343,8 @@ export async function exportPages(
nextConfig,
options,
renderResumeDataCachesByPage = {},
- deploymentId,
} = input
- // Set the global asset suffix for Turbopack compiled code to use during prerendering
- ;(globalThis as any).NEXT_CLIENT_ASSET_SUFFIX = deploymentId
- ? `?dpl=${deploymentId}`
- : ''
-
installGlobalBehaviors(nextConfig)
if (nextConfig.enablePrerenderSourceMaps) {
diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx
index affb9a96628f8c..c181e47419d6a0 100644
--- a/packages/next/src/lib/metadata/metadata.tsx
+++ b/packages/next/src/lib/metadata/metadata.tsx
@@ -1,6 +1,6 @@
import React, { Suspense, cache } from 'react'
import type { ParsedUrlQuery } from 'querystring'
-import type { GetDynamicParamFromSegment } from '../../server/app-render/app-render'
+import type { Params } from '../../server/request/params'
import type { LoaderTree } from '../../server/lib/app-dir-module'
import type { SearchParams } from '../../server/request/search-params'
import {
@@ -14,7 +14,6 @@ import type {
} from './types/metadata-interface'
import { isHTTPAccessFallbackError } from '../../client/components/http-access-fallback/http-access-fallback'
import type { MetadataContext } from './types/resolvers'
-import type { WorkStore } from '../../server/app-render/work-async-storage.external'
import { createServerSearchParamsForMetadata } from '../../server/request/search-params'
import { createServerPathnameForMetadata } from '../../server/request/pathname'
import { isPostpone } from '../../server/lib/router-utils/is-postpone'
@@ -39,39 +38,30 @@ export function createMetadataComponents({
pathname,
parsedQuery,
metadataContext,
- getDynamicParamFromSegment,
+ interpolatedParams,
errorType,
- workStore,
serveStreamingMetadata,
}: {
tree: LoaderTree
pathname: string
parsedQuery: SearchParams
metadataContext: MetadataContext
- getDynamicParamFromSegment: GetDynamicParamFromSegment
+ interpolatedParams: Params
errorType?: MetadataErrorType | 'redirect'
- workStore: WorkStore
serveStreamingMetadata: boolean
}): {
Viewport: React.ComponentType
Metadata: React.ComponentType
MetadataOutlet: React.ComponentType
} {
- const searchParams = createServerSearchParamsForMetadata(
- parsedQuery,
- workStore
- )
- const pathnameForMetadata = createServerPathnameForMetadata(
- pathname,
- workStore
- )
+ const searchParams = createServerSearchParamsForMetadata(parsedQuery)
+ const pathnameForMetadata = createServerPathnameForMetadata(pathname)
async function Viewport() {
const tags = await getResolvedViewport(
tree,
searchParams,
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams,
errorType
).catch((viewportErr) => {
// When Legacy PPR is enabled viewport can reject with a Postpone type
@@ -84,8 +74,7 @@ export function createMetadataComponents({
return getNotFoundViewport(
tree,
searchParams,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
).catch(() => null)
}
// We're going to throw the error from the metadata outlet so we just render null here instead
@@ -109,9 +98,8 @@ export function createMetadataComponents({
tree,
pathnameForMetadata,
searchParams,
- getDynamicParamFromSegment,
+ interpolatedParams,
metadataContext,
- workStore,
errorType
).catch((metadataErr) => {
// When Legacy PPR is enabled metadata can reject with a Postpone type
@@ -125,9 +113,8 @@ export function createMetadataComponents({
tree,
pathnameForMetadata,
searchParams,
- getDynamicParamFromSegment,
- metadataContext,
- workStore
+ interpolatedParams,
+ metadataContext
).catch(() => null)
}
// We're going to throw the error from the metadata outlet so we just render null here instead
@@ -166,18 +153,11 @@ export function createMetadataComponents({
tree,
pathnameForMetadata,
searchParams,
- getDynamicParamFromSegment,
+ interpolatedParams,
metadataContext,
- workStore,
- errorType
- ),
- getResolvedViewport(
- tree,
- searchParams,
- getDynamicParamFromSegment,
- workStore,
errorType
),
+ getResolvedViewport(tree, searchParams, interpolatedParams, errorType),
]).then(() => null)
// TODO: We shouldn't change what we render based on whether we are streaming or not.
@@ -206,9 +186,8 @@ async function getResolvedMetadataImpl(
tree: LoaderTree,
pathname: Promise,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
+ interpolatedParams: Params,
metadataContext: MetadataContext,
- workStore: WorkStore,
errorType?: MetadataErrorType | 'redirect'
): Promise {
const errorConvention = errorType === 'redirect' ? undefined : errorType
@@ -216,9 +195,8 @@ async function getResolvedMetadataImpl(
tree,
pathname,
searchParams,
- getDynamicParamFromSegment,
+ interpolatedParams,
metadataContext,
- workStore,
errorConvention
)
}
@@ -228,18 +206,16 @@ async function getNotFoundMetadataImpl(
tree: LoaderTree,
pathname: Promise,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- metadataContext: MetadataContext,
- workStore: WorkStore
+ interpolatedParams: Params,
+ metadataContext: MetadataContext
): Promise {
const notFoundErrorConvention = 'not-found'
return renderMetadata(
tree,
pathname,
searchParams,
- getDynamicParamFromSegment,
+ interpolatedParams,
metadataContext,
- workStore,
notFoundErrorConvention
)
}
@@ -248,33 +224,24 @@ const getResolvedViewport = cache(getResolvedViewportImpl)
async function getResolvedViewportImpl(
tree: LoaderTree,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore,
+ interpolatedParams: Params,
errorType?: MetadataErrorType | 'redirect'
): Promise {
const errorConvention = errorType === 'redirect' ? undefined : errorType
- return renderViewport(
- tree,
- searchParams,
- getDynamicParamFromSegment,
- workStore,
- errorConvention
- )
+ return renderViewport(tree, searchParams, interpolatedParams, errorConvention)
}
const getNotFoundViewport = cache(getNotFoundViewportImpl)
async function getNotFoundViewportImpl(
tree: LoaderTree,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
): Promise {
const notFoundErrorConvention = 'not-found'
return renderViewport(
tree,
searchParams,
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams,
notFoundErrorConvention
)
}
@@ -283,9 +250,8 @@ async function renderMetadata(
tree: LoaderTree,
pathname: Promise,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
+ interpolatedParams: Params,
metadataContext: MetadataContext,
- workStore: WorkStore,
errorConvention?: MetadataErrorType
) {
const resolvedMetadata = await resolveMetadata(
@@ -293,8 +259,7 @@ async function renderMetadata(
pathname,
searchParams,
errorConvention,
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams,
metadataContext
)
return <>{createMetadataElements(resolvedMetadata)}>
@@ -303,16 +268,14 @@ async function renderMetadata(
async function renderViewport(
tree: LoaderTree,
searchParams: Promise,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore,
+ interpolatedParams: Params,
errorConvention?: MetadataErrorType
) {
const resolvedViewport = await resolveViewport(
tree,
searchParams,
errorConvention,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
return <>{createViewportElements(resolvedViewport)}>
}
diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts
index c379427ae41168..1c736db4140d7f 100644
--- a/packages/next/src/lib/metadata/resolve-metadata.ts
+++ b/packages/next/src/lib/metadata/resolve-metadata.ts
@@ -8,7 +8,7 @@ import type {
WithStringifiedURLs,
} from './types/metadata-interface'
import type { MetadataImageModule } from '../../build/webpack/loaders/metadata/types'
-import type { GetDynamicParamFromSegment } from '../../server/app-render/app-render'
+import { getSegmentParam } from '../../shared/lib/router/utils/get-segment-param'
import type { Twitter } from './types/twitter-types'
import type { OpenGraph } from './types/opengraph-types'
import type { AppDirModules } from '../../build/webpack/loaders/next-app-loader'
@@ -21,7 +21,8 @@ import type {
} from './types/metadata-types'
import type { ParsedUrlQuery } from 'querystring'
import type { StaticMetadata } from './types/icons'
-import type { WorkStore } from '../../server/app-render/work-async-storage.external'
+import { workAsyncStorage } from '../../server/app-render/work-async-storage.external'
+import { InvariantError } from '../../shared/lib/invariant-error'
import type { Params } from '../../server/request/params'
import type { SearchParams } from '../../server/request/search-params'
@@ -708,8 +709,7 @@ const resolveMetadataItems = cache(async function (
tree: LoaderTree,
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
) {
const parentParams = {}
const metadataItems: MetadataItems = []
@@ -723,8 +723,7 @@ const resolveMetadataItems = cache(async function (
searchParams,
errorConvention,
errorMetadataItem,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
})
@@ -737,8 +736,7 @@ async function resolveMetadataItemsImpl(
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
errorMetadataItem: MetadataItems[number],
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
): Promise {
const [segment, parallelRoutes, { page }] = tree
const currentTreePrefix =
@@ -746,19 +744,19 @@ async function resolveMetadataItemsImpl(
const isPage = typeof page !== 'undefined'
// Handle dynamic segment params.
- const segmentParam = getDynamicParamFromSegment(tree)
- /**
- * Create object holding the parent params and current params
- */
let currentParams = parentParams
- if (segmentParam && segmentParam.value !== null) {
- currentParams = {
- ...parentParams,
- [segmentParam.param]: segmentParam.value,
+ const segmentParam = getSegmentParam(segment)
+ if (segmentParam) {
+ const value = interpolatedParams[segmentParam.paramName]
+ if (value !== null && value !== undefined) {
+ currentParams = {
+ ...parentParams,
+ [segmentParam.paramName]: value,
+ }
}
}
- const params = createServerParamsForMetadata(currentParams, workStore)
+ const params = createServerParamsForMetadata(currentParams)
const props: SegmentProps = isPage ? { params, searchParams } : { params }
await collectMetadata({
@@ -783,8 +781,7 @@ async function resolveMetadataItemsImpl(
searchParams,
errorConvention,
errorMetadataItem,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
}
@@ -802,8 +799,7 @@ const resolveViewportItems = cache(async function (
tree: LoaderTree,
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
) {
const parentParams = {}
const viewportItems: ViewportItems = []
@@ -819,8 +815,7 @@ const resolveViewportItems = cache(async function (
searchParams,
errorConvention,
errorViewportItemRef,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
})
@@ -833,8 +828,7 @@ async function resolveViewportItemsImpl(
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
errorViewportItemRef: ErrorViewportItemRef,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
): Promise {
const [segment, parallelRoutes, { page }] = tree
const currentTreePrefix =
@@ -842,19 +836,19 @@ async function resolveViewportItemsImpl(
const isPage = typeof page !== 'undefined'
// Handle dynamic segment params.
- const segmentParam = getDynamicParamFromSegment(tree)
- /**
- * Create object holding the parent params and current params
- */
let currentParams = parentParams
- if (segmentParam && segmentParam.value !== null) {
- currentParams = {
- ...parentParams,
- [segmentParam.param]: segmentParam.value,
+ const segmentParam = getSegmentParam(segment)
+ if (segmentParam) {
+ const value = interpolatedParams[segmentParam.paramName]
+ if (value !== null && value !== undefined) {
+ currentParams = {
+ ...parentParams,
+ [segmentParam.paramName]: value,
+ }
}
}
- const params = createServerParamsForMetadata(currentParams, workStore)
+ const params = createServerParamsForMetadata(currentParams)
let layerProps: LayoutProps | PageProps
if (isPage) {
@@ -890,8 +884,7 @@ async function resolveViewportItemsImpl(
searchParams,
errorConvention,
errorViewportItemRef,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
}
@@ -1259,17 +1252,19 @@ export async function resolveMetadata(
pathname: Promise,
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore,
+ interpolatedParams: Params,
metadataContext: MetadataContext
): Promise {
const metadataItems = await resolveMetadataItems(
tree,
searchParams,
errorConvention,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
return accumulateMetadata(
workStore.route,
metadataItems,
@@ -1283,15 +1278,13 @@ export async function resolveViewport(
tree: LoaderTree,
searchParams: Promise,
errorConvention: MetadataErrorType | undefined,
- getDynamicParamFromSegment: GetDynamicParamFromSegment,
- workStore: WorkStore
+ interpolatedParams: Params
): Promise {
const viewportItems = await resolveViewportItems(
tree,
searchParams,
errorConvention,
- getDynamicParamFromSegment,
- workStore
+ interpolatedParams
)
return accumulateViewport(viewportItems)
}
diff --git a/packages/next/src/next-devtools/dev-overlay.browser.tsx b/packages/next/src/next-devtools/dev-overlay.browser.tsx
index 6e6ee662a6cda4..0203d3301ef2bb 100644
--- a/packages/next/src/next-devtools/dev-overlay.browser.tsx
+++ b/packages/next/src/next-devtools/dev-overlay.browser.tsx
@@ -22,6 +22,7 @@ import {
type OverlayState,
type DispatcherEvent,
ACTION_CACHE_INDICATOR,
+ ACTION_CACHE_ONLY_TOGGLE,
} from './dev-overlay/shared'
import {
@@ -73,6 +74,7 @@ export interface Dispatcher {
segmentExplorerNodeAdd(nodeState: SegmentNodeState): void
segmentExplorerNodeRemove(nodeState: SegmentNodeState): void
segmentExplorerUpdateRouteState(page: string): void
+ cacheOnlyToggle(): void
}
type Dispatch = ReturnType[1]
@@ -224,6 +226,9 @@ export const dispatcher: Dispatcher = {
dispatch({ type: ACTION_DEVTOOL_UPDATE_ROUTE_STATE, page })
}
),
+ cacheOnlyToggle: createQueuable((dispatch: Dispatch) => {
+ dispatch({ type: ACTION_CACHE_ONLY_TOGGLE })
+ }),
}
function replayQueuedEvents(dispatch: NonNullable) {
diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/next-logo.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/next-logo.tsx
index d12da9fb58e383..931bb761ec3566 100644
--- a/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/next-logo.tsx
+++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/next-logo.tsx
@@ -45,16 +45,23 @@ export function NextLogo({
const isCacheFilling = state.cacheIndicator === 'filling'
const isCacheBypassing = state.cacheIndicator === 'bypass'
- // Determine if we should show any status (excluding cache bypass, which renders like error badge)
- const shouldShowStatus =
+ // Determine if we should show any transient status (excluding cache bypass)
+ const shouldShowTransientStatus =
state.buildingIndicator || state.renderingIndicator || isCacheFilling
// Delay showing for 400ms to catch fast operations,
// and keep visible for minimum time (longer for warnings)
- const { rendered: showStatusIndicator } = useDelayedRender(shouldShowStatus, {
- enterDelay: 400,
- exitDelay: 500,
- })
+ const { rendered: showTransientStatusIndicator } = useDelayedRender(
+ shouldShowTransientStatus,
+ {
+ enterDelay: 400,
+ exitDelay: 500,
+ }
+ )
+
+ // Instant mode shows immediately (no delay) since it's a persistent user-toggled state
+ const isCacheOnly = state.cacheOnly
+ const showStatusIndicator = showTransientStatusIndicator || isCacheOnly
const ref = useRef(null)
const measuredWidth = useMeasureWidth(ref)
@@ -63,7 +70,8 @@ export function NextLogo({
const currentStatus = getCurrentStatus(
state.buildingIndicator,
state.renderingIndicator,
- state.cacheIndicator
+ state.cacheIndicator,
+ state.cacheOnly
)
const displayStatus = showStatusIndicator ? currentStatus : Status.None
@@ -184,6 +192,17 @@ export function NextLogo({
}
}
+ &[data-cache-only='true']:not([data-error='true']):not(
+ [data-cache-bypassing='true']
+ ) {
+ background: rgba(59, 130, 246, 0.95);
+ --color-inner-border: rgba(96, 165, 250, 0.9);
+
+ [data-issues-open] {
+ color: white;
+ }
+ }
+
&[data-error-expanded='false'][data-error='true'] ~ [data-dot] {
scale: 1;
}
@@ -379,6 +398,7 @@ export function NextLogo({
data-error-expanded={isExpanded}
data-status={hasError || isCacheBypassing ? Status.None : currentStatus}
data-cache-bypassing={isCacheBypassing}
+ data-cache-only={isCacheOnly}
data-animate={newErrorDetected}
style={{ width }}
>
@@ -489,7 +509,20 @@ export function NextLogo({
!state.disableDevIndicator && (
{
+ document.cookie =
+ 'next-instant-navigation-testing=; path=/; max-age=0'
+ window.location.reload()
+ }
+ : onTriggerClick
+ }
/>
)}
>
diff --git a/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/status-indicator.tsx b/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/status-indicator.tsx
index 2fe665eff27205..00556a2f8051f4 100644
--- a/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/status-indicator.tsx
+++ b/packages/next/src/next-devtools/dev-overlay/components/devtools-indicator/status-indicator.tsx
@@ -7,16 +7,18 @@ export enum Status {
Compiling = 'compiling',
Prerendering = 'prerendering',
CacheBypassing = 'cache-bypassing',
+ Instant = 'instant',
}
export function getCurrentStatus(
buildingIndicator: boolean,
renderingIndicator: boolean,
- cacheIndicator: CacheIndicatorState
+ cacheIndicator: CacheIndicatorState,
+ instantMode?: boolean
): Status {
const isCacheFilling = cacheIndicator === 'filling'
- // Priority order: compiling > prerendering > rendering
+ // Priority order: compiling > prerendering > rendering > instant
// Note: cache bypassing is now handled as a badge, not a status indicator
if (buildingIndicator) {
return Status.Compiling
@@ -27,21 +29,30 @@ export function getCurrentStatus(
if (renderingIndicator) {
return Status.Rendering
}
+ if (instantMode) {
+ return Status.Instant
+ }
return Status.None
}
interface StatusIndicatorProps {
status: Status
onClick?: () => void
+ title?: string
}
-export function StatusIndicator({ status, onClick }: StatusIndicatorProps) {
+export function StatusIndicator({
+ status,
+ onClick,
+ title,
+}: StatusIndicatorProps) {
const statusText: Record = {
[Status.None]: '',
[Status.CacheBypassing]: 'Cache disabled',
[Status.Prerendering]: 'Prerendering',
[Status.Compiling]: 'Compiling',
[Status.Rendering]: 'Rendering',
+ [Status.Instant]: 'Instant UI only',
}
// Status dot colors
@@ -51,6 +62,7 @@ export function StatusIndicator({ status, onClick }: StatusIndicatorProps) {
[Status.Prerendering]: '#f5a623',
[Status.Compiling]: '#f5a623',
[Status.Rendering]: '#50e3c2',
+ [Status.Instant]: '#fff', // White dot on blue badge background
}
if (status === Status.None) {
@@ -160,7 +172,8 @@ export function StatusIndicator({ status, onClick }: StatusIndicatorProps) {
data-indicator-status
data-nextjs-dev-tools-button
onClick={onClick}
- aria-label="Open Next.js Dev Tools"
+ title={title}
+ aria-label={title || 'Open Next.js Dev Tools'}
>
{statusDotColor[status] && (
{
label: 'Cache Components',
value: 'Enabled',
},
+ isAppRouter &&
+ !!process.env.__NEXT_INSTANT_NAV_TOGGLE && {
+ title:
+ 'When enabled, navigations show only the cached/prefetched state.',
+ label: 'Instant Navigation Mode',
+ value: state.cacheOnly ? 'On' : 'Off',
+ onClick: () => {
+ if (state.cacheOnly) {
+ // Turn off: delete cookie and reload to get dynamic data
+ document.cookie =
+ 'next-instant-navigation-testing=; path=/; max-age=0'
+ dispatch({ type: ACTION_CACHE_ONLY_TOGGLE })
+ window.location.reload()
+ } else {
+ // Turn on: set cookie to lock dynamic requests
+ document.cookie = 'next-instant-navigation-testing=1; path=/'
+ dispatch({ type: ACTION_CACHE_ONLY_TOGGLE })
+ setPanel(null)
+ setSelectedIndex(-1)
+ }
+ },
+ attributes: {
+ 'data-cache-only': true,
+ },
+ },
isAppRouter && {
label: 'Route Info',
value:
,
diff --git a/packages/next/src/next-devtools/dev-overlay/shared.ts b/packages/next/src/next-devtools/dev-overlay/shared.ts
index 65256de48d881d..1c2ebb12c914a5 100644
--- a/packages/next/src/next-devtools/dev-overlay/shared.ts
+++ b/packages/next/src/next-devtools/dev-overlay/shared.ts
@@ -70,6 +70,7 @@ export interface OverlayState {
readonly page: string
readonly theme: 'light' | 'dark' | 'system'
readonly hideShortcut: string | null
+ readonly cacheOnly: boolean
}
type DevtoolsPanelName = string
export type OverlayDispatch = React.Dispatch
@@ -101,6 +102,7 @@ export const ACTION_DEVTOOLS_PANEL_POSITION = 'devtools-panel-position'
export const ACTION_DEVTOOLS_SCALE = 'devtools-scale'
export const ACTION_DEVTOOLS_CONFIG = 'devtools-config'
+export const ACTION_CACHE_ONLY_TOGGLE = 'cache-only-toggle'
export const STORAGE_KEY_PANEL_POSITION_PREFIX =
'__nextjs-dev-tools-panel-position'
@@ -216,6 +218,10 @@ interface DevToolsConfigAction {
devToolsConfig: DevToolsConfig
}
+interface CacheOnlyToggleAction {
+ type: typeof ACTION_CACHE_ONLY_TOGGLE
+}
+
export type DispatcherEvent =
| BuildOkAction
| BuildErrorAction
@@ -241,6 +247,7 @@ export type DispatcherEvent =
| DevToolUpdateRouteStateAction
| DevIndicatorSetAction
| DevToolsConfigAction
+ | CacheOnlyToggleAction
const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX =
// 1st group: new frame + v8
@@ -263,6 +270,11 @@ const shouldDisableDevIndicator =
const devToolsInitialPositionFromNextConfig = (process.env
.__NEXT_DEV_INDICATOR_POSITION ?? 'bottom-left') as Corners
+const hasInstantTestCookie =
+ !!process.env.__NEXT_INSTANT_NAV_TOGGLE &&
+ typeof document !== 'undefined' &&
+ document.cookie.includes('next-instant-navigation-testing=')
+
export const INITIAL_OVERLAY_STATE: Omit<
OverlayState,
'isErrorOverlayOpen' | 'routerType'
@@ -274,12 +286,15 @@ export const INITIAL_OVERLAY_STATE: Omit<
renderingIndicator: false,
cacheIndicator: 'disabled',
staticIndicator: 'disabled',
- /*
+ /*
This is set to `true` when we can reliably know
- whether the indicator is in disabled state or not.
+ whether the indicator is in disabled state or not.
Otherwise the surface would flicker because the disabled flag loads from the config.
*/
- showIndicator: false,
+ // When cache-only is active, show the indicator immediately so the user
+ // can toggle it off. Normally this is set to true by the HMR connection,
+ // but the HMR WebSocket is only created during hydration.
+ showIndicator: hasInstantTestCookie,
disableDevIndicator: false,
buildingIndicator: false,
refreshState: { type: 'idle' },
@@ -294,6 +309,7 @@ export const INITIAL_OVERLAY_STATE: Omit<
page: '',
theme: 'system',
hideShortcut: null,
+ cacheOnly: hasInstantTestCookie,
}
function getInitialState(
@@ -507,6 +523,9 @@ export function useErrorOverlayReducer(
hideShortcut !== undefined ? hideShortcut : state.hideShortcut,
}
}
+ case ACTION_CACHE_ONLY_TOGGLE: {
+ return { ...state, cacheOnly: !state.cacheOnly }
+ }
default: {
return state
}
diff --git a/packages/next/src/next-devtools/dev-overlay/storybook/use-overlay-reducer.ts b/packages/next/src/next-devtools/dev-overlay/storybook/use-overlay-reducer.ts
index d8f0201cda3676..cff5821d972f9d 100644
--- a/packages/next/src/next-devtools/dev-overlay/storybook/use-overlay-reducer.ts
+++ b/packages/next/src/next-devtools/dev-overlay/storybook/use-overlay-reducer.ts
@@ -8,6 +8,7 @@ import {
ACTION_BUILDING_INDICATOR_HIDE,
ACTION_BUILDING_INDICATOR_SHOW,
ACTION_CACHE_INDICATOR,
+ ACTION_CACHE_ONLY_TOGGLE,
ACTION_DEBUG_INFO,
ACTION_DEV_INDICATOR,
ACTION_DEV_INDICATOR_SET,
@@ -83,6 +84,7 @@ export function useStorybookOverlayReducer(initialState?: OverlayState) {
case ACTION_RENDERING_INDICATOR_HIDE:
case ACTION_RENDERING_INDICATOR_SHOW:
case ACTION_CACHE_INDICATOR:
+ case ACTION_CACHE_ONLY_TOGGLE:
case ACTION_STATIC_INDICATOR:
case ACTION_UNHANDLED_ERROR:
case ACTION_UNHANDLED_REJECTION:
diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx
index 62620de47d8a6c..952ee1758af187 100644
--- a/packages/next/src/server/app-render/app-render.tsx
+++ b/packages/next/src/server/app-render/app-render.tsx
@@ -269,6 +269,7 @@ export type AppRenderContext = {
renderOpts: RenderOpts
parsedRequestHeaders: ParsedRequestHeaders
getDynamicParamFromSegment: GetDynamicParamFromSegment
+ interpolatedParams: Params
query: NextParsedUrlQuery
isPrefetch: boolean
isPossibleServerAction: boolean
@@ -523,7 +524,6 @@ async function generateDynamicRSCPayload(
createMetadataComponents,
Fragment,
},
- getDynamicParamFromSegment,
query,
requestId,
flightRouterState,
@@ -549,8 +549,7 @@ async function generateDynamicRSCPayload(
parsedQuery: query,
pathname: url.pathname,
metadataContext: createMetadataContext(ctx.renderOpts),
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams: ctx.interpolatedParams,
serveStreamingMetadata,
})
@@ -1443,8 +1442,7 @@ async function getRSCPayload(
parsedQuery: query,
pathname: url.pathname,
metadataContext: createMetadataContext(ctx.renderOpts),
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams: ctx.interpolatedParams,
serveStreamingMetadata,
})
@@ -1564,8 +1562,7 @@ async function getErrorRSCPayload(
pathname: url.pathname,
metadataContext: createMetadataContext(ctx.renderOpts),
errorType,
- getDynamicParamFromSegment,
- workStore,
+ interpolatedParams: ctx.interpolatedParams,
serveStreamingMetadata: serveStreamingMetadata,
})
@@ -2023,6 +2020,7 @@ async function renderToHTMLOrFlightImpl(
workStore,
parsedRequestHeaders,
getDynamicParamFromSegment,
+ interpolatedParams,
query,
isPrefetch: isPrefetchRequest,
isPossibleServerAction: isPossibleActionRequest,
diff --git a/packages/next/src/server/app-render/create-component-tree.tsx b/packages/next/src/server/app-render/create-component-tree.tsx
index 0f1242519b63f3..db8d6733e4a574 100644
--- a/packages/next/src/server/app-render/create-component-tree.tsx
+++ b/packages/next/src/server/app-render/create-component-tree.tsx
@@ -784,8 +784,7 @@ async function createComponentTreeInternal(
} else if (isStaticGeneration) {
const promiseOfParams =
createPrerenderParamsForClientSegment(currentParams)
- const promiseOfSearchParams =
- createPrerenderSearchParamsForClientPage(workStore)
+ const promiseOfSearchParams = createPrerenderSearchParamsForClientPage()
pageElement = createElement(ClientPageRoot, {
Component: PageComponent,
serverProvidedParams: {
@@ -809,7 +808,6 @@ async function createComponentTreeInternal(
// their usage in case the current render mode tracks dynamic API usage.
const params = createServerParamsForServerSegment(
currentParams,
- workStore,
varyParamsAccumulator,
isRuntimePrefetchable
)
@@ -819,7 +817,6 @@ async function createComponentTreeInternal(
// usage.
let searchParams = createServerSearchParamsForServerPage(
query,
- workStore,
varyParamsAccumulator,
isRuntimePrefetchable
)
@@ -993,7 +990,6 @@ async function createComponentTreeInternal(
} else {
const params = createServerParamsForServerSegment(
currentParams,
- workStore,
varyParamsAccumulator,
isRuntimePrefetchable
)
diff --git a/packages/next/src/server/app-render/instant-validation/instant-validation.tsx b/packages/next/src/server/app-render/instant-validation/instant-validation.tsx
index 50abee6be06cc2..ea259a465b64f7 100644
--- a/packages/next/src/server/app-render/instant-validation/instant-validation.tsx
+++ b/packages/next/src/server/app-render/instant-validation/instant-validation.tsx
@@ -29,7 +29,7 @@ import { parseLoaderTree } from '../../../shared/lib/router/utils/parse-loader-t
import type { GetDynamicParamFromSegment } from '../app-render'
import type {
AppSegmentConfig,
- InstantConfig,
+ Instant,
} from '../../../build/segment-config/app/app-segment-config'
import { Readable } from 'node:stream'
import {
@@ -84,7 +84,7 @@ export type RouteTree = {
module: null | {
type: 'layout' | 'page'
// TODO(instant-validation): We should know if a layout segment is shared
- instantConfig: InstantConfig | null
+ instantConfig: Instant | null
conventionPath: string
}
diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts
index 257a029385d222..7e2e755b74dc0a 100644
--- a/packages/next/src/server/config-schema.ts
+++ b/packages/next/src/server/config-schema.ts
@@ -239,6 +239,7 @@ export const experimentalSchema = {
externalMiddlewareRewritesResolve: z.boolean().optional(),
externalProxyRewritesResolve: z.boolean().optional(),
exposeTestingApiInProductionBuild: z.boolean().optional(),
+ instantNavigationDevToolsToggle: z.boolean().optional(),
fallbackNodePolyfills: z.literal(false).optional(),
fetchCacheKeyPrefix: z.string().optional(),
forceSwcTransforms: z.boolean().optional(),
diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts
index aaaef583f471e8..7db976bb51ce23 100644
--- a/packages/next/src/server/config-shared.ts
+++ b/packages/next/src/server/config-shared.ts
@@ -394,6 +394,12 @@ export interface ExperimentalConfig {
* Do not enable in user-facing production deployments.
*/
exposeTestingApiInProductionBuild?: boolean
+ /**
+ * Show the Instant Navigation Mode toggle in the dev tools indicator.
+ * When enabled, a menu item lets you lock navigations to only show
+ * the cached/prefetched state.
+ */
+ instantNavigationDevToolsToggle?: boolean
extensionAlias?: Record
allowedRevalidateHeaderKeys?: string[]
fetchCacheKeyPrefix?: string
diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts
index 2493d1733d6b13..65825723b735e7 100644
--- a/packages/next/src/server/next-server.ts
+++ b/packages/next/src/server/next-server.ts
@@ -1717,6 +1717,7 @@ export default class NextNodeServer extends BaseServer<
request: requestData,
useCache: true,
onWarning: params.onWarning,
+ deploymentId: this.deploymentId,
})
}
@@ -2014,6 +2015,7 @@ export default class NextNodeServer extends BaseServer<
params.req,
'serverComponentsHmrCache'
),
+ deploymentId: this.deploymentId,
})
if (result.fetchMetrics) {
diff --git a/packages/next/src/server/request/params.ts b/packages/next/src/server/request/params.ts
index 59cc5de188292f..93a4553cf87cc0 100644
--- a/packages/next/src/server/request/params.ts
+++ b/packages/next/src/server/request/params.ts
@@ -42,9 +42,12 @@ export type ParamValue = string | Array | undefined
export type Params = Record
export function createParamsFromClient(
- underlyingParams: Params,
- workStore: WorkStore
+ underlyingParams: Params
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
@@ -105,13 +108,11 @@ export function createParamsFromClient(
const metadataIsRuntimePrefetchable = false
export type CreateServerParamsForMetadata = typeof createServerParamsForMetadata
export function createServerParamsForMetadata(
- underlyingParams: Params,
- workStore: WorkStore
+ underlyingParams: Params
): Promise {
const metadataVaryParamsAccumulator = getMetadataVaryParamsAccumulator()
return createServerParamsForServerSegment(
underlyingParams,
- workStore,
metadataVaryParamsAccumulator,
metadataIsRuntimePrefetchable
)
@@ -120,9 +121,12 @@ export function createServerParamsForMetadata(
// routes always runs in RSC context so it is equivalent to a Server Page Component
export function createServerParamsForRoute(
underlyingParams: Params,
- workStore: WorkStore,
varyParamsAccumulator: VaryParamsAccumulator | null = null
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
@@ -183,10 +187,13 @@ export function createServerParamsForRoute(
export function createServerParamsForServerSegment(
underlyingParams: Params,
- workStore: WorkStore,
varyParamsAccumulator: VaryParamsAccumulator | null,
isRuntimePrefetchable: boolean
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
diff --git a/packages/next/src/server/request/pathname.ts b/packages/next/src/server/request/pathname.ts
index c1a6433bcdacb0..ea54f7f0a4ad0d 100644
--- a/packages/next/src/server/request/pathname.ts
+++ b/packages/next/src/server/request/pathname.ts
@@ -1,4 +1,7 @@
-import type { WorkStore } from '../app-render/work-async-storage.external'
+import {
+ workAsyncStorage,
+ type WorkStore,
+} from '../app-render/work-async-storage.external'
import {
postponeWithTracking,
@@ -19,9 +22,12 @@ import {
import { InvariantError } from '../../shared/lib/invariant-error'
export function createServerPathnameForMetadata(
- underlyingPathname: string,
- workStore: WorkStore
+ underlyingPathname: string
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
diff --git a/packages/next/src/server/request/search-params.ts b/packages/next/src/server/request/search-params.ts
index 46d4774761aed0..7dae0de5c26aef 100644
--- a/packages/next/src/server/request/search-params.ts
+++ b/packages/next/src/server/request/search-params.ts
@@ -1,4 +1,7 @@
-import type { WorkStore } from '../app-render/work-async-storage.external'
+import {
+ workAsyncStorage,
+ type WorkStore,
+} from '../app-render/work-async-storage.external'
import type { VaryParamsAccumulator } from '../app-render/vary-params'
import {
createVaryingSearchParams,
@@ -42,9 +45,12 @@ import { RenderStage } from '../app-render/staged-rendering'
export type SearchParams = { [key: string]: string | string[] | undefined }
export function createSearchParamsFromClient(
- underlyingSearchParams: SearchParams,
- workStore: WorkStore
+ underlyingSearchParams: SearchParams
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
@@ -86,13 +92,11 @@ export function createSearchParamsFromClient(
// TODO: metadata should inherit the runtime prefetchability of the page segment
const metadataIsRuntimePrefetchable = false
export function createServerSearchParamsForMetadata(
- underlyingSearchParams: SearchParams,
- workStore: WorkStore
+ underlyingSearchParams: SearchParams
): Promise {
const metadataVaryParamsAccumulator = getMetadataVaryParamsAccumulator()
return createServerSearchParamsForServerPage(
underlyingSearchParams,
- workStore,
metadataVaryParamsAccumulator,
metadataIsRuntimePrefetchable
)
@@ -100,10 +104,13 @@ export function createServerSearchParamsForMetadata(
export function createServerSearchParamsForServerPage(
underlyingSearchParams: SearchParams,
- workStore: WorkStore,
varyParamsAccumulator: VaryParamsAccumulator | null,
isRuntimePrefetchable: boolean
): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
switch (workUnitStore.type) {
@@ -143,9 +150,11 @@ export function createServerSearchParamsForServerPage(
throwInvariantForMissingStore()
}
-export function createPrerenderSearchParamsForClientPage(
- workStore: WorkStore
-): Promise {
+export function createPrerenderSearchParamsForClientPage(): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
if (workStore.forceStatic) {
// When using forceStatic we override all other logic and always just return an empty
// dictionary object.
@@ -381,9 +390,11 @@ function makeErroringSearchParams(
* error on access, because accessing searchParams inside of `"use cache"` is
* not allowed.
*/
-export function makeErroringSearchParamsForUseCache(
- workStore: WorkStore
-): Promise {
+export function makeErroringSearchParamsForUseCache(): Promise {
+ const workStore = workAsyncStorage.getStore()
+ if (!workStore) {
+ throw new InvariantError('Expected workStore to be initialized')
+ }
const cachedSearchParams = CachedSearchParamsForUseCache.get(workStore)
if (cachedSearchParams) {
return cachedSearchParams
diff --git a/packages/next/src/server/route-modules/app-route/module.ts b/packages/next/src/server/route-modules/app-route/module.ts
index efd0639ca3cf45..46b9538affd463 100644
--- a/packages/next/src/server/route-modules/app-route/module.ts
+++ b/packages/next/src/server/route-modules/app-route/module.ts
@@ -323,10 +323,7 @@ export class AppRouteRouteModule extends RouteModule<
const handlerContext: AppRouteHandlerFnContext = {
params: context.params
- ? createServerParamsForRoute(
- parsedUrlQueryToParams(context.params),
- workStore
- )
+ ? createServerParamsForRoute(parsedUrlQueryToParams(context.params))
: undefined,
}
diff --git a/packages/next/src/server/use-cache/use-cache-wrapper.ts b/packages/next/src/server/use-cache/use-cache-wrapper.ts
index 2956ba8cb7cfd9..275434ba8f9f3c 100644
--- a/packages/next/src/server/use-cache/use-cache-wrapper.ts
+++ b/packages/next/src/server/use-cache/use-cache-wrapper.ts
@@ -1139,7 +1139,7 @@ export async function cache(
// using a hanging promise for search params. For cached pages
// that do access them, which is an invalid dynamic usage, we
// need to ensure that an error is shown.
- makeErroringSearchParamsForUseCache(workStore),
+ makeErroringSearchParamsForUseCache(),
},
...otherInnerArgs,
]),
diff --git a/packages/next/src/server/web/sandbox/sandbox.ts b/packages/next/src/server/web/sandbox/sandbox.ts
index 7af41b9a5fcf6e..7ccdddbaa9c2f4 100644
--- a/packages/next/src/server/web/sandbox/sandbox.ts
+++ b/packages/next/src/server/web/sandbox/sandbox.ts
@@ -37,6 +37,7 @@ interface RunnerFnParams {
distDir: string
incrementalCache?: any
serverComponentsHmrCache?: ServerComponentsHmrCache
+ deploymentId: string
}
type RunnerFn = (params: RunnerFnParams) => Promise
@@ -96,6 +97,12 @@ export async function getRuntimeContext(
params.serverComponentsHmrCache
}
+ if (params.deploymentId) {
+ runtime.context.globalThis.NEXT_CLIENT_ASSET_SUFFIX = params.deploymentId
+ ? `?dpl=${params.deploymentId}`
+ : ''
+ }
+
for (const paramPath of params.paths) {
evaluateInContext(paramPath)
}
diff --git a/packages/next/src/types.ts b/packages/next/src/types.ts
index 485e9dd0ad8682..62c366901d6eb5 100644
--- a/packages/next/src/types.ts
+++ b/packages/next/src/types.ts
@@ -40,6 +40,8 @@ export type {
ResolvedViewport,
} from './lib/metadata/types/metadata-interface'
+export type { Instant } from './build/segment-config/app/app-segment-config'
+
export type { Instrumentation } from './server/instrumentation/types'
/**
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index 6645a8691e80e1..bbe1346cb26939 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json
index f0ee93a605e3e3..2cc8c0dd12bf0d 100644
--- a/packages/third-parties/package.json
+++ b/packages/third-parties/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/third-parties",
- "version": "16.2.0-canary.52",
+ "version": "16.2.0-canary.53",
"repository": {
"url": "vercel/next.js",
"directory": "packages/third-parties"
@@ -26,7 +26,7 @@
"third-party-capital": "1.0.20"
},
"devDependencies": {
- "next": "16.2.0-canary.52",
+ "next": "16.2.0-canary.53",
"outdent": "0.8.0",
"prettier": "2.5.1",
"typescript": "5.9.2"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4bf0f09d2cc567..e89517ec048375 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1011,7 +1011,7 @@ importers:
packages/eslint-config-next:
dependencies:
'@next/eslint-plugin-next':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../eslint-plugin-next
eslint:
specifier: '>=9.0.0'
@@ -1088,7 +1088,7 @@ importers:
packages/next:
dependencies:
'@next/env':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../next-env
'@swc/helpers':
specifier: 0.5.15
@@ -1216,19 +1216,19 @@ importers:
specifier: 1.2.0
version: 1.2.0
'@next/font':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../font
'@next/polyfill-module':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../next-polyfill-module
'@next/polyfill-nomodule':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../next-polyfill-nomodule
'@next/react-refresh-utils':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../react-refresh-utils
'@next/swc':
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../next-swc
'@opentelemetry/api':
specifier: 1.6.0
@@ -1943,7 +1943,7 @@ importers:
version: 1.0.20
devDependencies:
next:
- specifier: 16.2.0-canary.52
+ specifier: 16.2.0-canary.53
version: link:../next
outdent:
specifier: 0.8.0
diff --git a/test/development/app-dir/cache-only-toggle/app/layout.tsx b/test/development/app-dir/cache-only-toggle/app/layout.tsx
new file mode 100644
index 00000000000000..e97632058d22f5
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/app/layout.tsx
@@ -0,0 +1,20 @@
+import { ReactNode } from 'react'
+export default function Root({ children }: { children: ReactNode }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/test/development/app-dir/cache-only-toggle/app/page.tsx b/test/development/app-dir/cache-only-toggle/app/page.tsx
new file mode 100644
index 00000000000000..deca6128f04b47
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/app/page.tsx
@@ -0,0 +1,49 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+
+
Instant Navigation Mode Demo
+
+ This fixture tests the Instant Navigation Mode toggle
+ in Next.js Dev Tools. When enabled, navigations show only the cached or
+ prefetched state — dynamic data is not streamed.
+
+
How to test
+
+
+ Open Next.js Dev Tools (click the Next.js logo in the
+ corner).
+
+
+ Toggle Instant Navigation Mode to On . The
+ indicator turns blue.
+
+
+ Click the link below. You should see the loading skeleton instead of
+ the final page content.
+
+
+ Click the blue Instant UI only indicator to unblock dynamic
+ data and resume normal navigation.
+
+
+
+
+ Go to target page →
+
+
+
+ )
+}
diff --git a/test/development/app-dir/cache-only-toggle/app/target-page/loading.tsx b/test/development/app-dir/cache-only-toggle/app/target-page/loading.tsx
new file mode 100644
index 00000000000000..e1ea6e030f2112
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/app/target-page/loading.tsx
@@ -0,0 +1,54 @@
+export default function Loading() {
+ return (
+
+
+
+
+
+
+
+ {[0, 1, 2].map((i) => (
+
+ ))}
+
+
+ )
+}
diff --git a/test/development/app-dir/cache-only-toggle/app/target-page/page.tsx b/test/development/app-dir/cache-only-toggle/app/target-page/page.tsx
new file mode 100644
index 00000000000000..99cabfb0b1a90e
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/app/target-page/page.tsx
@@ -0,0 +1,126 @@
+import { Suspense } from 'react'
+import { connection } from 'next/server'
+import Link from 'next/link'
+
+function Skeleton({
+ width,
+ height,
+ style,
+}: {
+ width: string | number
+ height: number
+ style?: React.CSSProperties
+}) {
+ return (
+
+ )
+}
+
+async function DynamicComments() {
+ await connection()
+
+ const comments = [
+ { author: 'Alice', text: 'This loaded dynamically via streaming.' },
+ {
+ author: 'Bob',
+ text: 'With Instant Navigation Mode on, you would see the skeleton instead.',
+ },
+ {
+ author: 'Charlie',
+ text: 'Click the "Instant UI only" indicator to unblock dynamic data.',
+ },
+ ]
+
+ return (
+
+ {comments.map((c, i) => (
+
+
{c.author}
+
{c.text}
+
+ ))}
+
+ )
+}
+
+function CommentsSkeleton() {
+ return (
+
+ {[0, 1, 2].map((i) => (
+
+
+
+
+ ))}
+
+ )
+}
+
+export default function TargetPage() {
+ return (
+
+
+
Target Page
+
+ The heading and this paragraph are static — they appear instantly. The
+ comments below are dynamic and stream in after the shell.
+
+
Comments
+
+ }>
+
+
+
+
+
+ ← Back to home
+
+
+
+ )
+}
diff --git a/test/development/app-dir/cache-only-toggle/cache-only-toggle.test.ts b/test/development/app-dir/cache-only-toggle/cache-only-toggle.test.ts
new file mode 100644
index 00000000000000..8e70be55dc9892
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/cache-only-toggle.test.ts
@@ -0,0 +1,188 @@
+import { nextTestSetup } from 'e2e-utils'
+import {
+ retry,
+ waitForDevToolsIndicator,
+ toggleDevToolsIndicatorPopover,
+} from 'next-test-utils'
+
+describe('instant-mode-toggle', () => {
+ const { next } = nextTestSetup({
+ files: __dirname,
+ })
+
+ async function clearInstantModeCookie(browser: any) {
+ await browser.eval(() => {
+ document.cookie = 'next-instant-navigation-testing=; path=/; max-age=0'
+ })
+ }
+
+ async function clickInstantModeMenuItem(browser: any) {
+ await browser.eval(() => {
+ const portal = [].slice
+ .call(document.querySelectorAll('nextjs-portal'))
+ .find((p: any) =>
+ p.shadowRoot.querySelector('[data-nextjs-toast]')
+ ) as any
+ portal?.shadowRoot?.querySelector('[data-cache-only]')?.click()
+ })
+ }
+
+ async function getInstantModeMenuValue(browser: any): Promise {
+ return browser.eval(() => {
+ const portal = [].slice
+ .call(document.querySelectorAll('nextjs-portal'))
+ .find((p: any) =>
+ p.shadowRoot.querySelector('[data-nextjs-toast]')
+ ) as any
+ return (
+ portal?.shadowRoot
+ ?.querySelector('[data-cache-only]')
+ ?.innerText.split('\n')
+ .pop() || ''
+ )
+ })
+ }
+
+ async function getBadgeStatus(browser: any): Promise {
+ return browser.eval(() => {
+ const portal = [].slice
+ .call(document.querySelectorAll('nextjs-portal'))
+ .find((p: any) =>
+ p.shadowRoot.querySelector('[data-nextjs-toast]')
+ ) as any
+ return (
+ portal?.shadowRoot
+ ?.querySelector('[data-next-badge]')
+ ?.getAttribute('data-status') || ''
+ )
+ })
+ }
+
+ it('should show "instant" status after toggling on, not stuck on "compiling"', async () => {
+ const browser = await next.browser('/')
+ await clearInstantModeCookie(browser)
+ await browser.waitForElementByCss('[data-testid="home-title"]')
+
+ // Wait for initial compilation to settle — the badge status should
+ // become "none" once compilation is done and no transient status is active
+ await retry(async () => {
+ const status = await getBadgeStatus(browser)
+ expect(status).toBe('none')
+ })
+
+ // Toggle instant mode on
+ await waitForDevToolsIndicator(browser)
+ await toggleDevToolsIndicatorPopover(browser)
+ await clickInstantModeMenuItem(browser)
+
+ // The badge status should settle to "instant", not stay on "compiling"
+ await retry(async () => {
+ const status = await getBadgeStatus(browser)
+ expect(status).toBe('instant')
+ })
+
+ // Clean up
+ await clearInstantModeCookie(browser)
+ })
+
+ it('should show "Instant mode" menu item and toggle it on', async () => {
+ const browser = await next.browser('/')
+ await clearInstantModeCookie(browser)
+
+ await waitForDevToolsIndicator(browser)
+ await toggleDevToolsIndicatorPopover(browser)
+
+ // Verify the "Instant mode" menu item shows "Off"
+ await retry(async () => {
+ const value = await getInstantModeMenuValue(browser)
+ expect(value).toBe('Off')
+ })
+
+ // Click to toggle on — this also closes the menu
+ await clickInstantModeMenuItem(browser)
+
+ // Verify the badge appears with data-cache-only="true"
+ await retry(async () => {
+ const badge = await browser.elementByCss('[data-next-badge]')
+ const attr = await badge.getAttribute('data-cache-only')
+ expect(attr).toBe('true')
+ })
+
+ // Verify the status indicator shows "Instant..."
+ await retry(async () => {
+ const hasIndicator = await browser.hasElementByCss(
+ '[data-indicator-status]'
+ )
+ expect(hasIndicator).toBe(true)
+ })
+
+ // Clean up
+ await clearInstantModeCookie(browser)
+ })
+
+ it('should show loading skeleton during SPA navigation when instant mode is on', async () => {
+ const browser = await next.browser('/')
+ await clearInstantModeCookie(browser)
+ await browser.waitForElementByCss('[data-testid="home-title"]')
+
+ // Toggle instant mode on
+ await toggleDevToolsIndicatorPopover(browser)
+ await clickInstantModeMenuItem(browser)
+
+ // Wait for instant mode to be active
+ await retry(async () => {
+ const badge = await browser.elementByCss('[data-next-badge]')
+ const attr = await badge.getAttribute('data-cache-only')
+ expect(attr).toBe('true')
+ })
+
+ // Navigate to target page via SPA
+ await browser.elementByCss('#link-to-target').click()
+
+ // The comments skeleton should be visible (dynamic content is locked)
+ // while static content (heading, paragraph) is already rendered
+ await retry(async () => {
+ const skeleton = await browser.hasElementByCss(
+ '[data-testid="comments-skeleton"]'
+ )
+ expect(skeleton).toBe(true)
+ })
+
+ // Clean up
+ await clearInstantModeCookie(browser)
+ })
+
+ it('should turn off instant mode when clicking the badge', async () => {
+ const browser = await next.browser('/')
+ await clearInstantModeCookie(browser)
+ await browser.waitForElementByCss('[data-testid="home-title"]')
+
+ // Toggle instant mode on via menu
+ await toggleDevToolsIndicatorPopover(browser)
+ await clickInstantModeMenuItem(browser)
+
+ // Verify it's on
+ await retry(async () => {
+ const badge = await browser.elementByCss('[data-next-badge]')
+ const attr = await badge.getAttribute('data-cache-only')
+ expect(attr).toBe('true')
+ })
+
+ // Click the "Instant..." status indicator to unlock — this clears the cookie and reloads
+ await browser.eval(() => {
+ const portal = [].slice
+ .call(document.querySelectorAll('nextjs-portal'))
+ .find((p: any) =>
+ p.shadowRoot.querySelector('[data-nextjs-toast]')
+ ) as any
+ portal?.shadowRoot?.querySelector('[data-indicator-status]')?.click()
+ })
+
+ // After reload, instant mode should be off
+ await retry(async () => {
+ const badge = await browser.elementByCss('[data-next-badge]')
+ const attr = await badge.getAttribute('data-cache-only')
+ expect(attr).toBe('false')
+ })
+ })
+})
diff --git a/test/development/app-dir/cache-only-toggle/next.config.js b/test/development/app-dir/cache-only-toggle/next.config.js
new file mode 100644
index 00000000000000..346b5a53107ccb
--- /dev/null
+++ b/test/development/app-dir/cache-only-toggle/next.config.js
@@ -0,0 +1,11 @@
+/**
+ * @type {import('next').NextConfig}
+ */
+const nextConfig = {
+ cacheComponents: true,
+ experimental: {
+ instantNavigationDevToolsToggle: true,
+ },
+}
+
+module.exports = nextConfig
diff --git a/test/e2e/url-deployment-id/app/api/route.js b/test/e2e/url-deployment-id/app/api/route.js
deleted file mode 100644
index 2d4b4ca64c8c22..00000000000000
--- a/test/e2e/url-deployment-id/app/api/route.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import imported from '../../public/test.png'
-const url = new URL('../../public/test.png', import.meta.url).toString()
-
-export function GET() {
- return Response.json({ imported, url })
-}
diff --git a/test/e2e/url-deployment-id/app/client/page.js b/test/e2e/url-deployment-id/app/client/page.js
deleted file mode 100644
index 62dc52a20ab4db..00000000000000
--- a/test/e2e/url-deployment-id/app/client/page.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use client'
-import imported from '../../public/test.png'
-const url = new URL('../../public/test.png', import.meta.url).toString()
-
-export default function Page() {
- return (
-
- {imported.src}
- {url}
-
- )
-}
diff --git a/test/e2e/url-deployment-id/app/dynamic-client/layout.js b/test/e2e/url-deployment-id/app/dynamic-client/layout.js
deleted file mode 100644
index cb130459e80075..00000000000000
--- a/test/e2e/url-deployment-id/app/dynamic-client/layout.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { headers } from 'next/headers'
-
-export default async function Layout({ children }) {
- // Use headers() to opt into dynamic rendering
- await headers()
- return children
-}
diff --git a/test/e2e/url-deployment-id/app/dynamic-client/page.js b/test/e2e/url-deployment-id/app/dynamic-client/page.js
deleted file mode 100644
index 62dc52a20ab4db..00000000000000
--- a/test/e2e/url-deployment-id/app/dynamic-client/page.js
+++ /dev/null
@@ -1,12 +0,0 @@
-'use client'
-import imported from '../../public/test.png'
-const url = new URL('../../public/test.png', import.meta.url).toString()
-
-export default function Page() {
- return (
-
- {imported.src}
- {url}
-
- )
-}
diff --git a/test/e2e/url-deployment-id/app/dynamic/page.js b/test/e2e/url-deployment-id/app/dynamic/page.js
deleted file mode 100644
index 2e055ae8bc38ba..00000000000000
--- a/test/e2e/url-deployment-id/app/dynamic/page.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { headers } from 'next/headers'
-import imported from '../../public/test.png'
-const url = new URL('../../public/test.png', import.meta.url).toString()
-
-export default async function Page() {
- // Use headers() to opt into dynamic rendering
- await headers()
- return (
-
- {imported.src}
- {url}
-
- )
-}
diff --git a/test/e2e/url-deployment-id/app/layout.js b/test/e2e/url-deployment-id/app/layout.js
deleted file mode 100644
index 803f17d863c8ad..00000000000000
--- a/test/e2e/url-deployment-id/app/layout.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function RootLayout({ children }) {
- return (
-
- {children}
-
- )
-}
diff --git a/test/e2e/url-deployment-id/app/page.js b/test/e2e/url-deployment-id/app/page.js
deleted file mode 100644
index cc9db13229ee7d..00000000000000
--- a/test/e2e/url-deployment-id/app/page.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import imported from '../public/test.png'
-const url = new URL('../public/test.png', import.meta.url).toString()
-
-export default function Page() {
- return (
-
- {imported.src}
- {url}
-
- )
-}
diff --git a/test/e2e/url-deployment-id/next.config.js b/test/e2e/url-deployment-id/next.config.js
deleted file mode 100644
index cc5fdecd511209..00000000000000
--- a/test/e2e/url-deployment-id/next.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-module.exports = {
- deploymentId: 'test-deployment-id',
-}
diff --git a/test/e2e/url-deployment-id/public/test.png b/test/e2e/url-deployment-id/public/test.png
deleted file mode 100644
index cb137a989e5ffe..00000000000000
Binary files a/test/e2e/url-deployment-id/public/test.png and /dev/null differ
diff --git a/test/e2e/url-deployment-id/url-deployment-id.test.ts b/test/e2e/url-deployment-id/url-deployment-id.test.ts
deleted file mode 100644
index 2e08954f04ad58..00000000000000
--- a/test/e2e/url-deployment-id/url-deployment-id.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { nextTestSetup } from 'e2e-utils'
-
-// Skip for webpack - dpl suffix in asset URLs is only implemented for Turbopack
-;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)(
- 'URL asset references with deploymentId',
- () => {
- const { next, skipped } = nextTestSetup({
- files: __dirname,
- skipDeployment: true,
- })
-
- if (skipped) {
- return
- }
-
- const deploymentId = 'test-deployment-id'
-
- describe('import src attribute', () => {
- it('should include dpl query in RSC page', async () => {
- const $ = await next.render$('/')
- const src = $('#imported-src').text()
- expect(src).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in client page', async () => {
- const $ = await next.render$('/client')
- const src = $('#imported-src').text()
- expect(src).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in client page (after hydration)', async () => {
- const browser = await next.browser('/client')
- const url = await browser.elementByCss('#imported-src').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
- })
-
- describe('new URL() pattern', () => {
- it('should include dpl query in RSC page', async () => {
- const $ = await next.render$('/')
- const url = $('#new-url').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in client page', async () => {
- const $ = await next.render$('/client')
- const url = $('#new-url').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in client page (after hydration)', async () => {
- const browser = await next.browser('/client')
- const url = await browser.elementByCss('#new-url').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
- })
-
- describe('dynamic RSC page (headers)', () => {
- it('should include dpl query in dynamic RSC page', async () => {
- const $ = await next.render$('/dynamic')
- const src = $('#imported-src').text()
- expect(src).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in new URL pattern', async () => {
- const $ = await next.render$('/dynamic')
- const url = $('#new-url').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
- })
-
- describe('dynamic client page (headers in layout)', () => {
- it('should include dpl query in dynamic client page', async () => {
- const $ = await next.render$('/dynamic-client')
- const src = $('#imported-src').text()
- expect(src).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query in new URL pattern', async () => {
- const $ = await next.render$('/dynamic-client')
- const url = $('#new-url').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
-
- it('should include dpl query after hydration', async () => {
- const browser = await next.browser('/dynamic-client')
- const url = await browser.elementByCss('#imported-src').text()
- expect(url).toContain('dpl=' + deploymentId)
- })
- })
-
- describe('API route', () => {
- it('should return import src with dpl query', async () => {
- const data = await next
- .fetch('/api')
- .then((res) => res.ok && res.json())
- expect(data.imported.src).toContain('dpl=' + deploymentId)
- })
-
- it('should return new URL with dpl query', async () => {
- const data = await next
- .fetch('/api')
- .then((res) => res.ok && res.json())
- expect(data.url).not.toContain('dpl=')
- })
- })
- }
-)
diff --git a/test/e2e/url/app/api/route.js b/test/e2e/url/app/api/route.js
index 8e7a6b6dfd49e9..67fc664fb62eb3 100644
--- a/test/e2e/url/app/api/route.js
+++ b/test/e2e/url/app/api/route.js
@@ -1,6 +1,16 @@
+import fs from 'fs'
+import { fileURLToPath } from 'url'
+
import imported from '../../public/vercel.png'
-const url = new URL('../../public/vercel.png', import.meta.url).toString()
+const url = new URL('../../public/vercel.png', import.meta.url)
export function GET(req, res) {
- return Response.json({ imported, url })
+ let size
+ try {
+ size = fs.readFileSync(fileURLToPath(url)).length
+ } catch (e) {
+ size = e.message
+ }
+
+ return Response.json({ imported: imported.src, url: url.toString(), size })
}
diff --git a/test/e2e/url/app/opengraph-image.js b/test/e2e/url/app/opengraph-image.js
index 07cd809ad1b0fb..f6e762acbcf263 100644
--- a/test/e2e/url/app/opengraph-image.js
+++ b/test/e2e/url/app/opengraph-image.js
@@ -5,5 +5,5 @@ export const contentType = 'text/json'
// Image generation
export default async function Image() {
- return Response.json({ imported, url })
+ return Response.json({ imported: imported.src, url })
}
diff --git a/test/e2e/url/middleware.ts b/test/e2e/url/middleware.ts
index 1d87d3d5bb57c1..2f9ae73c0c10cd 100644
--- a/test/e2e/url/middleware.ts
+++ b/test/e2e/url/middleware.ts
@@ -6,7 +6,7 @@ const url = new URL('./public/vercel.png', import.meta.url).toString()
export async function middleware(req: NextRequest) {
if (req.nextUrl.toString().endsWith('/middleware')) {
- return Response.json({ imported, url })
+ return Response.json({ imported: imported.src, url })
}
return NextResponse.next()
diff --git a/test/e2e/url/next.config.js b/test/e2e/url/next.config.js
new file mode 100644
index 00000000000000..767719fc4fba59
--- /dev/null
+++ b/test/e2e/url/next.config.js
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {}
+
+module.exports = nextConfig
diff --git a/test/e2e/url/pages/api/pages-edge/index.js b/test/e2e/url/pages/api/pages-edge/index.js
index d124c191ac55ef..68d074acefbc5a 100644
--- a/test/e2e/url/pages/api/pages-edge/index.js
+++ b/test/e2e/url/pages/api/pages-edge/index.js
@@ -4,7 +4,7 @@ const url = new URL('../../../public/vercel.png', import.meta.url)
export default (req, res) => {
return new Response(
JSON.stringify({
- imported,
+ imported: imported.src,
url: url.toString(),
})
)
diff --git a/test/e2e/url/pages/api/pages/index.js b/test/e2e/url/pages/api/pages/index.js
index 6e1a9c032b0d4d..5f88a5340eb766 100644
--- a/test/e2e/url/pages/api/pages/index.js
+++ b/test/e2e/url/pages/api/pages/index.js
@@ -1,4 +1,5 @@
import fs from 'fs'
+import { fileURLToPath } from 'url'
import imported from '../../../public/vercel.png'
const url = new URL('../../../public/vercel.png', import.meta.url)
@@ -6,13 +7,13 @@ const url = new URL('../../../public/vercel.png', import.meta.url)
export default (req, res) => {
let size
try {
- size = fs.readFileSync(url).length
+ size = fs.readFileSync(fileURLToPath(url)).length
} catch (e) {
size = e.message
}
res.send({
- imported,
+ imported: imported.src,
url: url.toString(),
size,
})
diff --git a/test/e2e/url/url.test.ts b/test/e2e/url/url.test.ts
index a89ead35b292eb..59ad7b638a2ecd 100644
--- a/test/e2e/url/url.test.ts
+++ b/test/e2e/url/url.test.ts
@@ -1,5 +1,5 @@
import { retry } from 'next-test-utils'
-import { nextTestSetup } from 'e2e-utils'
+import { isNextDev, isNextStart, nextTestSetup } from 'e2e-utils'
// | | Pages Client | Pages Server (SSR,RSC) | API Routes/Middleware/Metadata |
// |---------|-------------------------|-------------------------|--------------------------------|
@@ -11,9 +11,12 @@ import { nextTestSetup } from 'e2e-utils'
// - a bug where App Router API routes (and Metadata) return client assets for `new URL`s.
// - a bug where Edge Page routes return client assets for `new URL`s.
describe(`Handle new URL asset references`, () => {
- const { next, skipped } = nextTestSetup({
+ const { next, skipped, isTurbopack } = nextTestSetup({
files: __dirname,
- // Workaround for `Error: invariant: htmlFsRef != null && jsonFsRef != null /ssg` errors
+ env: {
+ // rely on skew protection when deployed
+ NEXT_DEPLOYMENT_ID: isNextStart ? 'test-deployment-id' : undefined,
+ },
skipDeployment: true,
})
@@ -21,81 +24,89 @@ describe(`Handle new URL asset references`, () => {
return
}
- const serverFilePath = expect.stringMatching(
- /file:.*\/.next(\/dev)?\/server\/.*\/vercel\.[0-9a-f]{8}\.png$/
- )
- const serverEdgeUrl = expect.stringMatching(
- /^blob:.*vercel\.[0-9a-f]{8,}\.png$/
- )
- const clientFilePath = expect.stringMatching(
- /^\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png$/
+ const serverFileRegex = expect.stringMatching(
+ /file:.*\/.next(\/dev)?\/server\/.*\/vercel.HASH.png$/
)
+ const serverEdgeUrl = isTurbopack
+ ? `blob:server/edge/assets/vercel.HASH.png`
+ : `blob:vercel.HASH.png`
+
+ let clientUrl: string
+ const expectedPageContent = (count: number) =>
+ 'Hello ' + Array(count).fill(clientUrl).join('+')
+
+ beforeAll(() => {
+ let expectedToken
+ if (isNextDev || !isTurbopack) {
+ expectedToken = undefined
+ } else {
+ expectedToken = next.deploymentId
+ if (!expectedToken) {
+ throw new Error('Missing deployment id')
+ }
+ }
+ clientUrl = `/_next/static/media/vercel.HASH.png${expectedToken ? `?dpl=${expectedToken}` : ''}`
+ })
it('should respond on middleware api', async () => {
const data = await next
.fetch('/middleware')
- .then((res) => res.ok && res.json())
+ .then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
- imported: expect.objectContaining({
- src: clientFilePath,
- }),
+ expect(json).toEqual({
+ imported: clientUrl,
url: serverEdgeUrl,
})
})
- const expectedPage =
- /^Hello \/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png(\+\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png(\+\/_next\/static\/media\/vercel\.[0-9a-f]{8}\.png)?)?$/
-
describe('app router', () => {
it('should respond on webmanifest', async () => {
const data = await next
.fetch('/manifest.webmanifest')
- .then((res) => res.ok && res.json())
+ .then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
+ expect(json).toEqual({
short_name: 'Next.js',
name: 'Next.js',
icons: [
{
- src: clientFilePath,
+ src: clientUrl,
type: 'image/png',
sizes: '512x512',
},
],
// TODO Webpack bug?
- description: process.env.IS_TURBOPACK_TEST
- ? serverFilePath
- : clientFilePath,
+ description: isTurbopack ? serverFileRegex : clientUrl,
})
})
it('should respond on opengraph-image', async () => {
const data = await next
.fetch('/opengraph-image')
- .then((res) => res.ok && res.json())
+ .then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
- imported: expect.objectContaining({
- src: clientFilePath,
- }),
+ expect(json).toEqual({
+ imported: clientUrl,
// TODO Webpack bug?
- url: process.env.IS_TURBOPACK_TEST ? serverFilePath : clientFilePath,
+ url: isTurbopack ? serverFileRegex : clientUrl,
})
})
for (const page of ['/rsc', '/rsc-edge', '/client', '/client-edge']) {
// TODO Webpack bug?
- let shouldSkip = process.env.IS_TURBOPACK_TEST
- ? false
- : page.includes('edge')
+ let shouldSkip = isTurbopack ? false : page.includes('edge')
;(shouldSkip ? it.skip : it)(
`should render the ${page} page`,
async () => {
const $ = await next.render$(page)
// eslint-disable-next-line jest/no-standalone-expect
- expect($('main').text()).toMatch(expectedPage)
+ expect(stripVercelPngHash($('main').text())).toEqual(
+ expectedPageContent(2)
+ )
}
)
;(shouldSkip ? it.skip : it)(
@@ -103,46 +114,46 @@ describe(`Handle new URL asset references`, () => {
async () => {
const browser = await next.browser(page)
await retry(async () =>
- expect(await browser.elementByCss('main').text()).toMatch(
- expectedPage
- )
+ expect(
+ stripVercelPngHash(await browser.elementByCss('main').text())
+ ).toEqual(expectedPageContent(2))
)
}
)
}
it('should respond on API', async () => {
- const data = await next.fetch('/api').then((res) => res.ok && res.json())
+ const data = await next.fetch('/api').then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
- imported: expect.objectContaining({
- src: clientFilePath,
- }),
+ expect(json).toEqual({
+ imported: clientUrl,
// TODO Webpack bug?
- url: process.env.IS_TURBOPACK_TEST ? serverFilePath : clientFilePath,
+ url: isTurbopack ? serverFileRegex : clientUrl,
+ size: isTurbopack ? 30079 : expect.toBeString(),
})
})
})
describe('pages router', () => {
- for (const page of [
- '/pages/static',
- '/pages/ssr',
- '/pages/ssg',
- '/pages-edge/static',
- '/pages-edge/ssr',
- ]) {
+ for (const [page, count] of [
+ ['/pages/static', 2],
+ ['/pages/ssr', 3],
+ ['/pages/ssg', 3],
+ ['/pages-edge/static', 2],
+ ['/pages-edge/ssr', 3],
+ ] as const) {
// TODO Webpack bug?
- let shouldSkip = process.env.IS_TURBOPACK_TEST
- ? false
- : page.includes('edge')
+ let shouldSkip = isTurbopack ? false : page.includes('edge')
;(shouldSkip ? it.skip : it)(
`should render the ${page} page`,
async () => {
const $ = await next.render$(page)
// eslint-disable-next-line jest/no-standalone-expect
- expect($('main').text()).toMatch(expectedPage)
+ expect(stripVercelPngHash($('main').text())).toEqual(
+ expectedPageContent(count)
+ )
}
)
;(shouldSkip ? it.skip : it)(
@@ -150,9 +161,9 @@ describe(`Handle new URL asset references`, () => {
async () => {
const browser = await next.browser(page)
await retry(async () =>
- expect(await browser.elementByCss('main').text()).toMatch(
- expectedPage
- )
+ expect(
+ stripVercelPngHash(await browser.elementByCss('main').text())
+ ).toEqual(expectedPageContent(count))
)
}
)
@@ -161,13 +172,12 @@ describe(`Handle new URL asset references`, () => {
it('should respond on API', async () => {
const data = await next
.fetch('/api/pages/')
- .then((res) => res.ok && res.json())
+ .then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
- imported: expect.objectContaining({
- src: clientFilePath,
- }),
- url: serverFilePath,
+ expect(json).toEqual({
+ imported: clientUrl,
+ url: serverFileRegex,
size: 30079,
})
})
@@ -175,14 +185,17 @@ describe(`Handle new URL asset references`, () => {
it('should respond on edge API', async () => {
const data = await next
.fetch('/api/pages-edge/')
- .then((res) => res.ok && res.json())
+ .then((res) => res.ok && res.text())
+ const json = JSON.parse(stripVercelPngHash(data))
- expect(data).toEqual({
- imported: expect.objectContaining({
- src: clientFilePath,
- }),
+ expect(json).toEqual({
+ imported: clientUrl,
url: serverEdgeUrl,
})
})
})
})
+
+function stripVercelPngHash(text: string) {
+ return text.replace(/vercel\.[0-9a-f]{8,}\.png/g, 'vercel.HASH.png')
+}
diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts
index 9381d46ff5f5f3..a631d6287ba6d1 100644
--- a/test/lib/next-modes/base.ts
+++ b/test/lib/next-modes/base.ts
@@ -284,7 +284,7 @@ export class NextInstance {
? // since we can't get the build id as a build artifact,
// add it in build logs
{
- 'post-build': `node -e 'console.log("BUILD" + "_ID: " + require("fs").readFileSync("${this.distDir}/BUILD_ID"))'`,
+ 'post-build': `node -e 'console.log("BUILD" + "_ID: " + fs.readFileSync("${this.distDir}/BUILD_ID") + "\\nDEPLOYMENT_ID: " + process.env.NEXT_DEPLOYMENT_ID)'`,
}
: {}),
...pkgScripts,
@@ -639,6 +639,14 @@ export class NextInstance {
return ''
}
+ public get deploymentId(): string | undefined {
+ return undefined
+ }
+
+ public get deploymentIdQuery(): string {
+ return this.deploymentId ? `?dpl=${this.deploymentId}` : ''
+ }
+
public get cliOutput(): string {
return ''
}
diff --git a/test/lib/next-modes/next-deploy.ts b/test/lib/next-modes/next-deploy.ts
index 4f3f4f6e0ee699..10862443b684c9 100644
--- a/test/lib/next-modes/next-deploy.ts
+++ b/test/lib/next-modes/next-deploy.ts
@@ -9,6 +9,7 @@ import { Span } from 'next/dist/trace'
export class NextDeployInstance extends NextInstance {
private _cliOutput: string
private _buildId: string
+ private _deploymentId: string | undefined
private _writtenHostsLine: string | null = null
protected throwIfUnavailable(): void | never {
@@ -33,6 +34,10 @@ export class NextDeployInstance extends NextInstance {
return this._buildId
}
+ public get deploymentId() {
+ return this._deploymentId
+ }
+
private async deployUsingCustomScript(): Promise<{ url: string }> {
const deployScriptPath = process.env.NEXT_TEST_DEPLOY_SCRIPT_PATH!
@@ -162,13 +167,23 @@ export class NextDeployInstance extends NextInstance {
}
const buildId = this._cliOutput.match(/BUILD_ID: (.+)/)?.[1]?.trim()
-
if (!buildId) {
throw new Error(`Failed to get buildId from logs ${this._cliOutput}`)
}
this._buildId = buildId
+ const deploymentId = this._cliOutput
+ .match(/DEPLOYMENT_ID: (.+)/)?.[1]
+ ?.trim()
+ if (!deploymentId) {
+ throw new Error(
+ `Failed to get deploymentId from logs ${this._cliOutput}`
+ )
+ }
+ this._deploymentId = deploymentId
- require('console').log(`Got buildId: ${this._buildId}`)
+ require('console').log(
+ `Got buildId: ${this._buildId}, deploymentId: ${this._deploymentId}`
+ )
return
}
diff --git a/test/lib/next-modes/next-start.ts b/test/lib/next-modes/next-start.ts
index be2ffc93e54276..2b7a531fcb3532 100644
--- a/test/lib/next-modes/next-start.ts
+++ b/test/lib/next-modes/next-start.ts
@@ -8,6 +8,7 @@ import { quote as shellQuote } from 'shell-quote'
export class NextStartInstance extends NextInstance {
private _buildId: string
+ private _deploymentId: string | undefined
private _cliOutput: string = ''
private _prerenderFinishedTimeMS: number | null = null
@@ -16,6 +17,10 @@ export class NextStartInstance extends NextInstance {
return this._buildId
}
+ public get deploymentId() {
+ return this._deploymentId
+ }
+
public get cliOutput() {
return this._cliOutput
}
@@ -113,6 +118,21 @@ export class NextStartInstance extends NextInstance {
)
.catch(() => '')
).trim()
+
+ try {
+ const requiredServerFiles = JSON.parse(
+ await fs.readFile(
+ path.join(
+ this.testDir,
+ this.nextConfig?.distDir || '.next',
+ 'required-server-files.json'
+ ),
+ 'utf8'
+ )
+ )
+ this._deploymentId =
+ requiredServerFiles.config?.deploymentId || undefined
+ } catch {}
}
console.log('running', shellQuote(startArgs))
@@ -252,6 +272,20 @@ export class NextStartInstance extends NextInstance {
.catch(() => '')
).trim()
+ try {
+ const requiredServerFiles = JSON.parse(
+ await fs.readFile(
+ path.join(
+ this.testDir,
+ this.nextConfig?.distDir || '.next',
+ 'required-server-files.json'
+ ),
+ 'utf8'
+ )
+ )
+ this._deploymentId = requiredServerFiles.config?.deploymentId || undefined
+ } catch {}
+
return result
}
diff --git a/test/production/app-dir/build-output-prerender/build-output-prerender.test.ts b/test/production/app-dir/build-output-prerender/build-output-prerender.test.ts
index 5ddfb10d8d9b9a..47730b6b43515d 100644
--- a/test/production/app-dir/build-output-prerender/build-output-prerender.test.ts
+++ b/test/production/app-dir/build-output-prerender/build-output-prerender.test.ts
@@ -225,7 +225,7 @@ describe('build-output-prerender', () => {
expect(getPrerenderOutput(next.cliOutput)).toMatchInlineSnapshot(`
"Error: Route "/client" used \`new Date()\` inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time-client
at Page (webpack:///app/client/page.tsx:4:28)
- at ClientPageRoot (webpack:///src/client/components/client-page.tsx:74:12)
+ at ClientPageRoot (webpack:///src/client/components/client-page.tsx:61:12)
2 |
3 | export default function Page() {
> 4 | return Current time: {new Date().toISOString()}