diff --git a/.eslintignore b/.eslintignore index c0a1165bb2d41..d168d82d7a560 100644 --- a/.eslintignore +++ b/.eslintignore @@ -26,6 +26,7 @@ packages/next-env/**/*.d.ts packages/create-next-app/templates/** test/integration/eslint/** test/development/basic/legacy-decorators/**/* +test/production/emit-decorator-metadata/**/*.js test-timings.json packages/next-swc/crates/** bench/nested-deps/pages/** diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8d45c2a88972c..569ca335626ae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,13 +12,8 @@ # Image Component (@styfle) -/examples/image-component/ @timneutkens @ijjk @shuding @styfle -/packages/next/build/webpack/loaders/next-image-loader.js @timneutkens @ijjk @shuding @styfle -/packages/next/client/image.tsx @timneutkens @ijjk @shuding @styfle -/packages/next/image-types/ @timneutkens @ijjk @shuding @styfle -/packages/next/server/image-config.ts @timneutkens @ijjk @shuding @styfle -/packages/next/server/image-optimizer.ts @timneutkens @ijjk @shuding @styfle +/**/*image*/** @timneutkens @ijjk @shuding @styfle +/packages/next/client/use-intersection.tsx @timneutkens @ijjk @shuding @styfle +/packages/next/server/lib/squoosh/ @timneutkens @ijjk @shuding @styfle /packages/next/server/serve-static.ts @timneutkens @ijjk @shuding @styfle /packages/next/server/config.ts @timneutkens @ijjk @shuding @styfle -/test/integration/image-optimizer/ @timneutkens @ijjk @shuding @styfle -/test/integration/image-component/ @timneutkens @ijjk @shuding @styfle diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index d0aa0b7589ace..dbce6318ee4af 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -7,49 +7,28 @@ body: value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. - type: markdown attributes: - value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - - type: markdown - attributes: - value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.' - - type: markdown - attributes: - value: 'next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line.' + value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions ["Help" section](https://github.com/vercel/next.js/discussions/categories/help). + - type: checkboxes + attributes: + label: Verify canary release + description: '`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.' + options: + - label: I verified that the issue exists in Next.js canary release + required: true - type: textarea attributes: - label: Run `next info` (available from version 12.0.8 and up) + label: Provide environment information description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. - validations: - required: false - - type: input - attributes: - label: What version of Next.js are you using? - description: 'For example: 10.0.1' validations: required: true - type: input attributes: - label: What version of Node.js are you using? - description: 'For example: 12.0.0' - validations: - required: true + label: What browser are you using? (if relevant) + description: 'Please specify the exact version. For example: Chrome 100.0.4878.0' - type: input attributes: - label: What browser are you using? - description: 'For example: Chrome, Safari' - validations: - required: true - - type: input - attributes: - label: What operating system are you using? - description: 'For example: macOS, Windows' - validations: - required: true - - type: input - attributes: - label: How are you deploying your application? + label: How are you deploying your application? (if relevant) description: 'For example: next start, next export, Vercel, Other platform' - validations: - required: true - type: textarea attributes: label: Describe the Bug diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index 8535a441bf4a7..04483f5a4518c 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -1,49 +1,40 @@ name: Example Bug Report -description: Create a bug report for the examples +description: Create a bug report for one of the Next.js examples labels: 'type: example,template: bug' body: - type: markdown attributes: - value: Thanks for taking the time to file a examples bug report! Please fill out this form as completely as possible. + value: Thanks for taking the time to file a bug report for [one of the examples](https://github.com/vercel/next.js/tree/canary/examples)! Please fill out this form as completely as possible. - type: markdown attributes: - value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - - type: input + value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions ["Help" section](https://github.com/vercel/next.js/discussions/categories/help). + - type: checkboxes attributes: - label: What example does this report relate to? - description: 'For example: with-styled-components' - validations: - required: true - - type: input - attributes: - label: What version of Next.js are you using? - description: 'For example: 10.0.1' - validations: - required: true - - type: input + label: Verify canary release + description: '`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.' + options: + - label: I verified that the issue exists in Next.js canary release + required: true + - type: textarea attributes: - label: What version of Node.js are you using? - description: 'For example: 12.0.0' + label: Provide environment information + description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. validations: required: true - type: input attributes: - label: What browser are you using? - description: 'For example: Chrome, Safari' + label: Which example does this report relate to? + description: "See a complete list in the [examples folder](https://github.com/vercel/next.js/tree/canary/examples). For example: with-styled-components. Note: Examples not in the examples folder might be maintained by the example's library author. Check out their projects before opening the issue on Next.js" validations: required: true - type: input attributes: - label: What operating system are you using? - description: 'For example: macOS, Windows' - validations: - required: true + label: What browser are you using? (if relevant) + description: 'Please specify the exact version. For example: Chrome 100.0.4878.0' - type: input attributes: - label: How are you deploying your application? + label: How are you deploying your application? (if relevant) description: 'For example: next start, next export, Vercel, Other platform' - validations: - required: true - type: textarea attributes: label: Describe the Bug diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 7906496c0d9ad..ff748b463394e 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -88,6 +88,46 @@ jobs: - run: ./scripts/check-manifests.js - run: yarn lint + rust-check: + runs-on: ubuntu-latest + needs: build + steps: + - name: Install + uses: actions-rs/toolchain@v1 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + profile: minimal + toolchain: nightly-2021-11-15 + components: rustfmt, clippy + + - name: Cache cargo registry + uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + path: ~/.cargo/registry + key: stable-ubuntu-clippy-cargo-registry + + - name: Cache cargo index + uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + path: ~/.cargo/git + key: stable-ubuntu-clippy-cargo-index + + - uses: actions/cache@v2 + id: restore-build + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - name: Check + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + run: | + cargo fmt -- --check + cargo clippy --all -- -D warnings + working-directory: packages/next-swc + checkPrecompiled: name: Check Pre-compiled runs-on: ubuntu-latest @@ -97,7 +137,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -141,7 +181,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -171,7 +211,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -201,6 +241,52 @@ jobs: name: Run test/development if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - name: Upload test trace + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-trace + if-no-files-found: ignore + retention-days: 2 + path: | + test/traces + + testDevE2E: + name: Test Development (E2E) + runs-on: ubuntu-latest + needs: [build, build-native-dev] + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + node-version: 14 + + - run: echo ${{needs.build.outputs.docsChange}} + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: NEXT_TEST_MODE=dev node run-tests.js --type e2e name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -225,7 +311,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -255,6 +341,42 @@ jobs: name: Run test/production if: ${{needs.build.outputs.docsChange != 'docs only change'}} + testProdE2E: + name: Test Production (E2E) + runs-on: ubuntu-latest + needs: [build, build-native-dev] + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + node-version: 14 + + - run: echo ${{needs.build.outputs.docsChange}} + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: NEXT_TEST_MODE=start node run-tests.js --type e2e name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -270,11 +392,11 @@ jobs: strategy: fail-fast: false matrix: - group: [1, 2, 3, 4, 5, 6] + group: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -291,6 +413,10 @@ jobs: path: ./* key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + - uses: pnpm/action-setup@v2.2.1 + with: + version: 6.32.2 + - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: @@ -300,7 +426,7 @@ jobs: - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 + - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/18 if: ${{needs.build.outputs.docsChange != 'docs only change'}} - name: Upload test trace @@ -324,7 +450,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -374,7 +500,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -409,7 +535,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -453,7 +579,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -490,7 +616,7 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 17 check-latest: true @@ -518,19 +644,12 @@ jobs: - build - build-wasm - build-native - - build-windows-i686 - - build-windows-aarch64 - - build-linux-musl - - build-linux-arm7 - - build-linux-aarch64-gnu - - build-android-aarch64 - - build-linux-aarch64-musl env: NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: node-version: 14 @@ -565,7 +684,6 @@ jobs: steps: - name: Setup node uses: actions/setup-node@v2 - if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} with: node-version: 14 @@ -606,7 +724,7 @@ jobs: uses: actions/setup-node@v2 if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: - node-version: 14 + node-version: 16 check-latest: true - name: Install @@ -617,18 +735,18 @@ jobs: toolchain: nightly-2021-11-15 - name: Cache cargo registry - uses: actions/cache@v1 + uses: actions/cache@v2 if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: path: ~/.cargo/registry - key: stable-ubuntu-18.04-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + key: stable-ubuntu-18.04-cargo-registry - name: Cache cargo index - uses: actions/cache@v1 + uses: actions/cache@v2 if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} with: path: ~/.cargo/git - key: stable-ubuntu-18.04-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + key: stable-ubuntu-18.04-cargo-index # We use week in the turbo cache key to keep the cache from infinitely growing - id: get-week @@ -660,9 +778,7 @@ jobs: # since the repo's dependencies aren't installed we need # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - + - run: npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 - name: Build if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} run: turbo run build-native --cache-dir=".turbo" @@ -747,95 +863,137 @@ jobs: # Build binaries for publishing build-native: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} strategy: + fail-fast: false matrix: - os: [ubuntu-18.04, macos-latest, windows-latest] - description: [default] - include: - - os: ubuntu-18.04 - target: x86_64-unknown-linux-gnu - name: linux-x64-gnu - - os: windows-latest - target: x86_64-pc-windows-msvc - name: win32-x64-msvc - - os: macos-latest - target: x86_64-apple-darwin - name: darwin-x64 - - os: macos-latest - target: aarch64-apple-darwin - name: darwin-arm64 - description: m1 - - name: next-swc - ${{ matrix.os }} - ${{ matrix.target }} - node@14 - runs-on: ${{ matrix.os }} - + settings: + - host: macos-latest + target: 'x86_64-apple-darwin' + build: | + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release + strip -x packages/next-swc/native/next-swc.*.node + - host: windows-latest + build: | + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release + target: 'x86_64-pc-windows-msvc' + - host: windows-latest + build: | + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target i686-pc-windows-msvc + target: 'i686-pc-windows-msvc' + - host: ubuntu-latest + target: 'x86_64-unknown-linux-gnu' + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine-zig + # Node.js in Baidu need to compatible with `GLIBC_2.12` + build: >- + set -e && + rustup toolchain install nightly-2021-11-15 && + rustup default nightly-2021-11-15 && + rustup target add x86_64-unknown-linux-gnu && + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 && + turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-gnu --zig --zig-abi-suffix 2.12 && + llvm-strip -x packages/next-swc/native/next-swc.*.node + - host: ubuntu-latest + target: 'x86_64-unknown-linux-musl' + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: >- + set -e && + rustup toolchain install nightly-2021-11-15 && + rustup default nightly-2021-11-15 && + rustup target add x86_64-unknown-linux-musl && + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 && + turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-musl && + strip packages/next-swc/native/next-swc.*.node + - host: macos-latest + target: 'aarch64-apple-darwin' + build: | + sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; + export CC=$(xcrun -f clang); + export CXX=$(xcrun -f clang++); + SYSROOT=$(xcrun --sdk macosx --show-sdk-path); + export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-apple-darwin + strip -x packages/next-swc/native/next-swc.*.node + - host: ubuntu-latest + target: 'aarch64-unknown-linux-gnu' + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine-zig + build: >- + set -e && + rustup toolchain install nightly-2021-11-15 && + rustup default nightly-2021-11-15 && + rustup target add aarch64-unknown-linux-gnu && + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 && + turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-gnu --zig --zig-abi-suffix 2.12 && + llvm-strip -x packages/next-swc/native/next-swc.*.node + - host: ubuntu-18.04 + target: 'armv7-unknown-linux-gnueabihf' + setup: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y + build: | + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target armv7-unknown-linux-gnueabihf + arm-linux-gnueabihf-strip packages/next-swc/native/next-swc.*.node + - host: ubuntu-latest + target: aarch64-linux-android + build: | + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang" + export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang" + export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++" + export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}" + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-linux-android + ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip packages/next-swc/native/next-swc.*.node + - host: ubuntu-latest + target: armv7-linux-androideabi + build: | + export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang" + export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang" + export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++" + export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}" + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target armv7-linux-androideabi + ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip packages/next-swc/native/next-swc.*.node + - host: ubuntu-latest + target: 'aarch64-unknown-linux-musl' + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: >- + set -e && + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 && + rustup toolchain install nightly-2021-11-15 && + rustup default nightly-2021-11-15 && + rustup target add aarch64-unknown-linux-musl && + turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-musl && + llvm-strip -x packages/next-swc/native/next-swc.*.node + - host: windows-latest + target: 'aarch64-pc-windows-msvc' + build: | + npm i -g @napi-rs/cli@2.4.4 turbo@1.0.28 + turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-pc-windows-msvc + if: ${{ needs.build.outputs.isRelease == 'true' }} + needs: build + name: stable - ${{ matrix.settings.target }} - node@16 + runs-on: ${{ matrix.settings.host }} steps: # https://github.com/actions/virtual-environments/issues/1187 - name: tune linux network run: sudo ethtool -K eth0 tx off rx off - if: ${{ matrix.os == 'ubuntu-18.04' }} + if: ${{ matrix.settings.host == 'ubuntu-18.04' }} + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + if: ${{ matrix.settings.host == 'ubuntu-latest' }} - name: tune windows network run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6 - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.settings.host == 'windows-latest' }} - name: tune mac network run: sudo sysctl -w net.link.generic.system.hwcksum_tx=0 && sudo sysctl -w net.link.generic.system.hwcksum_rx=0 - if: ${{ matrix.os == 'macos-latest' }} - - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - check-latest: true - + if: ${{ matrix.settings.host == 'macos-latest' }} # we use checkout here instead of the build cache since # it can fail to restore in different OS' - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - - name: Install - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-11-15 - target: ${{ matrix.target }} - - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: stable-${{ matrix.os }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: stable-${{ matrix.os }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} - - - name: Turbo cache - id: turbo-cache - uses: actions/cache@v2 - with: - path: .turbo - key: turbo-${{ github.job }}-${{ matrix.name }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}-${{ matrix.name }}- - turbo-${{ github.job }}-${{ matrix.name }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-${{ matrix.name }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Cross build aarch64 setup - if: ${{ matrix.target == 'aarch64-apple-darwin' }} - run: | - sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; - export CC=$(xcrun -f clang); - export CXX=$(xcrun -f clang++); - SYSROOT=$(xcrun --sdk macosx --show-sdk-path); - export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; # We use restore-key to pick latest cache. # We will not get exact match, but doc says # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." @@ -844,164 +1002,9 @@ jobs: uses: actions/cache@v2 with: path: ./packages/next-swc/target - key: next-swc-cargo-cache-${{ matrix.os }}--${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - next-swc-cargo-cache-${{ matrix.os }} - - - name: 'Build' - shell: bash - run: turbo run build-native --cache-dir=".turbo" -- --release --target ${{ matrix.target }} - env: - MACOSX_DEPLOYMENT_TARGET: '10.13' - - - name: Upload artifact - uses: actions/upload-artifact@v2.2.4 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.${{ matrix.name }}.node - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache - - build-windows-i686: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - windows-i686 - node@14 - runs-on: windows-latest - env: - CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32 - CARGO_PROFILE_RELEASE_LTO: 'false' - steps: - - name: Install node x86 - run: | - choco install nodejs-lts --x86 -y --force - refreshenv - - - name: Set 32bit Node.js path - run: | - echo "C:\\Program Files (x86)\\nodejs" >> $GITHUB_PATH - shell: bash - - - name: Node.js arch - run: node -e "console.log(process.arch)" - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-11-15 - override: true - target: i686-pc-windows-msvc - - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v2 - with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Build - shell: bash - run: turbo run build-native --cache-dir=".turbo" -- --release --target i686-pc-windows-msvc - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.win32-ia32-msvc.node - - build-windows-aarch64: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - windows-aarch64 - node@14 - runs-on: windows-latest - steps: - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-11-15 - override: true - target: aarch64-pc-windows-msvc - - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v2 - with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} + key: next-swc-cargo-cache-${{ matrix.settings.target }}--${{ hashFiles('**/Cargo.lock') }} restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Build - shell: bash - run: turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-pc-windows-msvc - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.win32-arm64-msvc.node - - build-linux-musl: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - linux-musl - node@lts - runs-on: ubuntu-latest - steps: - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - - name: Login to registry - run: | - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL - env: - DOCKER_REGISTRY_URL: ghcr.io - DOCKER_USERNAME: ${{ github.actor }} - DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: Cache - uses: actions/cache@v2 - with: - path: | - target/ - key: linux-musl-publish-integration - - - name: Pull docker image - run: | - docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine - docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder - + next-swc-cargo-cache-${{ matrix.settings.target }} - name: Turbo Cache id: turbo-cache uses: actions/cache@v2 @@ -1013,250 +1016,58 @@ jobs: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - name: 'Build' - run: | - docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder sh -c "npm i -g @napi-rs/cli@1.2.1 && npm i -g turbo@1.0.28 && turbo run build-native --cache-dir=".turbo" -- --release --target x86_64-unknown-linux-musl" - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.linux-x64-musl.node - - build-linux-aarch64-gnu: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - aarch64-unknown-linux-gnu - node@14 - runs-on: ubuntu-18.04 - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-11-15 - override: true - target: aarch64-unknown-linux-gnu - - - name: Cache - uses: actions/cache@v2 - with: - path: | - target/ - key: aarch64-linux-gnu-publish-integration - - - name: Install cross compile toolchain - run: | - sudo apt-get update - sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y - - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v2 - with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Cross build aarch64 - if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} - run: turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-gnu - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.linux-arm64-gnu.node - - build-linux-aarch64-musl: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - aarch64-unknown-linux-musl - node@14 - runs-on: ubuntu-18.04 - steps: - name: Setup node uses: actions/setup-node@v2 + if: ${{ !matrix.settings.docker }} with: - node-version: 14 - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 + node-version: 16 + check-latest: true + cache: yarn - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - name: Install Rust + - name: Install uses: actions-rs/toolchain@v1 + if: ${{ !matrix.settings.docker }} with: profile: minimal - toolchain: nightly-2021-11-15 override: true - target: aarch64-unknown-linux-musl - - - name: Cache - uses: actions/cache@v2 - with: - path: | - target/ - key: aarch64-linux-musl-publish-integration - - - name: Install cross compile toolchain - run: | - sudo apt-get update - sudo apt-get install gcc-aarch64-linux-gnu -y - - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v2 - with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Cross build aarch64 - run: turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-unknown-linux-musl - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.linux-arm64-musl.node - - build-linux-arm7: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - arm7-unknown-linux-gnu - node@14 - runs-on: ubuntu-18.04 - steps: - - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset - - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal toolchain: nightly-2021-11-15 - override: true - target: armv7-unknown-linux-gnueabihf + target: ${{ matrix.settings.target }} - - name: Cache + - name: Cache cargo registry uses: actions/cache@v2 with: - path: | - target/ - key: arm7-linux-gnu-publish-integration - - - name: Install cross compile toolchain - run: | - sudo apt-get update - sudo apt-get install gcc-arm-linux-gnueabihf -y + path: ~/.cargo/registry + key: ${{ matrix.settings.target }}-cargo-registry - - name: Turbo Cache - id: turbo-cache + - name: Cache cargo index uses: actions/cache@v2 with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- - - - name: Cross build aarch64 - run: turbo run build-native --cache-dir=".turbo" -- --release --target armv7-unknown-linux-gnueabihf - - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: next-swc-binaries - path: packages/next-swc/native/next-swc.linux-arm-gnueabihf.node - - build-android-aarch64: - needs: build - if: ${{ needs.build.outputs.isRelease == 'true' }} - name: next-swc - Android - aarch64 - runs-on: macos-latest - steps: - - name: Setup node - uses: actions/setup-node@v2 - with: - node-version: 14 - - # we use checkout here instead of the build cache since - # it can fail to restore in different OS' - - uses: actions/checkout@v2 - - # since the repo's dependencies aren't installed we need - # to install napi globally - - run: npm i -g @napi-rs/cli@1.2.1 - - run: npm i -g turbo@1.0.28 + path: ~/.cargo/git + key: ${{ matrix.settings.target }}-cargo-index - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly-2021-11-15 - override: true - target: aarch64-linux-android + - name: Setup toolchain + run: ${{ matrix.settings.setup }} + if: ${{ matrix.settings.setup }} + shell: bash - - name: Turbo Cache - id: turbo-cache - uses: actions/cache@v2 + - name: Build in docker + uses: addnab/docker-run-action@v3 + if: ${{ matrix.settings.docker }} with: - path: .turbo - key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}-${{ github.sha }} - restore-keys: | - turbo-${{ github.job }}- - turbo-${{ github.job }}-${{ github.ref_name }}-${{ needs.build.outputs.weekNum }}- - turbo-${{ github.job }}-canary-${{ needs.build.outputs.weekNum }}- + image: ${{ matrix.settings.docker }} + options: -v ${{ env.HOME }}/.cargo/git:/root/.cargo/git -v ${{ env.HOME }}/.cargo/registry:/root/.cargo/registry -v ${{ github.workspace }}:/build -w /build + run: ${{ matrix.settings.build }} - - name: Build + - name: 'Build' + run: ${{ matrix.settings.build }} + if: ${{ !matrix.settings.docker }} shell: bash - run: | - export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" - turbo run build-native --cache-dir=".turbo" -- --release --target aarch64-linux-android - name: Upload artifact uses: actions/upload-artifact@v2 with: name: next-swc-binaries - path: packages/next-swc/native/next-swc.android-arm64.node + path: packages/next-swc/native/next-swc.*.node build-wasm: needs: build diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index c8df2516aa2c9..41b7e8c78beb4 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -2,8 +2,8 @@ name: 'Lock Threads' on: schedule: - # This runs every hour: https://crontab.guru/every-1-hour - - cron: '0 * * * *' + # This runs twice a day: https://crontab.guru/#0_0,12_*_*_* + - cron: '0 0,12 * * *' workflow_dispatch: permissions: @@ -16,10 +16,12 @@ concurrency: jobs: action: runs-on: ubuntu-latest + if: github.repository_owner == 'vercel' steps: - uses: dessant/lock-threads@v3 with: - github-token: ${{ secrets.LOCK_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: 30 - issue-comment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.' + issue-comment: 'This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.' pr-inactive-days: 30 + log-output: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1cbbfceb7cc5c..cb6abe0bc4885 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,6 +8,7 @@ on: jobs: stale: runs-on: ubuntu-latest + if: github.repository_owner == 'vercel' steps: - uses: actions/stale@v4 id: stale @@ -15,10 +16,10 @@ jobs: with: repo-token: ${{ secrets.STALE_TOKEN }} only-labels: 'please add a complete reproduction' - close-issue-message: 'This issue has been automatically closed after 30 days of inactivity with no reproduction. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' + close-issue-message: 'This issue has been automatically closed because it received no activity for a month and had no reproduction to investigate. If you think this was closed by accident, please leave a comment. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' days-before-issue-close: 1 days-before-issue-stale: 30 days-before-pr-close: -1 days-before-pr-stale: -1 exempt-issue-labels: 'blocked,must,should,keep' - operation-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close + operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b8d1a5be1b2b..6714bc34d2f24 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,64 +2,43 @@ ### Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ### Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our community include: -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -- The use of sexualized language or imagery and unwelcome sexual attention or - advances -- Trolling, insulting/derogatory comments, and personal or political attacks +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- Publishing others’ private information, such as a physical or email address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting -### Our Responsibilities +### Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ### Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ### Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [coc@vercel.com](mailto:coc@vercel.com). All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team responsible for enforcement at [coc@vercel.com](mailto:coc@vercel.com). All complaints will be reviewed and investigated promptly and fairly. + +All project maintainers are obligated to respect the privacy and security of the reporter of any incident. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other @@ -67,8 +46,8 @@ members of the project's leadership. ### Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, +available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct/][version] [homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +[version]: https://www.contributor-covenant.org/version/2/1 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index da56c8d77fc24..57978a2058b6d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,11 +69,9 @@ stages: - stage: Test dependsOn: Build jobs: - - job: test_ie11 + - job: test_integration pool: vmImage: 'windows-2019' - variables: - BROWSER_NAME: internet explorer steps: - checkout: none - task: NodeTool@0 @@ -89,8 +87,7 @@ stages: displayName: Cache Build - script: | - npm i -g selenium-standalone@6.18.0 - displayName: 'Install selenium node' + npx playwright install chromium - script: | node run-tests.js -c 1 test/integration/production/test/index.test.js test/integration/css-client-nav/test/index.test.js test/integration/rewrites-has-condition/test/index.test.js diff --git a/contributing.md b/contributing.md index 1fd1133aee776..e98980b0f01be 100644 --- a/contributing.md +++ b/contributing.md @@ -1,8 +1,12 @@ # Contributing to Next.js -Read about our [Commitment to Open Source](https://vercel.com/oss). To -contribute to [our examples](examples), please see **[Adding -examples](#adding-examples)** below. +[Watch the 40-minute walkthrough video on how to contribute to Next.js.](https://www.youtube.com/watch?v=cuoNzXFLitc) + +--- + +- Read about our [Commitment to Open Source](https://vercel.com/oss). +- To contribute to [our examples](examples), please see [Adding examples](#adding-examples) below. +- Before jumping into a PR be sure to search [existing PRs](https://github.com/vercel/next.js/pulls) or [issues](https://github.com/vercel/next.js/issues) for an open or closed item that relates to your submission. ## Developing diff --git a/docs/advanced-features/automatic-static-optimization.md b/docs/advanced-features/automatic-static-optimization.md index abf202aacbd67..8780102d7a671 100644 --- a/docs/advanced-features/automatic-static-optimization.md +++ b/docs/advanced-features/automatic-static-optimization.md @@ -20,6 +20,14 @@ If the above is not the case, Next.js will **statically optimize** your page aut During prerendering, the router's `query` object will be empty since we do not have `query` information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the `query` object. +The cases where the query will be updated after hydration triggering another render are: + +- The page is a [dynamic-route](/docs/routing/dynamic-routes.md). +- The page has query values in the URL. +- [Rewrites](/docs/api-reference/next.config.js/rewrites.md) are configured in your `next.config.js` since these can have parameters that may need to be parsed and provided in the `query`. + +To be able to distinguish if the query is fully updated and ready for use, you can leverage the `isReady` field on [`next/router`](/docs/api-reference/next/router.md#router-object). + > **Note:** Parameters added with [dynamic routes](/docs/routing/dynamic-routes.md) to a page that's using [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) will always be available inside the `query` object. `next build` will emit `.html` files for statically optimized pages. For example, the result for the page `pages/about.js` would be: diff --git a/docs/advanced-features/compiler.md b/docs/advanced-features/compiler.md index ac07f30fe24c1..439474fa11f33 100644 --- a/docs/advanced-features/compiler.md +++ b/docs/advanced-features/compiler.md @@ -7,9 +7,10 @@ description: Learn about the Next.js Compiler, written in Rust, which transforms
Version History -| Version | Changes | -| --------- | --------------------------------------------------------------- | -| `v12.0.0` | Next.js Compiler [introduced](https://nextjs.org/blog/next-12). | +| Version | Changes | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `v12.1.0` | Added support for Styled Components, Jest, Relay, Remove React Properties, Legacy Decorators, Remove Console, and jsxImportSource. | +| `v12.0.0` | Next.js Compiler [introduced](https://nextjs.org/blog/next-12). |
@@ -30,21 +31,7 @@ We chose to build on SWC for a few reasons: - **WebAssembly:** Rust's support for WASM is essential for supporting all possible platforms and taking Next.js development everywhere. - **Community:** The Rust community and ecosystem are amazing and still growing. -## Experimental Features - -### Minification - -You can opt-in to using the Next.js compiler for minification. This is 7x faster than Terser. - -```js -// next.config.js - -module.exports = { - swcMinify: true, -} -``` - -If you have feedback about `swcMinify`, please share it on the [feedback discussion](https://github.com/vercel/next.js/discussions/30237). +## Supported Features ### Styled Components @@ -56,7 +43,7 @@ First, update to the latest version of Next.js: `npm install next@latest`. Then, // next.config.js module.exports = { - experimental: { + compiler: { // ssr and displayName are configured by default styledComponents: true, }, @@ -94,6 +81,26 @@ const customJestConfig = { module.exports = createJestConfig(customJestConfig) ``` +### Relay + +To enable [Relay](https://relay.dev/) support: + +```js +// next.config.js +module.exports = { + compiler: { + relay: { + // This should match relay.config.js + src: './', + artifactDirectory: './__generated__', + language: 'typescript', + }, + }, +} +``` + +NOTE: In Next.js all JavaScript files in `pages` directory are considered routes. So, for `relay-compiler` you'll need to specify `artifactDirectory` configuration settings outside of the `pages`, otherwise `relay-compiler` will generate files next to the source file in the `__generated__` directory, and this file will be considered a route, which will break production builds. + ### Remove React Properties Allows to remove JSX properties. This is often used for testing. Similar to `babel-plugin-react-remove-properties`. @@ -103,7 +110,7 @@ To remove properties matching the default regex `^data-test`: ```js // next.config.js module.exports = { - experimental: { + compiler: { reactRemoveProperties: true, }, } @@ -114,7 +121,7 @@ To remove custom properties: ```js // next.config.js module.exports = { - experimental: { + compiler: { // The regexes defined here are processed in Rust so the syntax is different from // JavaScript `RegExp`s. See https://docs.rs/regex. reactRemoveProperties: { properties: ['^data-custom$'] }, @@ -122,22 +129,6 @@ module.exports = { } ``` -### Legacy Decorators - -Next.js will automatically detect `experimentalDecorators` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with older versions of libraries like `mobx`. - -This flag is only supported for compatibility with existing applications. We do not recommend using legacy decorators in new applications. - -First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `jsconfig.json` or `tsconfig.json` file: - -```js -{ - "compilerOptions": { - "experimentalDecorators": true - } -} -``` - ### Remove Console This transform allows for removing all `console.*` calls in application code (not `node_modules`). Similar to `babel-plugin-transform-remove-console`. @@ -147,7 +138,7 @@ Remove all `console.*` calls: ```js // next.config.js module.exports = { - experimental: { + compiler: { removeConsole: true, }, } @@ -158,7 +149,7 @@ Remove `console.*` output except `console.error`: ```js // next.config.js module.exports = { - experimental: { + compiler: { removeConsole: { exclude: ['error'], }, @@ -166,6 +157,22 @@ module.exports = { } ``` +### Legacy Decorators + +Next.js will automatically detect `experimentalDecorators` in `jsconfig.json` or `tsconfig.json`. Legacy decorators are commonly used with older versions of libraries like `mobx`. + +This flag is only supported for compatibility with existing applications. We do not recommend using legacy decorators in new applications. + +First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `jsconfig.json` or `tsconfig.json` file: + +```js +{ + "compilerOptions": { + "experimentalDecorators": true + } +} +``` + ### importSource Next.js will automatically detect `jsxImportSource` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with libraries like Theme UI. @@ -175,11 +182,27 @@ First, update to the latest version of Next.js: `npm install next@latest`. Then, ```js { "compilerOptions": { - "jsxImportSource": true + "jsxImportSource": 'preact' } } ``` +## Experimental Features + +### Minification + +You can opt-in to using the Next.js compiler for minification. This is 7x faster than Terser. + +```js +// next.config.js + +module.exports = { + swcMinify: true, +} +``` + +If you have feedback about `swcMinify`, please share it on the [feedback discussion](https://github.com/vercel/next.js/discussions/30237). + ## Unsupported Features When your application has a `.babelrc` file, Next.js will automatically fall back to using Babel for transforming individual files. This ensures backwards compatibility with existing applications that leverage custom Babel plugins. diff --git a/docs/advanced-features/custom-document.md b/docs/advanced-features/custom-document.md index b6af8d8b18b1e..97d427fbec1bc 100644 --- a/docs/advanced-features/custom-document.md +++ b/docs/advanced-features/custom-document.md @@ -4,76 +4,67 @@ description: Extend the default document markup added by Next.js. # Custom `Document` -A custom `Document` is commonly used to augment your application's `` and `` tags. This is necessary because Next.js pages skip the definition of the surrounding document's markup. +A custom `Document` can update the `` and `` tags used to render a [Page](/docs/basic-features/pages.md). This file is only rendered on the server, so event handlers like `onClick` cannot be used in `_document`. -To override the default `Document`, create the file `./pages/_document.js` and extend the `Document` class as shown below: +To override the default `Document`, create the file `pages/_document.js` as shown below: ```jsx -import Document, { Html, Head, Main, NextScript } from 'next/document' - -class MyDocument extends Document { - static async getInitialProps(ctx) { - const initialProps = await Document.getInitialProps(ctx) - return { ...initialProps } - } - - render() { - return ( - - - -
- - - - ) - } +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) } - -export default MyDocument ``` -> The code above is the default `Document` added by Next.js. Feel free to remove the `getInitialProps` or `render` function from `MyDocument` if you don't need to change them. - -``, ``, `
` and `` are required for the page to be properly rendered. - -Custom attributes are allowed as props, like `lang`: +The code above is the default `Document` added by Next.js. Custom attributes are allowed as props. For example, we might want to add `lang="en"` to the `` tag: ```jsx ``` -The `` component used here is not the same one from [`next/head`](/docs/api-reference/next/head.md). The `` component used here should only be used for any `` code that is common for all pages. For all other cases, such as `` tags, we recommend using [`next/head`](/docs/api-reference/next/head.md) in your pages or components. +Or add a `className` to the `body` tag: -The `ctx` object is equivalent to the one received in [`getInitialProps`](/docs/api-reference/data-fetching/get-initial-props.md#context-object), with one addition: +```jsx +<body className="bg-white"> +``` -- `renderPage`: `Function` - a callback that runs the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering) +`<Html>`, `<Head />`, `<Main />` and `<NextScript />` are required for the page to be properly rendered. ## Caveats -- `Document` is only rendered in the server, event handlers like `onClick` won't work. -- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), take a look at the [`App`](/docs/advanced-features/custom-app.md) component instead. -- `Document`'s `getInitialProps` function is not called during client-side transitions, nor when a page is [statically optimized](/docs/advanced-features/automatic-static-optimization.md). +- The `<Head />` component used in `_document` is not the same as [`next/head`](/docs/api-reference/next/head.md). The `<Head />` component used here should only be used for any `<head>` code that is common for all pages. For all other cases, such as `<title>` tags, we recommend using [`next/head`](/docs/api-reference/next/head.md) in your pages or components. +- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), read [Layouts](/docs/basic-features/layouts.md) instead. - `Document` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/overview.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). ## Customizing `renderPage` -> It should be noted that the only reason you should be customizing `renderPage` is for usage with **css-in-js** libraries that need to wrap the application to properly work with server-side rendering. +> **Note:** This is advanced and only needed for libraries like CSS-in-JS to support server-side rendering. This is not needed for built-in `styled-jsx` support. -It takes as argument an options object for further customization: +To prepare for [React 18](/docs/advanced-features/react-18.md), we recommend avoiding customizing `getInitialProps` and `renderPage`, if possible. + +The `ctx` object shown below is equivalent to the one received in [`getInitialProps`](/docs/api-reference/data-fetching/get-initial-props.md#context-object), with the addition of `renderPage`. ```jsx -import Document from 'next/document' +import Document, { Html, Head, Main, NextScript } from 'next/document' class MyDocument extends Document { static async getInitialProps(ctx) { const originalRenderPage = ctx.renderPage + // Run the React rendering logic synchronously ctx.renderPage = () => originalRenderPage({ - // useful for wrapping the whole react tree + // Useful for wrapping the whole react tree enhanceApp: (App) => App, - // useful for wrapping in a per-page basis + // Useful for wrapping in a per-page basis enhanceComponent: (Component) => Component, }) @@ -82,11 +73,25 @@ class MyDocument extends Document { return initialProps } + + render() { + return ( + <Html> + <Head /> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ) + } } export default MyDocument ``` +> **Note**: `getInitialProps` in `_document` is not called during client-side transitions. + ## TypeScript You can use the built-in `DocumentContext` type and change the file name to `./pages/_document.tsx` like so: diff --git a/docs/advanced-features/custom-server.md b/docs/advanced-features/custom-server.md index d887c841025c1..d7cb7bb6b2343 100644 --- a/docs/advanced-features/custom-server.md +++ b/docs/advanced-features/custom-server.md @@ -36,18 +36,24 @@ const app = next({ dev, hostname, port }) const handle = app.getRequestHandler() app.prepare().then(() => { - createServer((req, res) => { - // Be sure to pass `true` as the second argument to `url.parse`. - // This tells it to parse the query portion of the URL. - const parsedUrl = parse(req.url, true) - const { pathname, query } = parsedUrl - - if (pathname === '/a') { - app.render(req, res, '/a', query) - } else if (pathname === '/b') { - app.render(req, res, '/b', query) - } else { - handle(req, res, parsedUrl) + createServer(async (req, res) => { + try { + // Be sure to pass `true` as the second argument to `url.parse`. + // This tells it to parse the query portion of the URL. + const parsedUrl = parse(req.url, true) + const { pathname, query } = parsedUrl + + if (pathname === '/a') { + await app.render(req, res, '/a', query) + } else if (pathname === '/b') { + await app.render(req, res, '/b', query) + } else { + await handle(req, res, parsedUrl) + } + } catch (err) { + console.error('Error occurred handling', req.url, err) + res.statusCode = 500 + res.end('internal server error') } }).listen(port, (err) => { if (err) throw err diff --git a/docs/advanced-features/debugging.md b/docs/advanced-features/debugging.md index d119ccffc15c4..2cbcafec88345 100644 --- a/docs/advanced-features/debugging.md +++ b/docs/advanced-features/debugging.md @@ -52,7 +52,7 @@ Now go to the Debug panel (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>D</kbd> on Wind ### Client-side code -Start your development server as usual by running `next dev`, `npm run dev`, or `yarn dev`. Once the server starts, open `http://localhost:3000` (or your alternate URL) in Chrome. Next, open Chrome's Developer Tools (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>J</kbd> on Windows/Linux, <kbd>⌥</kbd>+<kbd>⌘</kbd>+<kbd>I</kbd> on macOS), then go to the **Sources** tab +Start your development server as usual by running `next dev`, `npm run dev`, or `yarn dev`. Once the server starts, open `http://localhost:3000` (or your alternate URL) in Chrome. Next, open Chrome's Developer Tools (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>J</kbd> on Windows/Linux, <kbd>⌥</kbd>+<kbd>⌘</kbd>+<kbd>I</kbd> on macOS), then go to the **Sources** tab. Now, any time your client-side code reaches a [`debugger`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) statement, code execution will pause and that file will appear in the debug area. You can also press <kbd>Ctrl</kbd>+<kbd>P</kbd> on Windows/Linux or <kbd>⌘</kbd>+<kbd>P</kbd> on macOS to search for a file and set breakpoints manually. Note that when searching here, your source files will have paths starting with `webpack://_N_E/./`. diff --git a/docs/advanced-features/error-handling.md b/docs/advanced-features/error-handling.md new file mode 100644 index 0000000000000..a164c58e48392 --- /dev/null +++ b/docs/advanced-features/error-handling.md @@ -0,0 +1,104 @@ +--- +description: Handle errors in your Next.js app. +--- + +# Error Handling + +This documentation explains how you can handle development, server-side, and client-side errors. + +## Handling Errors in Development + +When there is a runtime error during the development phase of your Next.js application, you will encounter an **overlay**. It is a modal that covers the webpage. It is only visible when the development server runs using `next dev`, `npm run dev`, or `yarn dev` and not in production. Fixing the error will automatically dismiss the overlay. + +Here is an example of an overlay: + +![Example of an overlay when in development mode](https://assets.vercel.com/image/upload/v1645118290/docs-assets/static/docs/error-handling/overlay.png) + +## Handling Server Errors + +Next.js provides a static 500 page by default to handle server-side errors that occur in your application. You can also [customize this page](/docs/advanced-features/custom-error-page#customizing-the-500-page) by creating a `pages/500.js` file. + +Having a 500 page in your application does not show specific errors to the app user. + +You can also use [404 page](/docs/advanced-features/custom-error-page#404-page) to handle specific runtime error like `file not found`. + +## Handling Client Errors + +React [Error Boundaries](https://reactjs.org/docs/error-boundaries.html) is a graceful way to handle a JavaScript error on the client so that the other parts of the application continue working. In addition to preventing the page from crashing, it allows you to provide a custom fallback component and even log error information. + +To use Error Boundaries for your Next.js application, you must create a class component `ErrorBoundary` and wrap the `Component` prop in the `pages/_app.js` file. This component will be responsible to: + +- Render a fallback UI after an error is thrown +- Provide a way to reset the Application's state +- Log error information + +You can create an `ErrorBoundary` class component by extending `React.Component`. For example: + +```jsx +class ErrorBoundary extends React.Component { + constructor(props) { + super(props) + + // Define a state variable to track whether is an error or not + this.state = { hasError: false } + } + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI + + return { hasError: true } + } + componentDidCatch(error, errorInfo) { + // You can use your own error logging service here + console.log({ error, errorInfo }) + } + render() { + // Check if the error is thrown + if (this.state.hasError) { + // You can render any custom fallback UI + return ( + <div> + <h2>Oops, there is an error!</h2> + <button + type="button" + onClick={() => this.setState({ hasError: false })} + > + Try again? + </button> + </div> + ) + } + + // Return children components in case of no error + + return this.props.children + } +} + +export default ErrorBoundary +``` + +The `ErrorBoundary` component keeps track of an `hasError` state. The value of this state variable is a boolean. When the value of `hasError` is `true`, then the `ErrorBoundary` component will render a fallback UI. Otherwise, it will render the children components. + +After creating an `ErrorBoundary` component, import it in the `pages/_app.js` file to wrap the `Component` prop in your Next.js application. + +```jsx +// Import the ErrorBoundary component +import ErrorBoundary from '../components/ErrorBoundary' + +function MyApp({ Component, pageProps }) { + return ( + // Wrap the Component prop with ErrorBoundary component + <ErrorBoundary FallbackComponent={ErrorFallback}> + <Component {...pageProps} /> + </ErrorBoundary> + ) +} + +export default MyApp +``` + +You can learn more about [Error Boundaries](https://reactjs.org/docs/error-boundaries.html) in React's documentation. + +### Reporting Errors + +To monitor client errors, use a service like [Sentry](https://github.com/vercel/next.js/tree/canary/examples/with-sentry), Bugsnag or Datadog. diff --git a/docs/advanced-features/i18n-routing.md b/docs/advanced-features/i18n-routing.md index 119e43578f1db..0a55f52ffd69a 100644 --- a/docs/advanced-features/i18n-routing.md +++ b/docs/advanced-features/i18n-routing.md @@ -170,6 +170,11 @@ import { NextRequest, NextResponse } from 'next/server' const PUBLIC_FILE = /\.(.*)$/ +const stripDefaultLocale = (str: string): string => { + const stripped = str.replace('/default', '') + return stripped +} + export function middleware(request: NextRequest) { const shouldHandleLocale = !PUBLIC_FILE.test(request.nextUrl.pathname) && @@ -177,7 +182,11 @@ export function middleware(request: NextRequest) { request.nextUrl.locale === 'default' return shouldHandleLocale - ? NextResponse.redirect(`/en${request.nextUrl.href}`) + ? NextResponse.redirect( + `/en${stripDefaultLocale(request.nextUrl.pathname)}${ + request.nextUrl.search + }` + ) : undefined } ``` diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md index 0a861b416473d..a9c20215e0b7e 100644 --- a/docs/advanced-features/measuring-performance.md +++ b/docs/advanced-features/measuring-performance.md @@ -159,7 +159,7 @@ export function reportWebVitals(metric) { > **Note**: If you use [Google Analytics](https://analytics.google.com/analytics/web/), using the > `id` value can allow you to construct metric distributions manually (to calculate percentiles, -> etc...). +> etc.) > > ```js > export function reportWebVitals({ id, name, label, value }) { diff --git a/docs/advanced-features/output-file-tracing.md b/docs/advanced-features/output-file-tracing.md index 8441905147537..c325d05fc3b00 100644 --- a/docs/advanced-features/output-file-tracing.md +++ b/docs/advanced-features/output-file-tracing.md @@ -34,7 +34,7 @@ module.exports = { This will create a folder at `.next/standalone` which can then be deployed on it's own without installing `node_modules`. -Additionally, a minimal `server.js` file is also output which can be used instead of `next start`. This minimal server does not copy the `.next/static` directory by default as this should ideally be handled by a CDN instead, although it can be copied to the `standalone` folder manually and the `server.js` file will serve it automatically. +Additionally, a minimal `server.js` file is also output which can be used instead of `next start`. This minimal server does not copy the `public` or `.next/static` folders by default as these should ideally be handled by a CDN instead, although these folders can be copied to the `standalone/public` and `standalone/.next/static` folders manually, after which `server.js` file will serve these automatically. ## Caveats @@ -50,5 +50,5 @@ module.exports = { } ``` -- There are some cases that Next.js might fail to include required files, or might incorrectly include unused files. In those cases, you can export page configs props `unstable_includeFiles` and `unstable_excludeFiles` respectively. Each prop accepts an array of [globs](<https://en.wikipedia.org/wiki/Glob_(programming)>) relative to the project's root to either include or exclude in the trace. +- There are some cases that Next.js might fail to include required files, or might incorrectly include unused files. In those cases, you can export page configs props `unstable_includeFiles` and `unstable_excludeFiles` respectively. Each prop accepts an array of [minimatch globs](https://www.npmjs.com/package/minimatch) relative to the project's root to either include or exclude in the trace. - Currently, Next.js does not do anything with the emitted `.nft.json` files. The files must be read by your deployment platform, for example [Vercel](https://vercel.com), to create a minimal deployment. In a future release, a new command is planned to utilize these `.nft.json` files. diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index 4d467c9e07401..7850663d5506b 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -171,22 +171,23 @@ https://<your-site>/api/preview?secret=<token>&slug=<path> ## More Details -### Clear the preview mode cookies +### Clear the Preview Mode cookies -By default, no expiration date is set for the preview mode cookies, so the preview mode ends when the browser is closed. +By default, no expiration date is set for Preview Mode cookies, so the preview session ends when the browser is closed. -To clear the preview cookies manually, you can create an API route which calls `clearPreviewData` and then access this API route. +To clear the Preview Mode cookies manually, create an API route that calls `clearPreviewData()`: ```js +// pages/api/clear-preview-mode-cookies.js + export default function handler(req, res) { - // Clears the preview mode cookies. - // This function accepts no arguments. res.clearPreviewData() - // ... } ``` -### Specify the preview mode duration +Then, send a request to `/api/clear-preview-mode-cookies` to invoke the API Route. If calling this route using [`next/link`](/docs/api-reference/next/link.md), you must pass `prefetch={false}` to prevent calling `clearPreviewData` during link prefetching. + +### Specify the Preview Mode duration `setPreviewData` takes an optional second parameter which should be an options object. It accepts the following keys: diff --git a/docs/advanced-features/react-18.md b/docs/advanced-features/react-18.md deleted file mode 100644 index 1d82c82b71b47..0000000000000 --- a/docs/advanced-features/react-18.md +++ /dev/null @@ -1,154 +0,0 @@ -# React 18 - -[React 18](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) adds new features including, Suspense, automatic batching of updates, APIs like `startTransition`, and a new streaming API for server rendering with support for `React.lazy`. - -React 18 is in RC now. Read more about React 18's [release plan](https://github.com/reactwg/react-18/discussions) and discussions from the [working group](https://github.com/reactwg/react-18/discussions). - -### React 18 Usage in Next.js - -Ensure you have the `rc` npm tag of React installed: - -```jsx -npm install next@latest react@rc react-dom@rc -``` - -That's all! You can now start using React 18's new APIs like `startTransition` and `Suspense` in Next.js. - -### Enable SSR Streaming (Alpha) - -Concurrent features in React 18 include built-in support for server-side Suspense and SSR streaming support, allowing you to server-render pages using HTTP streaming. - -This is an experimental feature in Next.js 12, but once enabled, SSR will use the same [Edge Runtime](/docs/api-reference/edge-runtime.md) as [Middleware](/docs/middleware.md). - -To enable, use the experimental flag `concurrentFeatures: true`: - -```jsx -// next.config.js -module.exports = { - experimental: { - concurrentFeatures: true, - }, -} -``` - -Once enabled, you can use Suspense and SSR streaming for all pages. This also means that you can use Suspense-based data-fetching, `next/dynamic`, and React's built-in `React.lazy` with Suspense boundaries. - -```jsx -import dynamic from 'next/dynamic' -import { lazy, Suspense } from 'react' - -import Content from '../components/content' - -// These two ways are identical: -const Profile = dynamic(() => import('./profile'), { suspense: true }) -const Footer = lazy(() => import('./footer')) - -export default function Home() { - return ( - <div> - <Suspense fallback={<Spinner />}> - {/* A component that uses Suspense-based */} - <Content /> - </Suspense> - <Suspense fallback={<Spinner />}> - <Profile /> - </Suspense> - <Suspense fallback={<Spinner />}> - <Footer /> - </Suspense> - </div> - ) -} -``` - -## React Server Components - -React Server Components allow us to render everything, including the components themselves, on the server. This is fundamentally different from server-side rendering where you're pre-generating HTML on the server. With Server Components, there's **zero client-side JavaScript needed,** making page rendering faster. This improves the user experience of your application, pairing the best parts of server-rendering with client-side interactivity. - -### Enable React Server Components (Alpha) - -To use React Server Components, ensure you have React 18 installed. Then, turn on the `concurrentFeatures` and `serverComponents` options in `next.config.js`: - -```jsx -// next.config.js -module.exports = { - experimental: { - concurrentFeatures: true, - serverComponents: true, - }, -} -``` - -Next, if you already have customized `pages/_document` component, you need to remove the `getInitialProps` static method and the `getServerSideProps` export if there’s any, otherwise it won't work with server components. If no custom Document component is provided, Next.js will fallback to a default one like below. - -```jsx -// pages/_document.js -import { Html, Head, Main, NextScript } from 'next/document' - -export default function Document() { - return ( - <Html> - <Head /> - <body> - <Main /> - <NextScript /> - </body> - </Html> - ) -} -``` - -Then, you can start using React Server Components. [See our example](https://github.com/vercel/next-rsc-demo) for more information. - -### Server Components APIs (Alpha) - -To run a component on the server, append `.server.js` to the end of the filename. For example `./pages/home.server.js` is a Server Component. - -For client components, add `.client.js`. For example, `./components/avatar.client.js`. - -You can then import other server or client components from any server component. Note: a server component **can not** be imported by a client component. Components without "server/client" extensions will be treated as "universal component" and can be used and rendered by both sides, depending on where it is imported. For example: - -```jsx -// pages/home.server.js - -import { Suspense } from 'react' - -import Profile from '../components/profile.server' -import Content from '../components/content.client' - -export default function Home() { - return ( - <div> - <h1>Welcome to React Server Components</h1> - <Suspense fallback={'Loading...'}> - <Profile /> - </Suspense> - <Content /> - </div> - ) -} -``` - -The `<Home>` and `<Profile>` components will always be server-side rendered and streamed to the client, and will not be included by the client runtime. However `<Content>` will still be hydrated on the client-side, like normal React components. - -To see a full example, check out [link to the demo and repository](https://github.com/vercel/next-rsc-demo). - -## **Supported Next.js APIs** - -- `next/link` / `next/image` -- `next/document` / `next/app` -- Dynamic routing - -## **Unsupported Next.js APIs** - -While RSC and SSR streaming is still in the alpha stage, not all Next.js APIs are supported. The following Next.js APIs have limited functionality inside Server Components: - -- React internals: Most of React hooks such as `useContext`, `useState`, `useReducer`, `useEffect` and `useLayoutEffect` are not supported as of today since Server Components are executed per requests and aren't stateful. -- `next/head` -- Partial: Note that Inside `.client.js` components `useRouter` is supported -- Styled JSX -- CSS Modules -- Next.js I18n -- `getInitialProps`, `getStaticProps` and `getStaticPaths` - -React 18 without SSR streaming isn't affected. diff --git a/docs/advanced-features/react-18/overview.md b/docs/advanced-features/react-18/overview.md new file mode 100644 index 0000000000000..735431c54d17b --- /dev/null +++ b/docs/advanced-features/react-18/overview.md @@ -0,0 +1,28 @@ +# React 18 + +[React 18](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) adds new features including Suspense, automatic batching of updates, APIs like `startTransition`, and a new streaming API for server rendering with support for `React.lazy`. +Next.js also provides streaming related APIs, please checkout [next/streaming](/docs/api-reference/next/streaming.md) for details. + +React 18 is now in Release Candidate (RC). Read more about React 18's [release plan](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) and discussions from the [working group](https://github.com/reactwg/react-18/discussions). + +## Using React 18 with Next.js + +Install the RC version of React 18: + +```jsx +npm install next@latest react@rc react-dom@rc +``` + +You can now start using React 18's new APIs like `startTransition` and `Suspense` in Next.js. + +## Streaming SSR (Alpha) + +Streaming server-rendering (SSR) is an experimental feature in Next.js 12. When enabled, SSR will use the same [Edge Runtime](/docs/api-reference/edge-runtime.md) as [Middleware](/docs/middleware.md). + +[Learn how to enable streaming in Next.js.](/docs/advanced-features/react-18/streaming.md) + +## React Server Components (Alpha) + +Server Components are a new feature in React that let you reduce your JavaScript bundle size by separating server and client-side code. Server Components allow developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering. + +Server Components are still in research and development. [Learn how to try Server Components](/docs/advanced-features/react-18/server-components.md) as an experimental feature in Next.js. diff --git a/docs/advanced-features/react-18/server-components.md b/docs/advanced-features/react-18/server-components.md new file mode 100644 index 0000000000000..dea51f2014fd2 --- /dev/null +++ b/docs/advanced-features/react-18/server-components.md @@ -0,0 +1,132 @@ +# React Server Components (Alpha) + +Server Components allow us to render React components on the server. This is fundamentally different from server-side rendering (SSR) where you're pre-generating HTML on the server. With Server Components, there's **zero client-side JavaScript needed,** making page rendering faster. This improves the user experience of your application, pairing the best parts of server-rendering with client-side interactivity. + +### Enable React Server Components + +To use React Server Components, ensure you have React 18 installed: + +```jsx +npm install next@latest react@rc react-dom@rc +``` + +Then, update your `next.config.js`: + +```jsx +// next.config.js +module.exports = { + experimental: { + runtime: 'nodejs', + serverComponents: true, + }, +} +``` + +Using `runtime` also enables [Streaming SSR](/docs/advanced-features/react-18/streaming). When setting `runtime` to `'edge'`, the server will be running entirely in the [Edge Runtime](https://nextjs.org/docs/api-reference/edge-runtime). + +Now, you can start using React Server Components in Next.js. [See our example](https://github.com/vercel/next-rsc-demo) for more information. + +### Server Components Conventions + +To run a component on the server, append `.server.js` to the end of the filename. For example, `./pages/home.server.js` will be treated as a Server Component. + +For client components, append `.client.js` to the filename. For example, `./components/avatar.client.js`. + +You can then import other server or client components from any server component. Note: a server component **can not** be imported by a client component. Components without "server/client" extensions will be treated as shared components and can be used and rendered by both sides, depending on where it is imported. For example: + +```jsx +// pages/home.server.js + +import { Suspense } from 'react' + +import Profile from '../components/profile.server' +import Content from '../components/content.client' + +export default function Home() { + return ( + <div> + <h1>Welcome to React Server Components</h1> + <Suspense fallback={'Loading...'}> + <Profile /> + </Suspense> + <Content /> + </div> + ) +} +``` + +The `<Home>` and `<Profile>` components will always be server-side rendered and streamed to the client, and will not be included by the client-side JavaScript. However, `<Content>` will still be hydrated on the client-side, like normal React components. + +> Make sure you're using default imports and exports for server components (`.server.js`). The support of named exports are a work in progress! + +To see a full example, check out the [vercel/next-react-server-components demo](https://github.com/vercel/next-react-server-components). + +## Supported Next.js APIs + +### `next/link` and `next/image` + +You can use `next/link` and `next/image` like before and they will be treated as client components to keep the interaction on client side. + +### `next/document` + +If you have a custom `_document`, you have to change your `_document` to a functional component like below to use server components. If you don't have one, Next.js will use the default `_document` component for you. + +```jsx +// pages/_document.js +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + <Html> + <Head /> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ) +} +``` + +### `next/app` + +If you're using `_app.js`, the usage is the same as [Custom App](/docs/advanced-features/custom-app). +If you're using `_app.server.js` as a server component, see the example below where it only receives the `children` prop as React elements. You can wrap any other client or server components around `children` to customize the layout of your app. + +```js +// pages/_app.server.js +export default function App({ children }) { + return children +} +``` + +### Routing + +Both basic routes with path and queries and dynamic routes are supported. If you need to access the router in server components(`.server.js`), they will receive `router` instance as a prop so that you can directly access them without using the `useRouter()` hook. + +```jsx +// pages/index.server.js + +export default function Index({ router }) { + // You can access routing information by `router.pathname`, etc. + return 'hello' +} +``` + +### Unsupported Next.js APIs + +While RSC and SSR streaming are still in the alpha stage, not all Next.js APIs are supported. The following Next.js APIs have limited functionality within Server Components. React 18 use without SSR streaming is not affected. + +#### React internals + +Most React hooks, such as `useContext`, `useState`, `useReducer`, `useEffect` and `useLayoutEffect`, are not supported as of today since server components are executed per request and aren't stateful. + +#### Data Fetching & Styling + +Like streaming SSR, styling and data fetching within `Suspense` on the server side are not well supported. We're still working on them. + +Page level exported methods like `getInitialProps`, `getStaticProps` and `getStaticPaths` are not supported. + +#### `next/head` and I18n + +We are still working on support for these features. diff --git a/docs/advanced-features/react-18/streaming.md b/docs/advanced-features/react-18/streaming.md new file mode 100644 index 0000000000000..984264190dc56 --- /dev/null +++ b/docs/advanced-features/react-18/streaming.md @@ -0,0 +1,71 @@ +# Streaming SSR (Alpha) + +React 18 will include architectural improvements to React server-side rendering (SSR) performance. This means you can use `Suspense` in your React components in streaming SSR mode and React will render them on the server and send them through HTTP streams. +It's worth noting that another experimental feature, React Server Components, is based on streaming. You can read more about server components related streaming APIs in [`next/streaming`](/docs/api-reference/next/streaming.md). However, this guide focuses on basic React 18 streaming. + +## Enable Streaming SSR + +Enabling streaming SSR means React renders your components into streams and the client continues receiving updates from these streams even after the initial SSR response is sent. In other words, when any suspended components resolve down the line, they are rendered on the server and streamed to the client. With this strategy, the app can start emitting HTML even before all the data is ready, improving your app's loading performance. As an added bonus, in streaming SSR mode, the client will also use selective hydration strategy to prioritize component hydration which based on user interaction. + +To enable streaming SSR, set the experimental option `runtime` to either `'nodejs'` or `'edge'`: + +```jsx +// next.config.js +module.exports = { + experimental: { + runtime: 'nodejs', + }, +} +``` + +This option determines the environment in which streaming SSR will be happening. When setting to `'edge'`, the server will be running entirely in the [Edge Runtime](https://nextjs.org/docs/api-reference/edge-runtime). + +## Streaming Features + +### next/dynamic + +Dynamic imports through `React.lazy` have better support in React 18. Previously, Next.js supported dynamic imports internally without requiring `Suspense` or `React.lazy`. Now to embrace the official APIs on the React side, we provide you with `options.suspense` in `next/dynamic`. + +```jsx +import dynamic from 'next/dynamic' +import { lazy, Suspense } from 'react' + +import Content from '../components/content' + +// These two ways are identical: +const Profile = dynamic(() => import('./profile'), { suspense: true }) +const Footer = lazy(() => import('./footer')) + +export default function Home() { + return ( + <div> + <Suspense fallback={<Spinner />}> + {/* A component that uses Suspense */} + <Content /> + </Suspense> + <Suspense fallback={<Spinner />}> + <Profile /> + </Suspense> + <Suspense fallback={<Spinner />}> + <Footer /> + </Suspense> + </div> + ) +} +``` + +Check out [`next/streaming`](/docs/api-reference/next/streaming.md) for more details on building Next.js apps in streaming SSR mode. + +## Important Notes + +#### `next/head` and `next/script` + +Using resource tags (e.g. scripts or stylesheets) in `next/head` won't work as intended with streaming, as the loading order and timing of `next/head` tags can no longer be guaranteed once you add Suspense boundaries. We suggest moving resource tags to `next/script` with the `afterInteractive` or `lazyOnload` strategy, or to `_document`. For similar reasons, we also suggest migrating `next/script` instances with the `beforeInteractive` strategy to `_document`. + +#### Data Fetching + +Currently, data fetching within `Suspense` boundaries on the server side is not fully supported, which could lead to mismatching between server and client. In the short-term, please don't try data fetching within `Suspense`. + +#### Styling + +The Next.js team is working on support for `styled-jsx` and CSS modules in streaming SSR. Stay tuned for updates. diff --git a/docs/advanced-features/static-html-export.md b/docs/advanced-features/static-html-export.md index 6408adf0b7c68..a2a7a7f2b80ad 100644 --- a/docs/advanced-features/static-html-export.md +++ b/docs/advanced-features/static-html-export.md @@ -27,7 +27,7 @@ Update your build script in `package.json` to use `next export`: Running `npm run build` will generate an `out` directory. -`next export` builds an HTML version of your app. During `next build`, [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) and [`getStaticPaths`](/docs/basic-features/data-fetching/get-static-paths.md) will generate an HTML file for each page in your `pages` directory (or more for [dynamic routes](/docs/routing/dynamic-routes.md). Then, `next export` will copy the already exported files into the correct directory. `getInitialProps` will generate the HTML files during `next export` instead of `next build`. +`next export` builds an HTML version of your app. During `next build`, [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) and [`getStaticPaths`](/docs/basic-features/data-fetching/get-static-paths.md) will generate an HTML file for each page in your `pages` directory (or more for [dynamic routes](/docs/routing/dynamic-routes.md)). Then, `next export` will copy the already exported files into the correct directory. `getInitialProps` will generate the HTML files during `next export` instead of `next build`. For more advanced scenarios, you can define a parameter called [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to configure exactly which pages will be generated. diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md index 00f6df03a290a..6ed9b3f5756f0 100644 --- a/docs/advanced-features/using-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -46,10 +46,13 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: options: { remarkPlugins: [], rehypePlugins: [], + // If you use `MDXProvider`, uncomment the following line. + // providerImportSource: "@mdx-js/react", }, }) module.exports = withMDX({ - pageExtensions: ['js', 'jsx', 'md', 'mdx'], + // Append the default value with md extensions + pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], }) ``` @@ -155,12 +158,27 @@ The above generates the following `HTML`: When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. +To enable you need to specify `providerImportSource: "@mdx-js/react"` in `next.config.js`. + +```js +// next.config.js + +const withMDX = require('@next/mdx')({ + // ... + options: { + providerImportSource: '@mdx-js/react', + }, +}) +``` + +Then setup the provider in your page + ```jsx // pages/index.js import { MDXProvider } from '@mdx-js/react' import Image from 'next/image' -import { Heading, Text, Pre, Code, Table } from 'my-components' +import { Heading, InlineCode, Pre, Table, Text } from 'my-components' const ResponsiveImage = (props) => ( <Image alt={props.alt} layout="responsive" {...props} /> @@ -171,8 +189,8 @@ const components = { h1: Heading.H1, h2: Heading.H2, p: Text, - code: Pre, - inlineCode: Code, + pre: Pre, + code: InlineCode, } export default function Post(props) { @@ -184,6 +202,8 @@ export default function Post(props) { } ``` +If you use it across the site you may want to add the provider to `_app.js` so all MDX pages pick up the custom element config. + ## Helpful Links - [MDX](https://mdxjs.com) diff --git a/docs/api-reference/create-next-app.md b/docs/api-reference/create-next-app.md index f463840b88b91..e291dd5e94f5f 100644 --- a/docs/api-reference/create-next-app.md +++ b/docs/api-reference/create-next-app.md @@ -28,6 +28,7 @@ yarn create next-app --typescript - **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/canary/examples) or a GitHub URL. The URL can use any branch and/or subdirectory. - **--example-path [path-to-example]** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar` - **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend running `yarn create next-app` +- **--use-pnpm** - Explicitly tell the CLI to bootstrap the app using pnpm. To bootstrap using yarn we recommend running `yarn create next-app` ### Why use Create Next App? diff --git a/docs/api-reference/data-fetching/get-static-paths.md b/docs/api-reference/data-fetching/get-static-paths.md index 2ca0841fecaee..5b86474d88909 100644 --- a/docs/api-reference/data-fetching/get-static-paths.md +++ b/docs/api-reference/data-fetching/get-static-paths.md @@ -7,10 +7,12 @@ description: API reference for `getStaticPaths`. Learn how to fetch data and gen <details> <summary><b>Version History</b></summary> -| Version | Changes | -| -------- | --------------------------------------------------------------------------------------------------------------- | +| Version | Changes | +| ------- | ------- | + +| `v12.1.0` | [On-demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) added (Beta). | | `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) | -| `v9.3.0` | `getStaticPaths` introduced. | +| `v9.3.0` | `getStaticPaths` introduced. | </details> @@ -110,7 +112,7 @@ export default Post If `fallback` is `true`, then the behavior of `getStaticProps` changes in the following ways: - The paths returned from `getStaticPaths` will be rendered to `HTML` at build time by `getStaticProps`. -- The paths that have not been generated at build time will **not** result in a 404 page. Instead, Next.js will serve a [“fallback”](#fallback-pages) version of the page on the first request to such a path. +- The paths that have not been generated at build time will **not** result in a 404 page. Instead, Next.js will serve a [“fallback”](#fallback-pages) version of the page on the first request to such a path. Web crawlers, such as Google, won't be served a fallback and instead the path will behave as in [`fallback: 'blocking'`](#fallback-blocking). - In the background, Next.js will statically generate the requested path `HTML` and `JSON`. This includes running `getStaticProps`. - When complete, the browser receives the `JSON` for the generated path. This will be used to automatically render the page with the required props. From the user’s perspective, the page will be swapped from the fallback page to the full page. - At the same time, Next.js adds this path to the list of pre-rendered pages. Subsequent requests to the same path will serve the generated page, like other pages pre-rendered at build time. diff --git a/docs/api-reference/data-fetching/get-static-props.md b/docs/api-reference/data-fetching/get-static-props.md index 7e04d1d9252e7..c5f34276ffa8a 100644 --- a/docs/api-reference/data-fetching/get-static-props.md +++ b/docs/api-reference/data-fetching/get-static-props.md @@ -7,12 +7,14 @@ description: API reference for `getStaticProps`. Learn how to use `getStaticProp <details> <summary><b>Version History</b></summary> -| Version | Changes | -| --------- | ----------------------------------------------------------------------------------------------------------------- | -| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. | -| `v9.5.0` | Stable [Incremental Static Regeneration](https://nextjs.org/blog/next-9-5#stable-incremental-static-regeneration) | -| `v9.3.0` | `getStaticProps` introduced. | -| `v10.0.0` | `fallback: 'blocking'` return option added. | +| Version | Changes | +| ------- | ------- | + +| `v12.1.0` | [On-demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) added (Beta). | +| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. | +| `v10.0.0` | `fallback: 'blocking'` return option added. | +| `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) | +| `v9.3.0` | `getStaticProps` introduced. | </details> diff --git a/docs/api-reference/next.config.js/headers.md b/docs/api-reference/next.config.js/headers.md index 5905a0860dd94..3fb5be5578a33 100644 --- a/docs/api-reference/next.config.js/headers.md +++ b/docs/api-reference/next.config.js/headers.md @@ -376,7 +376,20 @@ module.exports = { ### Cache-Control -Cache-Control headers set in next.config.js will be overwritten in production to ensure that static assets can be cached effectively. If you need to revalidate the cache of a page that has been [statically generated](https://nextjs.org/docs/basic-features/pages#static-generation-recommended), you can do so by setting `revalidate` in the page's [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) function. +You can set the `Cache-Control` header in your [Next.js API Routes](/docs/api-routes/introduction.md) by using the `res.setHeader` method: + +```js +// pages/api/user.js + +export default function handler(req, res) { + res.setHeader('Cache-Control', 's-maxage=86400') + res.status(200).json({ name: 'John Doe' }) +} +``` + +You cannot set `Cache-Control` headers in `next.config.js` file as these will be overwritten in production to ensure that API Routes and static assets are cached effectively. + +If you need to revalidate the cache of a page that has been [statically generated](/docs/basic-features/pages.md#static-generation-recommended), you can do so by setting the `revalidate` prop in the page's [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) function. ## Related diff --git a/docs/api-reference/next.config.js/ignoring-typescript-errors.md b/docs/api-reference/next.config.js/ignoring-typescript-errors.md index 57335b30cf8ce..a26cbc26cbc8f 100644 --- a/docs/api-reference/next.config.js/ignoring-typescript-errors.md +++ b/docs/api-reference/next.config.js/ignoring-typescript-errors.md @@ -8,7 +8,7 @@ Next.js fails your **production build** (`next build`) when TypeScript errors ar If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step. -> Be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous. +If disabled, be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous. Open `next.config.js` and enable the `ignoreBuildErrors` option in the `typescript` config: diff --git a/docs/api-reference/next.config.js/introduction.md b/docs/api-reference/next.config.js/introduction.md index 6ecd28297c89c..4c7b116436a68 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -48,6 +48,20 @@ module.exports = (phase, { defaultConfig }) => { } ``` +Since Next.js 12.1.0, you can use an async function: + +```js +module.exports = async (phase, { defaultConfig }) => { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { + /* config options here */ + } + return nextConfig +} +``` + `phase` is the current context in which the configuration is loaded. You can see the [available phases](https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/constants.ts#L1-L5). Phases can be imported from `next/constants`: ```js diff --git a/docs/api-reference/next.config.js/rewrites.md b/docs/api-reference/next.config.js/rewrites.md index 16ae2b533df85..47571668dfb2f 100644 --- a/docs/api-reference/next.config.js/rewrites.md +++ b/docs/api-reference/next.config.js/rewrites.md @@ -300,15 +300,20 @@ module.exports = { <summary><b>Examples</b></summary> <ul> <li><a href="https://github.com/vercel/next.js/tree/canary/examples/custom-routes-proxying">Incremental adoption of Next.js</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-zones">Using Multiple Zones</a></li> </ul> </details> -Rewrites allow you to rewrite to an external url. This is especially useful for incrementally adopting Next.js. +Rewrites allow you to rewrite to an external url. This is especially useful for incrementally adopting Next.js. The following is an example rewrite for redirecting the `/blog` route of your main app to an external site. ```js module.exports = { async rewrites() { return [ + { + source: '/blog', + destination: 'https://example.com/blog', + }, { source: '/blog/:slug', destination: 'https://example.com/blog/:slug', // Matched parameters can be used in the destination @@ -318,6 +323,26 @@ module.exports = { } ``` +If you're using `trailingSlash: true`, you also need to insert a trailing slash in the `source` paramater. If the destination server is also expecting a trailing slash it should be included in the `destination` parameter as well. + +```js +module.exports = { + trailingSlash: 'true', + async rewrites() { + return [ + { + source: '/blog/', + destination: 'https://example.com/blog/', + }, + { + source: '/blog/:path*/', + destination: 'https://example.com/blog/:path*/', + }, + ] + }, +} +``` + ### Incremental adoption of Next.js You can also have Next.js fall back to proxying to an existing website after checking all Next.js routes. diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 830771ce34471..4905a0f1125a5 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,7 +16,8 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | -| `v12.0.9` | `lazyRoot` prop added | +| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | +| `v12.0.9` | `lazyRoot` prop added. | | `v12.0.0` | `formats` configuration added.<br/>AVIF support added.<br/>Wrapper `<div>` changed to `<span>`. | | `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | | `v11.0.0` | `src` prop support for static import.<br/>`placeholder` prop added.<br/>`blurDataURL` prop added. | @@ -220,11 +221,57 @@ You can also [generate a solid color Data URL](https://png-pixel.com) to match t A string (with similar syntax to the margin property) that acts as the bounding box used to detect the intersection of the viewport with the image and trigger lazy [loading](#loading). Defaults to `"200px"`. +If the image is nested in a scrollable parent element other than the root document, you will also need to assign the [lazyRoot](#lazyroot) prop. + [Learn more](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) ### lazyRoot -A React [Ref](https://reactjs.org/docs/refs-and-the-dom.html) pointing to the Element which the [lazyBoundary](#lazyBoundary) calculates for the Intersection detection. Defaults to `null`, referring to the document viewport. +A React [Ref](https://reactjs.org/docs/refs-and-the-dom.html) pointing to the scrollable parent element. Defaults to `null` (the document viewport). + +The Ref must point to a DOM element or a React component that [forwards the Ref](https://reactjs.org/docs/forwarding-refs.html) to the underlying DOM element. + +**Example pointing to a DOM element** + +```jsx +import Image from 'next/image' +import React from 'react' + +const lazyRoot = React.useRef(null) + +const Example = () => ( + <div ref={lazyRoot} style={{ overflowX: 'scroll', width: '500px' }}> + <Image lazyRoot={lazyRoot} src="/one.jpg" width="500" height="500" /> + <Image lazyRoot={lazyRoot} src="/two.jpg" width="500" height="500" /> + </div> +) +``` + +**Example pointing to a React component** + +```jsx +import Image from 'next/image' +import React from 'react' + +const Container = React.forwardRef((props, ref) => { + return ( + <div ref={ref} style={{ overflowX: 'scroll', width: '500px' }}> + {props.children} + </div> + ) +}) + +const Example = () => { + const lazyRoot = React.useRef(null) + + return ( + <Container ref={lazyRoot}> + <Image lazyRoot={lazyRoot} src="/one.jpg" width="500" height="500" /> + <Image lazyRoot={lazyRoot} src="/two.jpg" width="500" height="500" /> + </Container> + ) +} +``` [Learn more](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root) @@ -277,7 +324,7 @@ module.exports = { The following Image Optimization cloud providers are included: - Default: Works automatically with `next dev`, `next start`, or a custom server -- [Vercel](https://vercel.com): Works automatically when you deploy on Vercel, no configuration necessary. [Learn more](https://vercel.com/docs/next.js/image-optimization) +- [Vercel](https://vercel.com): Works automatically when you deploy on Vercel, no configuration necessary. [Learn more](https://vercel.com/docs/concepts/image-optimization) - [Imgix](https://www.imgix.com): `loader: 'imgix'` - [Cloudinary](https://cloudinary.com): `loader: 'cloudinary'` - [Akamai](https://www.akamai.com): `loader: 'akamai'` @@ -355,7 +402,7 @@ module.exports = { The following describes the caching algorithm for the default [loader](#loader). For all other loaders, please refer to your cloud provider's documentation. -Images are optimized dynamically upon request and stored in the `<distDir>/cache/images` directory. The optimized image file will be served for subsequent requests until the expiration is reached. When a request is made that matches a cached but expired file, the cached file is deleted before generating a new optimized image and caching the new file. +Images are optimized dynamically upon request and stored in the `<distDir>/cache/images` directory. The optimized image file will be served for subsequent requests until the expiration is reached. When a request is made that matches a cached but expired file, the expired image is served stale immediately. Then the image is optimized again in the background (also called revalidation) and saved to the cache with the new expiration date. The expiration (or rather Max Age) is defined by either the [`minimumCacheTTL`](#minimum-cache-ttl) configuration or the upstream server's `Cache-Control` header, whichever is larger. Specifically, the `max-age` value of the `Cache-Control` header is used. If both `s-maxage` and `max-age` are found, then `s-maxage` is preferred. @@ -393,6 +440,21 @@ module.exports = { } ``` +### Dangerously Allow SVG + +The default [loader](#loader) does not optimize SVG images for a few reasons. First, SVG is a vector format meaning it can be resized losslessly. Second, SVG has many of the same features as HTML/CSS, which can lead to vulnerabilities without proper [Content Security Policy (CSP) headers](/docs/advanced-features/security-headers.md). + +If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` and `contentSecurityPolicy` inside your `next.config.js`: + +```js +module.exports = { + images: { + dangerouslyAllowSVG: true, + contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", + }, +} +``` + ## Related For an overview of the Image component features and usage guidelines, see: diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index 69cacb1e66974..9c0e78dfd784e 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -50,7 +50,7 @@ The following is the definition of the `router` object returned by both [`useRou - `locales`: `String[]` - All supported locales (if enabled). - `defaultLocale`: `String` - The current default locale (if enabled). - `domainLocales`: `Array<{domain, defaultLocale, locales}>` - Any configured domain locales. -- `isReady`: `boolean` - Whether the router fields are updated client-side and ready for use. Should only be used inside of `useEffect` methods and not for conditionally rendering on the server. +- `isReady`: `boolean` - Whether the router fields are updated client-side and ready for use. Should only be used inside of `useEffect` methods and not for conditionally rendering on the server. See related docs for use case with [automatically statically optimized pages](/docs/advanced-features/automatic-static-optimization.md) - `isPreview`: `boolean` - Whether the application is currently in [preview mode](/docs/advanced-features/preview-mode.md). The following methods are included inside `router`: diff --git a/docs/api-reference/next/streaming.md b/docs/api-reference/next/streaming.md new file mode 100644 index 0000000000000..210b18c48df36 --- /dev/null +++ b/docs/api-reference/next/streaming.md @@ -0,0 +1,95 @@ +--- +description: Streaming related APIs to build Next.js apps in streaming SSR or with React Server Components. +--- + +# next/streaming + +The experimental `next/streaming` module provides streaming related APIs to port the existing functionality of Next.js apps to streaming scenarios and facilitate the usage of React Server Components. + +## unstable_useWebVitalsReport + +Next.js provides an `_app` component-level function, [`reportWebVitals`](docs/advanced-features/measuring-performance), for tracking performance metrics. With Server Components, you may have a pure server-side custom `_app` component (which doesn't run client effects) so the existing API won't work. + +With the new `unstable_useWebVitalsReport` API, you're able to track [Core Web Vitals](https://nextjs.org/learn/seo/web-performance) in client components: + +```jsx +// pages/_app.js +import { unstable_useWebVitalsReport } from 'next/streaming' + +export default function Home() { + unstable_useWebVitalsReport((data) => { + console.log(data) + }) + + return <div>Home Page</div> +} +``` + +This method could also be used to replace statically exported `reportWebVitals` functions in your existing `_app`: + +```jsx +// pages/_app.server.js +import Layout from '../components/layout.client.js' + +export default function App({ children }) { + return <Layout>{children}</Layout> +} +``` + +```jsx +// components/layout.client.js +import { unstable_useWebVitalsReport } from 'next/streaming' + +export default function Layout() { + unstable_useWebVitalsReport((data) => { + console.log(data) + }) + + return ( + <div className="container"> + <h1>Hello</h1> + <div className="main">{children}</div> + </div> + ) +} +``` + +## unstable_useRefreshRoot + +Since Server Components are rendered on the server-side, in some cases you might need to partially refresh content from the server. + +For example, a search bar (client component) which displays search results as server components. You'd want to update the search results while typing and rerender the results list with a certain frequency (e.g. with each keystroke or on a debounce). + +The `unstable_useRefreshRoot` hook returns a `refresh` API to let you re-render the React tree smoothly without flickering. This is only allowed for use on the client-side and will only affect Server Components at the moment. + +```jsx +// pages/index.server.js +import Search from '../components/search.client.js' +import SearchResults from '../components/search-results.server.js' + +function Home() { + return ( + <div> + <Search /> + <SearchResults /> + </div> + ) +} +``` + +```jsx +// components/search.client.js +import { unstable_useRefreshRoot as useRefreshRoot } from 'next/streaming' + +export default function Search() { + const refresh = useRefreshRoot() + + return ( + <SearchUI + onChange={() => { + refresh() + }} + /> + ) +} +``` diff --git a/docs/api-routes/api-middlewares.md b/docs/api-routes/api-middlewares.md index 7f1b41d398d9b..1815a1192dea8 100644 --- a/docs/api-routes/api-middlewares.md +++ b/docs/api-routes/api-middlewares.md @@ -34,7 +34,9 @@ export const config = { The `api` object includes all configs available for API routes. -`bodyParser` Enables body parsing, you can disable it if you want to consume it as a `Stream`: +`bodyParser` is automatically enabled. If you want to consume the body as a `Stream` or with [`raw-body`](https://www.npmjs.com/package/raw-body), you can set this to `false`. + +One use case for disabling the automatic `bodyParsing` is to allow you to verify the raw body of a **webhook** request, for example [from GitHub](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks#validating-payloads-from-github). ```js export const config = { @@ -66,6 +68,29 @@ export const config = { } ``` +`responseLimit` is automatically enabled, warning when an API routes' response body is over 4MB. + +If you are not using Next.js in a serverless environment, and understand the performance implications of not using a CDN or dedicated media host, you can set this limit to `false`. + +```js +export const config = { + api: { + responseLimit: false, + }, +} +``` + +`responseLimit` can also take the number of bytes or any string format supported by `bytes`, for example `1000`, `'500kb'` or `'3mb'`. +This value will be the maximum response size before a warning is displayed. Default is 4MB. (see above) + +```js +export const config = { + api: { + responseLimit: '8mb', + }, +} +``` + ## Connect/Express middleware support You can also use [Connect](https://github.com/senchalabs/connect) compatible middleware. diff --git a/docs/api-routes/introduction.md b/docs/api-routes/introduction.md index 4274f7d0f7b2b..3177caa5a68b1 100644 --- a/docs/api-routes/introduction.md +++ b/docs/api-routes/introduction.md @@ -29,8 +29,8 @@ export default function handler(req, res) { For an API route to work, you need to export a function as default (a.k.a **request handler**), which then receives the following parameters: -- `req`: An instance of [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage), plus some [pre-built middlewares](/docs/api-routes/api-middlewares.md) -- `res`: An instance of [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse), plus some [helper functions](/docs/api-routes/response-helpers.md) +- `req`: An instance of [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage), plus some [pre-built middlewares](/docs/api-routes/api-middlewares.md) +- `res`: An instance of [http.ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse), plus some [helper functions](/docs/api-routes/response-helpers.md) To handle different HTTP methods in an API route, you can use `req.method` in your request handler, like so: diff --git a/docs/api-routes/response-helpers.md b/docs/api-routes/response-helpers.md index 41673745b259b..0e3af609fbc6e 100644 --- a/docs/api-routes/response-helpers.md +++ b/docs/api-routes/response-helpers.md @@ -12,6 +12,7 @@ The included helpers are: - `res.json(body)` - Sends a JSON response. `body` must be a [serializable object](https://developer.mozilla.org/en-US/docs/Glossary/Serialization) - `res.send(body)` - Sends the HTTP response. `body` can be a `string`, an `object` or a `Buffer` - `res.redirect([status,] path)` - Redirects to a specified path or URL. `status` must be a valid [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). If not specified, `status` defaults to "307" "Temporary redirect". +- `res.unstable_revalidate(urlPath)` - [Revalidate a page on demand](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) using `getStaticProps`. `urlPath` must be a `string`. ## Setting the status code of a response diff --git a/docs/authentication.md b/docs/authentication.md index b62f4b2879afd..e500718664845 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -133,14 +133,15 @@ To see examples with other authentication providers, check out the [examples fol <details open> <summary><b>Examples</b></summary> <ul> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-firebase-authentication">with-firebase-authentication</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-magic">with-magic</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/auth0">auth0</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db">with-supabase-auth-realtime-db</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-userbase">with-userbase</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-supertokens">with-supertokens</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-nhost-auth-realtime-graphql">with-nhost-auth-realtime-graphql</a></li> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-clerk">with-clerk</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/auth0">Auth0</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-clerk">Clerk</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-firebase-authentication">Firebase</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-magic">Magic</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-nhost-auth-realtime-graphql">Nhost</a></li> + <li><a href="https://github.com/vercel/examples/tree/main/solutions/auth-with-ory">Ory</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db">Supabase</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-supertokens">Supertokens</a></li> + <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-userbase">Userbase</a></li> </ul> </details> diff --git a/docs/basic-features/data-fetching/client-side.md b/docs/basic-features/data-fetching/client-side.md index 168bb59967def..6b1768a6e901b 100644 --- a/docs/basic-features/data-fetching/client-side.md +++ b/docs/basic-features/data-fetching/client-side.md @@ -30,7 +30,7 @@ function Profile() { }, []) if (isLoading) return <p>Loading...</p> - if (!profileData) return <p>No profile data</p> + if (!data) return <p>No profile data</p> return ( <div> @@ -68,3 +68,14 @@ function Profile() { ) } ``` + +## Related + +For more information on what to do next, we recommend the following sections: + +<div class="card"> + <a href="/docs/routing/introduction.md"> + <b>Routing:</b> + <small>Learn more about routing in Next.js.</small> + </a> +</div> diff --git a/docs/basic-features/data-fetching/get-server-side-props.md b/docs/basic-features/data-fetching/get-server-side-props.md index 22aa4d77dbf73..da0463f1fecd5 100644 --- a/docs/basic-features/data-fetching/get-server-side-props.md +++ b/docs/basic-features/data-fetching/get-server-side-props.md @@ -19,7 +19,7 @@ export async function getServerSideProps(context) { `getServerSideProps` only runs on server-side and never runs on the browser. If a page uses `getServerSideProps`, then: - When you request this page directly, `getServerSideProps` runs at request time, and this page will be pre-rendered with the returned props -- When you request this page on client-side page transitions through [`next/link`](/docs/api-reference/next/link.md) or [`next/router`](/docs/api-reference/next/router.md), Next.js sends an API request to the server, which runs `getServerSideProps`. +- When you request this page on client-side page transitions through [`next/link`](/docs/api-reference/next/link.md) or [`next/router`](/docs/api-reference/next/router.md), Next.js sends an API request to the server, which runs `getServerSideProps` It then returns `JSON` that contains the result of running `getServerSideProps`, that `JSON` will be used to render the page. All this work will be handled automatically by Next.js, so you don’t need to do anything extra as long as you have `getServerSideProps` defined. @@ -47,8 +47,8 @@ Take the following example. An API route is used to fetch some data from a CMS. If your page contains frequently updating data, and you don’t need to pre-render the data, you can fetch the data on the [client side](/docs/basic-features/data-fetching/client-side.md). An example of this is user-specific data: -- First, immediately show the page without data. Parts of the page can be pre-rendered using Static Generation. You can show loading states for missing data. -- Then, fetch the data on the client side and display it when ready. +- First, immediately show the page without data. Parts of the page can be pre-rendered using Static Generation. You can show loading states for missing data +- Then, fetch the data on the client side and display it when ready This approach works well for user dashboard pages, for example. Because a dashboard is a private, user-specific page, SEO is not relevant and the page doesn’t need to be pre-rendered. The data is frequently updated, which requires request-time data fetching. @@ -74,6 +74,14 @@ export async function getServerSideProps() { export default Page ``` +## Does getServerSideProps render an error page + +If an error is thrown inside `getServerSideProps`, it will show the `pages/500.js` file. Check out the documentation for [500 page](/docs/advanced-features/custom-error-page#500-page) to learn more on how to create it. During development this file will not be used and the dev overlay will be shown instead. + +## Related + +For more information on what to do next, we recommend the following sections: + <div class="card"> <a href="/docs/api-reference/data-fetching/get-server-side-props.md"> <b>getServerSideProps API Reference</b> diff --git a/docs/basic-features/data-fetching/get-static-paths.md b/docs/basic-features/data-fetching/get-static-paths.md index cb9b5b68b4381..a5fa8d9c7453e 100644 --- a/docs/basic-features/data-fetching/get-static-paths.md +++ b/docs/basic-features/data-fetching/get-static-paths.md @@ -19,7 +19,7 @@ export async function getStaticPaths() { } ``` -Note that`getStaticProps` **must** be used with `getStaticPaths`, and that you **cannot** use it with [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). +`getStaticPaths` **must** be used with `getStaticProps`. You **cannot** use it with [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). The [`getStaticPaths` API reference](/docs/api-reference/data-fetching/get-static-paths.md) covers all parameters and props that can be used with `getStaticPaths`. @@ -35,11 +35,15 @@ You should use `getStaticPaths` if you’re statically pre-rendering pages that ## When does getStaticPaths run -`getStaticPaths` only runs at build time on server-side. If you're using [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md), `getStaticPaths` can also be run on-demand _in the background_, but still only on the server-side. +`getStaticPaths` will only run during build in production, it will not be called during runtime. You can validate code written inside `getStaticPaths` is removed from the client-side bundle [with this tool](https://next-code-elimination.vercel.app/). + +- `getStaticProps` runs during `next build` for any `paths` returned during build +- `getStaticProps` runs in the background when using `fallback: true` +- `getStaticProps` is called before initial render when using `fallback: blocking` ## Where can I use getStaticPaths -`getStaticPaths` can only be exported from a **page**. You **cannot** export it from non-page files. +`getStaticPaths` can only be exported from a [dynamic route](/docs/routing/dynamic-routes.md) that also uses `getStaticProps`. You **cannot** export it from non-page files e.g. from your `components` folder. Note that you must use export `getStaticPaths` as a standalone function — it will **not** work if you add `getStaticPaths` as a property of the page component. @@ -47,6 +51,10 @@ Note that you must use export `getStaticPaths` as a standalone function — it w In development (`next dev`), `getStaticPaths` will be called on every request. +## Related + +For more information on what to do next, we recommend the following sections: + <div class="card"> <a href="/docs/api-reference/data-fetching/get-static-paths.md"> <b>getStaticPaths API Reference</b> diff --git a/docs/basic-features/data-fetching/get-static-props.md b/docs/basic-features/data-fetching/get-static-props.md index b3485fa8a2ad3..a5a5190fda979 100644 --- a/docs/basic-features/data-fetching/get-static-props.md +++ b/docs/basic-features/data-fetching/get-static-props.md @@ -23,9 +23,17 @@ You should use `getStaticProps` if: - The data can be publicly cached (not user-specific) - The page must be pre-rendered (for SEO) and be very fast — `getStaticProps` generates `HTML` and `JSON` files, both of which can be cached by a CDN for performance +## When does getStaticProps run + +`getStaticProps` always runs on the server and never on the client. You can validate code written inside `getStaticProps` is removed from the client-side bundle [with this tool](https://next-code-elimination.vercel.app/). + +- `getStaticProps` always runs during `next build` +- `getStaticProps` runs in the background when using `revalidate` +- `getStaticProps` runs on-demand in the background when using [`unstable_revalidate`](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) + When combined with [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md), `getStaticProps` will run in the background while the stale page is being revalidated, and the fresh page served to the browser. -Because `getStaticProps` runs at build time, it does **not** have access to the incoming request (such as query parameters or `HTTP` headers) as it generates static `HTML`. If you need access to the request for your page, consider using [Middleware](/docs/middleware.md) in addition to `getStaticProps`. +`getStaticProps` does not have access to the incoming request (such as query parameters or HTTP headers) as it generates static HTML. If you need access to the request for your page, consider using [Middleware](/docs/middleware.md) in addition to `getStaticProps`. ## Using getStaticProps to fetch data from a CMS @@ -72,7 +80,35 @@ As `getStaticProps` runs only on the server-side, it will never run on the clien This means that instead of fetching an **API route** from `getStaticProps` (that itself fetches data from an external source), you can write the server-side code directly in `getStaticProps`. -Take the following example. An API route is used to fetch some data from a CMS. That API route is then called directly from `getStaticProps`. This produces an additional call, reducing performance. Instead, the logic for fetching the data from the CMS can be moved to `getStaticProps`. +Take the following example. An API route is used to fetch some data from a CMS. That API route is then called directly from `getStaticProps`. This produces an additional call, reducing performance. Instead, the logic for fetching the data from the CMS can be shared by using a `lib/` directory. Then it can be shared with `getStaticProps`. + +```jsx +// lib/fetch-posts.js + +// The following function is shared +// with getStaticProps and API routes +// from a `lib/` directory +export async function loadPosts() { + // Call an external API endpoint to get posts + const res = await fetch('https://.../posts/') + const data = await res.json() + + return data +} + +// pages/blog.js +import { loadPosts } from '../lib/load-posts' + +// This function runs only on the server side +export async function getStaticProps() { + // Instead of fetching your `/api` route you can call the same + // function directly in `getStaticProps` + const posts = await loadPosts() + + // Props returned will be passed to the page component + return { props: { posts } } +} +``` Alternatively, if you are **not** using API routes to fetch data, then the [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API _can_ be used directly in `getStaticProps` to fetch data. @@ -84,7 +120,7 @@ When a page with `getStaticProps` is pre-rendered at build time, in addition to This JSON file will be used in client-side routing through [`next/link`](/docs/api-reference/next/link.md) or [`next/router`](/docs/api-reference/next/router.md). When you navigate to a page that’s pre-rendered using `getStaticProps`, Next.js fetches this JSON file (pre-computed at build time) and uses it as the props for the page component. This means that client-side page transitions will **not** call `getStaticProps` as only the exported JSON is used. -When using Incremental Static Generation `getStaticProps` will be executed out of band to generate the JSON needed for client-side navigation. You may see this in the form of multiple requests being made for the same page, however, this is intended and has no impact on end-user performance +When using Incremental Static Generation, `getStaticProps` will be executed in the background to generate the JSON needed for client-side navigation. You may see this in the form of multiple requests being made for the same page, however, this is intended and has no impact on end-user performance. ## Where can I use getStaticProps @@ -100,9 +136,11 @@ In development (`next dev`), `getStaticProps` will be called on every request. ## Preview Mode -In some cases, you might want to temporarily bypass Static Generation and render the page at **request time** instead of build time. For example, you might be using a headless CMS and want to preview drafts before they're published. +You can temporarily bypass static generation and render the page at **request time** instead of build time using [**Preview Mode**](/docs/advanced-features/preview-mode.md). For example, you might be using a headless CMS and want to preview drafts before they're published. + +## Related -This use case is supported in Next.js by the [**Preview Mode**](/docs/advanced-features/preview-mode.md) feature. +For more information on what to do next, we recommend the following sections: <div class="card"> <a href="/docs/api-reference/data-fetching/get-static-props.md"> diff --git a/docs/basic-features/data-fetching/incremental-static-regeneration.md b/docs/basic-features/data-fetching/incremental-static-regeneration.md index 7a7d197c3d09c..fa0a1b350e8d5 100644 --- a/docs/basic-features/data-fetching/incremental-static-regeneration.md +++ b/docs/basic-features/data-fetching/incremental-static-regeneration.md @@ -16,22 +16,24 @@ description: 'Learn how to create or update static pages at runtime with Increme <details> <summary><b>Version History</b></summary> -| Version | Changes | -| -------- | ---------------- | -| `v9.5.0` | Base Path added. | +| Version | Changes | +| --------- | --------------------------------------------------------------------------------------- | +| `v12.1.0` | On-demand ISR added (Beta). | +| `v12.0.0` | [Bot-aware ISR fallback](https://nextjs.org/blog/next-12#bot-aware-isr-fallback) added. | +| `v9.5.0` | Base Path added. | </details> Next.js allows you to create or update static pages _after_ you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, **without needing to rebuild the entire site**. With ISR, you can retain the benefits of static while scaling to millions of pages. -To use ISR add the `revalidate` prop to `getStaticProps`: +To use ISR, add the `revalidate` prop to `getStaticProps`: ```jsx function Blog({ posts }) { return ( <ul> {posts.map((post) => ( - <li>{post.title}</li> + <li key={post.id}>{post.title}</li> ))} </ul> ) @@ -81,8 +83,101 @@ When a request is made to a page that was pre-rendered at build time, it will in - Any requests to the page after the initial request and before 10 seconds are also cached and instantaneous. - After the 10-second window, the next request will still show the cached (stale) page - Next.js triggers a regeneration of the page in the background. -- Once the page has been successfully generated, Next.js will invalidate the cache and show the updated page. If the background regeneration fails, the old page would still be unaltered. +- Once the page generates successfully, Next.js will invalidate the cache and show the updated page. If the background regeneration fails, the old page would still be unaltered. -When a request is made to a path that hasn’t been generated, Next.js will server-render the page on the first request. Future requests will serve the static file from the cache. +When a request is made to a path that hasn’t been generated, Next.js will server-render the page on the first request. Future requests will serve the static file from the cache. ISR on Vercel [persists the cache globally and handles rollbacks](https://vercel.com/docs/concepts/next.js/incremental-static-regeneration). -[Incremental Static Regeneration](https://vercel.com/docs/concepts/next.js/incremental-static-regeneration) covers how to persist the cache globally and handle rollbacks. +## On-demand Revalidation (Beta) + +If you set a `revalidate` time of `60`, all visitors will see the same generated version of your site for one minute. The only way to invalidate the cache is from someone visiting that page after the minute has passed. + +Starting with `v12.1.0`, Next.js supports on-demand Incremental Static Regeneration to manually purge the Next.js cache for a specific page. This makes it easier to update your site when: + +- Content from your headless CMS is created or updated +- Ecommerce metadata changes (price, description, category, reviews, etc.) + +Inside `getStaticProps`, you do not need to specify `revalidate` to use on-demand revalidation. If `revalidate` is omitted, Next.js will use the default value of `false` (no revalidation) and only revalidate the page on-demand when `unstable_revalidate` is called. + +### Using On-Demand Revalidation + +First, create a secret token only known by your Next.js app. This secret will be used to prevent unauthorized access to the revalidation API Route. You can access the route (either manually or with a webhook) with the following URL structure: + +```bash +https://<your-site.com>/api/revalidate?secret=<token> +``` + +Next, add the secret as an [Environment Variable](/docs/basic-features/environment-variables.md) to your application. Finally, create the revalidation API Route: + +```jsx +// pages/api/revalidate.js + +export default async function handler(req, res) { + // Check for secret to confirm this is a valid request + if (req.query.secret !== process.env.MY_SECRET_TOKEN) { + return res.status(401).json({ message: 'Invalid token' }) + } + + try { + await res.unstable_revalidate('/path-to-revalidate') + return res.json({ revalidated: true }) + } catch (err) { + // If there was an error, Next.js will continue + // to show the last successfully generated page + return res.status(500).send('Error revalidating') + } +} +``` + +[View our demo](https://on-demand-isr.vercel.app) to see on-demand revalidation in action and provide feedback. + +### Testing on-demand ISR during development + +When running locally with `next dev`, `getStaticProps` is invoked on every request. To verify your on-demand ISR configuration is correct, you will need to create a [production build](/docs/api-reference/cli.md#build) and start the [production server](/docs/api-reference/cli.md#production): + +```bash +$ next build +$ next start +``` + +Then, you are able to validate static pages are successfully revalidated. + +## Error handling and revalidation + +If there is an error inside `getStaticProps` when handling background regeneration, or you manually throw an error, the last successfully generated page will continue to show. On the next subsequent request, Next.js will retry calling `getStaticProps`. + +```jsx +export async function getStaticProps() { + // If this request throws an uncaught error, Next.js will + // not invalidate the currently shown page and + // retry getStaticProps on the next request. + const res = await fetch('https://.../posts') + const posts = await res.json() + + if (!res.ok) { + // If there is a server error, you might want to + // throw an error instead of returning so that the cache is not updated + // until the next successful request. + throw new Error(`Failed to fetch posts, received status ${res.status}`) + } + + // If the request was successful, return the posts + // and revalidate every 10 seconds. + return { + props: { + posts, + }, + revalidate: 10, + } +} +``` + +## Related + +For more information on what to do next, we recommend the following sections: + +<div class="card"> + <a href="/docs/basic-features/data-fetching/get-static-paths.md"> + <b>Dynamic routing</b> + <small>Learn more about dynamic routing in Next.js with getStaticPaths.</small> + </a> +</div> diff --git a/docs/basic-features/environment-variables.md b/docs/basic-features/environment-variables.md index 91182cbb2f3f9..d3b89f531ddee 100644 --- a/docs/basic-features/environment-variables.md +++ b/docs/basic-features/environment-variables.md @@ -149,3 +149,29 @@ export default async () => { loadEnvConfig(projectDir) } ``` + +## Environment Variable Load Order + +Depending on the environment (as set by `NODE_ENV`), Environment Variables are loaded from the following sources in top-to-bottom order. In all environments, the existing `env` is not overridden by following sources: + +`NODE_ENV=production` + +1. `.env.production.local` +1. `.env.local` +1. `.env.production` +1. `.env` + +`NODE_ENV=development` + +1. `.env.development.local` +1. `.env.local` +1. `.env.development` +1. `.env` + +`NODE_ENV=test` + +1. `.env.test.local` +1. `.env.test` +1. `.env` + +> **Note:** `.env.local` is not loaded when `NODE_ENV=test`. diff --git a/docs/basic-features/font-optimization.md b/docs/basic-features/font-optimization.md index 2948bd8402e2d..217267338e5a2 100644 --- a/docs/basic-features/font-optimization.md +++ b/docs/basic-features/font-optimization.md @@ -23,29 +23,7 @@ By default, Next.js will automatically inline font CSS at build time, eliminatin ## Usage -To add a web font to your Next.js application, override `next/head`. For example, you can add a font to a specific page: - -```js -// pages/index.js - -import Head from 'next/head' - -export default function IndexPage() { - return ( - <div> - <Head> - <link - href="https://fonts.googleapis.com/css2?family=Inter&display=optional" - rel="stylesheet" - /> - </Head> - <p>Hello world!</p> - </div> - ) -} -``` - -or to your entire application with a [Custom `Document`](/docs/advanced-features/custom-document.md). +To add a web font to your Next.js application, add the font to a [Custom `Document`](/docs/advanced-features/custom-document.md). ```js // pages/_document.js @@ -74,10 +52,14 @@ class MyDocument extends Document { export default MyDocument ``` +Note that we don't recommend adding fonts with `next/head`, as this only applies the font to the particular page and won't work with a streaming architecture. + Automatic Webfont Optimization currently supports Google Fonts and Typekit with support for other font providers coming soon. We're also planning to add control over [loading strategies](https://github.com/vercel/next.js/issues/21555) and `font-display` values. See [Google Font Display](https://nextjs.org/docs/messages/google-font-display) for more information. +> **Note**: Font Optimization does not currently support self-hosted fonts. + ## Disabling Optimization If you do not want Next.js to optimize your fonts, you can opt-out. diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index d59b9338f8abc..0c7cb5a6a292b 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -15,8 +15,8 @@ The Next.js Image component, [`next/image`](/docs/api-reference/next/image.md), Some of the optimizations built into the Image component include: -- **Improved Performance:** Always serve correctly sized image for each device, using modern image formats. -- **Visual Stability:** Prevent [Cumulative Layout Shift](https://nextjs.org/learn/seo/web-performance/cls) automatically. +- **Improved Performance:** Always serve correctly sized image for each device, using modern image formats +- **Visual Stability:** Prevent [Cumulative Layout Shift](https://nextjs.org/learn/seo/web-performance/cls) automatically - **Faster Page Loads:** Images are only loaded when they enter the viewport, with optional blur-up placeholders - **Asset Flexibility:** On-demand image resizing, even for images stored on remote servers diff --git a/docs/basic-features/layouts.md b/docs/basic-features/layouts.md index ea9bfd6f1424e..1bc5c1aa92509 100644 --- a/docs/basic-features/layouts.md +++ b/docs/basic-features/layouts.md @@ -86,7 +86,7 @@ export default function MyApp({ Component, pageProps }) { } ``` -When navigating between pages, we want to *persist* page state (input values, scroll position, etc) for a Single-Page Application (SPA) experience. +When navigating between pages, we want to *persist* page state (input values, scroll position, etc.) for a Single-Page Application (SPA) experience. This layout pattern enables state persistence because the React component tree is maintained between page transitions. With the component tree, React can understand which elements have changed to preserve state. diff --git a/docs/basic-features/typescript.md b/docs/basic-features/typescript.md index db23d29dad073..9869dc850e431 100644 --- a/docs/basic-features/typescript.md +++ b/docs/basic-features/typescript.md @@ -5,14 +5,19 @@ description: Next.js supports TypeScript by default and has built-in types for p # TypeScript <details> - <summary><b>Examples</b></summary> - <ul> - <li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-typescript">TypeScript</a></li> - </ul> + <summary><b>Version History</b></summary> + +| Version | Changes | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `v12.0.0` | [SWC](https://nextjs.org/docs/advanced-features/compiler) is now used by default to compile TypeScript and TSX for faster builds. | +| `v10.2.1` | [Incremental type checking](https://www.typescriptlang.org/tsconfig#incremental) support added when enabled in your `tsconfig.json`. | + </details> -Next.js provides an integrated [TypeScript](https://www.typescriptlang.org/) -experience out of the box, similar to an IDE. +Next.js provides an integrated [TypeScript](https://www.typescriptlang.org/) experience, including zero-configuration set up and built-in types for Pages, APIs, and more. + +- [Clone and deploy the TypeScript starter](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-typescript&project-name=with-typescript&repository-name=with-typescript) +- [View an example application](https://github.com/vercel/next.js/tree/canary/examples/with-typescript) ## `create-next-app` support @@ -120,26 +125,11 @@ export default (req: NextApiRequest, res: NextApiResponse<Data>) => { If you have a [custom `App`](/docs/advanced-features/custom-app.md), you can use the built-in type `AppProps` and change file name to `./pages/_app.tsx` like so: ```ts -// import App from "next/app"; -import type { AppProps /*, AppContext */ } from 'next/app' +import type { AppProps } from 'next/app' -function MyApp({ Component, pageProps }: AppProps) { +export default function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} /> } - -// Only uncomment this method if you have blocking data requirements for -// every single page in your application. This disables the ability to -// perform automatic static optimization, causing every page in your app to -// be server-side rendered. -// -// MyApp.getInitialProps = async (appContext: AppContext) => { -// // calls page's `getInitialProps` and fills `appProps.pageProps` -// const appProps = await App.getInitialProps(appContext); - -// return { ...appProps } -// } - -export default MyApp ``` ## Path aliases and baseUrl @@ -170,3 +160,25 @@ module.exports = nextConfig Since `v10.2.1` Next.js supports [incremental type checking](https://www.typescriptlang.org/tsconfig#incremental) when enabled in your `tsconfig.json`, this can help speed up type checking in larger applications. It is highly recommended to be on at least `v4.3.2` of TypeScript to experience the [best performance](https://devblogs.microsoft.com/typescript/announcing-typescript-4-3/#lazier-incremental) when leveraging this feature. + +## Ignoring TypeScript Errors + +Next.js fails your **production build** (`next build`) when TypeScript errors are present in your project. + +If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step. + +If disabled, be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous. + +Open `next.config.js` and enable the `ignoreBuildErrors` option in the `typescript` config: + +```js +module.exports = { + typescript: { + // !! WARN !! + // Dangerously allow production builds to successfully complete even if + // your project has type errors. + // !! WARN !! + ignoreBuildErrors: true, + }, +} +``` diff --git a/docs/deployment.md b/docs/deployment.md index 591ea8aab8a5e..f6c14f12436c4 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -28,9 +28,9 @@ All JavaScript code inside `.next` has been **compiled** and browser bundles hav ## Managed Next.js with Vercel -[Vercel](https://vercel.com/) is a frontend cloud platform from the creators of Next.js. It's the fastest way to deploy your managed Next.js application with zero configuration. +[Vercel](https://vercel.com?utm_source=github.com&utm_medium=referral&utm_campaign=deployment) is the fastest way to deploy your Next.js application with zero configuration. -When deploying to Vercel, the platform automatically detects Next.js, runs `next build`, and optimizes the build output for you, including: +When deploying to Vercel, the platform [automatically detects Next.js](https://vercel.com/solutions/nextjs?utm_source=github.com&utm_medium=referral&utm_campaign=deployment), runs `next build`, and optimizes the build output for you, including: - Persisting cached assets across deployments if unchanged - [Immutable deployments](https://vercel.com/features/previews) with a unique URL for every commit @@ -49,9 +49,7 @@ In addition, Vercel provides features like: - Support for [Image Optimization](/docs/basic-features/image-optimization.md) with `next/image` - Instant global deployments via `git push` -You can start using Vercel (for free) through a personal hobby account, or create a team to start the next big thing. Learn more about [Next.js on Vercel](https://vercel.com/solutions/nextjs) or read the [Vercel Documentation](https://vercel.com/docs). - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/hello-world&project-name=hello-world&repository-name=hello-world&utm_source=github.com&utm_medium=referral&utm_campaign=deployment) +[Deploy a Next.js application to Vercel](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/hello-world&project-name=hello-world&repository-name=hello-world&utm_source=github.com&utm_medium=referral&utm_campaign=deployment) for free to try it out. ## Self-Hosting diff --git a/docs/faq.md b/docs/faq.md index 96025209501f5..fe9ca68380340 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,65 +5,66 @@ description: Get to know more about Next.js with the frequently asked questions. # Frequently Asked Questions <details> - <summary>Is this production ready?</summary> - <p>Next.js has been powering <a href="https://vercel.com">https://vercel.com</a>  since its inception.</p> - - <p>We’re ecstatic about both the developer experience and end-user performance, so we decided to share it with the community.</p> + <summary>Is Next.js production ready?</summary> + <p>Yes! Next.js is used by many of the top websites in the world. See the + <a href="https://nextjs.org/showcase">Showcase</a> for more info.</p> </details> <details> - <summary>How big is it?</summary> - <p>The client side bundle size should be measured in a per-app basis. A small Next main bundle is around 65kb gzipped.</p> -</details> - -<details> - <summary>How can I change the internal webpack configs?</summary> - <p>Next.js tries its best to remove the overhead of webpack configurations, but for advanced cases where more control is needed, refer to the <a href="/docs/api-reference/next.config.js/custom-webpack-config.md">custom webpack config documentation</a>.</p> -</details> - -<details> - <summary>What syntactic features are compiled? How do I change them?</summary> - <p>We track V8. Since V8 has wide support for ES6 and async and await, we compile those. Since V8 doesn’t support class decorators, we don’t compile those.</p> - - <p>See the documentation about <a href="/docs/advanced-features/customizing-babel-config.md">customizing babel config</a> for more information.</p> + <summary>How do I fetch data in Next.js?</summary> + Next.js provides a variety of methods depending on your use case. You can use: + <ul> + <li> Client-side rendering: Fetch data with <a href="/docs/basic-features/data-fetching/client-side.md#client-side-data-fetching-with-useeffect">useEffect</a> or <a href="/docs/basic-features/data-fetching/client-side.md#client-side-data-fetching-with-swr">SWR</a> inside your React components</li> + <li> Server-side rendering with <a href="/docs/basic-features/data-fetching/get-server-side-props.md">getServerSideProps</a></li> + <li> Static-site generation with <a href="/docs/basic-features/data-fetching/get-static-props.md">getStaticProps</a></li> + <li> Incremental Static Regeneration by <a href="/docs/basic-features/data-fetching/incremental-static-regeneration.md">adding the `revalidate` prop to getStaticProps</a></li> + </ul> + To learn more about data fetching, visit our <a href="/docs/basic-features/data-fetching/overview.md">data fetching documentation</a>. </details> <details> - <summary>Why a new Router?</summary> - Next.js is special in that: + <summary>Why does Next.js have its own Router?</summary> + Next.js includes a built-in router for a few reasons: <ul> - <li>Routes don’t need to be known ahead of time, We don't ship a route manifest</li> + <li>It uses a file-system based router which reduces configuration</li> + <li>It supports shallow routing which allows you to change the URL without running data fetching methods</li> <li>Routes are always lazy-loadable</li> </ul> + If you're migrating from React Router, see the <a href="/docs/migrating/from-react-router.md">migration documentation</a>. </details> <details> - <summary>How do I fetch data?</summary> - <p>It's up to you. You can use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch">fetch API</a> or <a href="https://swr.vercel.app/">SWR</a> inside your React components for remote data fetching; or use our <a href="/docs/basic-features/data-fetching/overview.md">data fetching methods</a> for initial data population.</p> + <summary>Can I use Next.js with my favorite JavaScript library?</summary> + <p>Yes! We have hundreds of examples in our <a href="https://github.com/vercel/next.js/tree/canary/examples">examples directory</a>.</p> </details> <details> - <summary>Can I use it with GraphQL?</summary> - <p>Yes! Here's an <a href="https://github.com/vercel/next.js/tree/canary/examples/with-apollo">example with Apollo</a>.</p> + <summary>Can I use Next.js with GraphQL?</summary> + <p>Yes! Here's an <a href="https://github.com/vercel/next.js/tree/canary/examples/with-apollo">example with Apollo</a> and an <a href="https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql">example API Route with GraphQL</a>.</p> </details> <details> - <summary>Can I use it with Redux?</summary> - <p>Yes! Here's an <a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux">example</a>. And there's another <a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk">example with thunk</a>.</p> + <summary>Can I use Next.js with Redux?</summary> + <p>Yes! Here's an <a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux">example with Redux</a> and an <a href="https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk">example with thunk</a>.</p> +</details> + +<details> + <summary>Can I make a Next.js Progressive Web App (PWA)?</summary> + <p>Yes! Here's our <a href="https://github.com/vercel/next.js/tree/canary/examples/progressive-web-app">Next.js PWA Example</a>.</p> </details> <details> <summary>Can I use a CDN for static assets?</summary> - <p>Yes. You can read more about it <a href="/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md">here</a>.</p> + <p>Yes! When you deploy your Next.js application to <a href="https://vercel.com">Vercel</a>, your static assets are automatically detected and served by the Edge Network. If you self-host Next.js, you can learn how to manually configure the asset prefix <a href="/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md">here</a>.</p> </details> <details> - <summary>Can I use Next with my favorite JavaScript library or toolkit?</summary> - <p>Since our first release we've had many example contributions. You can check them out in the <a href="https://github.com/vercel/next.js/tree/canary/examples">examples</a> directory.</p> + <summary>How can I change the internal webpack config?</summary> + <p>In most cases, no manual webpack configuration is necessary since Next.js automatically configures webpack. For advanced cases where more control is needed, refer to the <a href="/docs/api-reference/next.config.js/custom-webpack-config.md">custom webpack config documentation</a>.</p> </details> <details> - <summary>What is this inspired by?</summary> + <summary>What is Next.js inspired by?</summary> <p>Many of the goals we set out to accomplish were the ones listed in The <a href="https://rauchg.com/2014/7-principles-of-rich-web-applications">7 principles of Rich Web Applications</a> by Guillermo Rauch.</p> <p>The ease-of-use of PHP is a great inspiration. We feel Next.js is a suitable replacement for many scenarios where you would otherwise use PHP to output HTML.</p> @@ -72,8 +73,3 @@ description: Get to know more about Next.js with the frequently asked questions. <p>As we were researching options for server-rendering React that didn’t involve a large number of steps, we came across <a href="https://github.com/facebookarchive/react-page">react-page</a> (now deprecated), a similar approach to Next.js by the creator of React Jordan Walke.</p> </details> - -<details> - <summary>Can I make a Next.js Progressive Web App (PWA)?</summary> - <p>Yes! Check out our <a href="https://github.com/vercel/next.js/tree/canary/examples/progressive-web-app">PWA Example</a> to see how it works.</p> -</details> diff --git a/docs/getting-started.md b/docs/getting-started.md index b01d60fd90927..8506f02b9d493 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,7 +6,7 @@ description: Get started with Next.js in the official documentation, and learn m Welcome to the Next.js documentation! -If you're new to Next.js we recommend that you start with the [learn course](https://nextjs.org/learn/basics/create-nextjs-app). +If you're new to Next.js, we recommend starting with the [learn course](https://nextjs.org/learn/basics/create-nextjs-app). The interactive course with quizzes will guide you through everything you need to know to use Next.js. @@ -17,7 +17,7 @@ If you have questions about anything related to Next.js, you're always welcome t - [Node.js 12.22.0](https://nodejs.org/) or later - MacOS, Windows (including WSL), and Linux are supported -## Setup +## Automatic Setup We recommend creating a new Next.js app using `create-next-app`, which sets up everything automatically for you. To create a project, run: @@ -37,11 +37,11 @@ yarn create next-app --typescript After the installation is complete: -- Run `npm run dev` or `yarn dev` to start the development server on `http://localhost:3000`. -- Visit `http://localhost:3000` to view your application. -- Edit `pages/index.js` and see the updated result in your browser. +- Run `npm run dev` or `yarn dev` to start the development server on `http://localhost:3000` +- Visit `http://localhost:3000` to view your application +- Edit `pages/index.js` and see the updated result in your browser -For more information on how to use `create-next-app`, you can review the [`create-next-app` documentation](/docs/api-reference/create-next-app.md) +For more information on how to use `create-next-app`, you can review the [`create-next-app` documentation](/docs/api-reference/create-next-app.md). ## Manual Setup @@ -89,12 +89,12 @@ export default HomePage So far, we get: -- Automatic compilation and bundling (with webpack and babel) +- Automatic compilation and [bundling](/docs/advanced-features/compiler.md) - [React Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh) - [Static generation and server-side rendering](/docs/basic-features/data-fetching/overview.md) of [`./pages/`](/docs/basic-features/pages.md) - [Static file serving](/docs/basic-features/static-file-serving.md). `./public/` is mapped to `/` -In addition, any Next.js application is ready for production from the start, read more in our [Deployment documentation](/docs/deployment.md). +In addition, any Next.js application is ready for production from the start. Read more in our [Deployment documentation](/docs/deployment.md). ## Related @@ -110,7 +110,7 @@ For more information on what to do next, we recommend the following sections: <div class="card"> <a href="/docs/basic-features/built-in-css-support.md"> <b>CSS Support:</b> - <small>Use the built-in CSS support to add custom styles to your app.</small> + <small>Built-in CSS support to add custom styles to your app.</small> </a> </div> diff --git a/docs/going-to-production.md b/docs/going-to-production.md index aa713bf04577c..f65f055d06776 100644 --- a/docs/going-to-production.md +++ b/docs/going-to-production.md @@ -22,6 +22,7 @@ Before taking your Next.js application to production, here are some recommendati - [`next/image` and Automatic Image Optimization](/docs/basic-features/image-optimization.md) - [Automatic Font Optimization](/docs/basic-features/font-optimization.md) - [Script Optimization](/docs/basic-features/script.md) +- Improve [loading performance](#loading-performance) ## Caching @@ -117,6 +118,26 @@ When an unhandled exception occurs, you can control the experience for your user You can also log and track exceptions with a tool like Sentry. [This example](https://github.com/vercel/next.js/tree/canary/examples/with-sentry) shows how to catch & report errors on both the client and server-side, using the Sentry SDK for Next.js. There's also a [Sentry integration for Vercel](https://vercel.com/integrations/sentry). +## Loading Performance + +To improve loading performance, you first need to determine what to measure and how to measure it. [Core Web Vitals](https://vercel.com/blog/core-web-vitals) is a good industry standard that is measured using your own web browser. If you are not familiar with the metrics of Core Web Vitals, review this [blog post](https://vercel.com/blog/core-web-vitals) and determine which specific metric/s will be your drivers for loading performance. Ideally, you would want to measure the loading performance in the following environments: + +- In the lab, using your own computer or a simulator. +- In the field, using real-world data from actual visitors. +- Local, using a test that runs on your device. +- Remote, using a test that runs in the cloud. + +Once you are able to measure the loading performance, use the following strategies to improve it iteratively so that you apply one strategy, measure the new performance and continue tweaking until you do not see much improvement. Then, you can move on to the next strategy. + +- Use caching regions that are close to the regions where your database or API is deployed. +- As described in the [caching](#caching) section, use a `stale-while-revalidate` value that will not overload your backend. +- Use [Incremental Static Regeneration](/docs/basic-features/data-fetching#incremental-static-regeneration) to reduce the number of requests to your backend. +- Remove unused JavaScript. Review this [blog post](https://calibreapp.com/blog/bundle-size-optimization) to understand what Core Web Vitals metrics bundle size affects and what strategies you can use to reduce it, such as: + - Setting up your Code Editor to view import costs and sizes + - Finding alternative smaller packages + - Dynamically loading components and dependencies + - For more in depth information, review this [guide](https://papyrus.dev/@PapyrusBlog/how-we-reduced-next.js-page-size-by-3.5x-and-achieved-a-98-lighthouse-score) and this [performance checklist](https://dev.to/endymion1818/nextjs-performance-checklist-5gjb). + ## Related For more information on what to do next, we recommend the following sections: diff --git a/docs/guides/building-forms.md b/docs/guides/building-forms.md new file mode 100644 index 0000000000000..1a9ac3791a6cc --- /dev/null +++ b/docs/guides/building-forms.md @@ -0,0 +1,374 @@ +--- +title: Building Forms with Next.js +description: Learn how to create forms with Next.js, from the form HTML element to advanced concepts with React. +--- + +# Building Forms with Next.js + +A web form has a **client-server** relationship. They are used to send data handled by a web server for processing and storage. The form itself is the client, and the server is any storage mechanism that can be used to store, retrieve and send data when needed. + +This guide will teach you how to create a web form with Next.js. + +## Part 1: HTML Form + +HTML forms are built using the `<form>` tag. It takes a set of attributes and fields to structure the form for features like text fields, checkboxes, dropdown menus, buttons, radio buttons, etc. + +Here's the syntax of a HTML form: + +```html +<!-- Basic HTML Form --> +<form action="/send-data-here" method="post"> + <label for="first">First name:</label> + <input type="text" id="first" name="first" /> + <label for="last">Last name:</label> + <input type="text" id="last" name="last" /> + <button type="submit">Submit</button> +</form> +``` + +The front-end looks like this: + +![html forms](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/html-forms.png) + +The HTML `<form>` tag acts as a container for different `<input>` elements like `text` field and submit `button`. Let's study each of these elements: + +- `action`: An attribute that specifies where the form data is sent when the form is submitted. It's generally a URL (an absolute URL or a relative URL). +- `method`: Specifies the [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), i.e., `GET` or `POST` used to send data while submitting the form. +- `<label>`: An element that defines the label for other form elements. Labels aid accessibility, especially for screen readers. +- `<input>`: The form element that is widely used to structure the form fields. It depends significantly on the value of the `type` attribute. Input types can be `text`, `checkbox`, `email`, `radio`, and more. +- `<button>`: Represents a clickable button that's used to submit the form data. + +### Form Validation + +A process that checks if the information provided by a user is correct or not. Form validation also ensures that the provided information is in the correct format (e.g. there's an @ in the email field). These are of two types: + +- **Client-side**: Validation is done in the browser +- **Server-side**: Validation is done on the server + +Though both of these types are equally important, this guide will focus on client-side validation only. + +Client-side validation is further categorized as: + +- **Built-in**: Uses HTML-based attributes like `required`, `type`, `minLength`, `maxLength`, `pattern`, etc. +- **JavaScript-based**: Validation that's coded with JavaScript. + +### Built-in Form Validation Using `required`, `type`, `minLength`, `maxLength` + +- `required`: Specifies which fields must be filled before submitting the form. +- `type`: Specifies the data's type (i.e a number, email address, string, etc). +- `minLength`: Specifies minimum length for the text data string. +- `maxLength`: Specifies maximum length for the text data string. + +So, a form using this attributes may look like: + +```html +<!-- HTML Form with Built-in Validation --> +<form action="/send-data-here" method="post"> + <label for="roll">Roll Number</label> + <input + type="text" + id="roll" + name="roll" + required + minlength="10" + maxlength="20" + /> + <label for="name">Name:</label> + <input type="text" id="name" name="name" required /> + <button type="submit">Submit</button> +</form> +``` + +With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, a roll number can only be entered if it's 10-20 characters long. + +![form validation](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/form-validation.jpg) + +### JavaScript-based Form Validation + +Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. + +The following example shows using JavaScript to validate a form: + +```html +<form onsubmit="validateFormWithJS()"> + <label for="rollNumber">Roll Number:</label> + <input type="text" name="rollNumber" id="rollNumber" /> + + <label for="name">Name:</label> + <input type="text" name="name" id="name" /> + + <button type="submit">Submit</button> +</form> + +<script> + function validateFormWithJS() { + const name = document.querySelector('#name').value + const rollNumber = document.querySelector('#rollNumber').value + + if (!name) { + alert('Please enter your name.') + return false + } + + if (rollNumber.length < 3) { + alert('Roll Number should be at least 3 digits long.') + return false + } + } +</script> +``` + +The HTML [script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) tag is used to embed any client-side JavaScript. It can either contain inline scripting statements (as shown in the example above) or point to an external script file via the `src` attribute. +This example validates the name and roll number of a user. The `validateFormWithJS()` function does not allow an empty name field, and the roll number must be at least three digits long. The validation is performed when you hit the Submit button. You are not redirected to the next page until the given values are correct. + +![js-validation](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/js-validation.jpg) + +#### Form Validation Using Regular Expressions + +JavaScript validation with Regular Expressions uses the `pattern` HTML attribute. A regular expression (commonly known as RegEx) is an object that describes a pattern of characters. You can only apply the `pattern` attribute to the `<input>` element. This way, you can validate the input value using Regular Expressions (RegEx) by defining your own rules. Once again, if the value does not match the defined pattern, the input will give an error. +The below example shows using the `pattern` attribute on an `input` element: + +```html +<form action="/action_page.php"> + <label for="pswrd">Password:</label> + <input + type="password" + id="pswrd" + name="pswrd" + pattern="[a-z]{0,9}" + title="Password should be digits (0 to 9) or alphabets (a to z)." + /> + + <button type="submit">Submit</button> +</form> +``` + +The password form field must only contain digits (0 to 9), lowercase alphabets (a to z) and it must be no more than 15 characters in length. No other characters (#,$,&, etc.) are allowed. The rule in RegEx is written as `[a-z0-9]{1,15}`. + +![form-validate-regex](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/form-validate-regex.jpg) + +> To learn more about HTML forms, check out the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Learn/Forms). + +## Part 2: Project Setup + +In the following section you will be creating forms in React using Next.js. + +Create a new Next.js app. You can use the [create-next-app](https://nextjs.org/docs/getting-started#setup) for a quick start. In your command line terminal, run the following: + +``` +npx create-next-app +``` + +Answer the questions to create your project, and give it a name, this example uses [`next-forms`](https://github.com/vercel/next.js/tree/canary/examples/next-forms). Next `cd` into this directory, and run `npm run dev` or `yarn dev` command to start the development server. + +Open the URL printed in the terminal to ensure that your app is running successfully. + +## Part 3: Setting up a Next.js Form API Route + +Both the client and the server will be built using Next.js. For the server part, create an API endpoint where you will send the form data. + +Next.js offers a file-based system for routing that's built on the [concept of pages](/docs/basic-features/pages). Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated as an API endpoint instead of a page. This [API endpoint](/docs/api-routes/introduction) is going to be server-side only. + +Go to `pages/api`, create a file called `form.js` and paste this code written in Node.js: + +```js +export default function handler(req, res) { + // Get data submitted in request's body. + const body = req.body + + // Optional logging to see the responses + // in the command line where next.js app is running. + console.log('body: ', body) + + // Guard clause checks for first and last name, + // and returns early if they are not found + if (!body.first || !body.last) { + // Sends a HTTP bad request error code + return res.status(400).json({ data: 'First or last name not found' }) + } + + // Found the name. + // Sends a HTTP success code + res.status(200).json({ data: `${body.first} ${body.last}` }) +} +``` + +This form `handler` function will receive the request `req` from the client (i.e. submitted form data). And in return, it'll send a response `res` as JSON that will have both the first and the last name. You can access this API endpoint at `http://localhost:3000/api/form` or replace the localhost URL with an actual Vercel deployment when you deploy. + +> Moreover, you can also attach this API to a database like MongoDB or Google Sheets. This way, your submitted form data will be securely stored for later use. For this guide, no database is used. Instead, the same data is returned to the user to demo how it's done. + +### Form Submission without JavaScript + +You can now use `/api/form` relative endpoint inside the `action` attribute of the form. You are sending form data to the server when the form is submitted via `POST` HTTP method (which is used to send data). + +```html +<form action="/api/form" method="post"> + <label for="first">First name:</label> + <input type="text" id="first" name="first" /> + <label for="last">Last name:</label> + <input type="text" id="last" name="last" /> + <button type="submit">Submit</button> +</form> +``` + +If you submit this form, it will submit the data to the forms API endpoint `/api/form`. The server then responds, generally handling the data and loading the URL defined by the action attribute, causing a new page load. So in this case you'll be redirected to `http://localhost:3000/api/form` with the following response from the server. + +![form-no-js](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/form-no-js.jpg) + +## Part 4: Configuring Forms in Next.js + +You have created a Next.js API Route for form submission. Now it's time to configure the client (the form itself) inside Next.js using React. The first step will be extending your knowledge of HTML forms and converting it into React (using [JSX](https://reactjs.org/docs/introducing-jsx.html)). + +Here's the same form in a [React function component](https://reactjs.org/docs/components-and-props.html) written using [JSX](https://reactjs.org/docs/introducing-jsx.html). + +```js +export default function Form() { + return ( + <form action="/api/form" method="post"> + <label htmlFor="first">First Name</label> + <input type="text" id="first" name="first" required /> + + <label htmlFor="last">Last Name</label> + <input type="text" id="last" name="last" required /> + + <button type="submit">Submit</button> + </form> + ) +} +``` + +Here's what changed: + +- The `for` attribute is changed to `htmlFor`. (Since `for` is a keyword associated with the "for" loop in JavaScript, React elements use `htmlFor` instead.) +- The `action` attribute now has a relative URL which is the form API endpoint. + +This completes the basic structure of your Next.js-based form. + +> You can view the entire source code of [next-forms](https://github.com/vercel/next.js/tree/canary/examples/next-forms) example repo that we're creating here as a working example. Feel free to clone it and start right away. This demo is built with create-next-app, and you can preview the basic form CSS styles inside `/styles/global.css` file. + +![forms with nextjs](https://assets.vercel.com/image/upload/dpr_auto,q_auto,f_auto/nextjs/guides/building-forms/forms-with-nextjs.png) + +## Part 5: Form Submission without JavaScript + +JavaScript brings interactivity to our web applications, but sometimes you need to control the JavaScript bundle from being too large, or your sites visitors might have JavaScript disabled. + +There are several reasons why users disable JavaScript: + +- Addressing bandwidth constraints +- Increasing device (phone or laptop) battery life +- For privacy so they won’t be tracked with analytical scripts + +Regardless of the reason, disabling JavaScript will impact site functionality partially, if not completely. + +Next open the `next-forms` directory. Inside the `/pages` directory, create a file `no-js-form.js`. + +> **Quick Tip**: In Next.js, a page is a React Component exported from a `.js`, `.jsx`, `.ts`, or `.tsx` file in the pages directory. Each page is associated with a route based on its file name. +> +> Example: If you create `pages/no-js-form.js`, it will be accessible at `your-domain.tld/no-js-form`. + +Let's use the same code from above: + +```js +export default function PageWithoutJSbasedForm() { + return ( + <form action="/api/form" method="post"> + <label htmlFor="first">First Name</label> + <input type="text" id="first" name="first" required /> + + <label htmlFor="last">Last Name</label> + <input type="text" id="last" name="last" required /> + + <button type="submit">Submit</button> + </form> + ) +} +``` + +With JavaScript disabled, when you hit the Submit button, an event is triggered, which collects the form data and sends it to our forms API endpoint as defined in the `action` attribute and using `POST` HTTP `method`. You'll be redirected to the `/api/form` endpoint since that's how form `action` works. + +The form data will be submitted on the server as a request `req` to the form handler function written above. It will process the data and return a JSON string as a response `res` with your submitted name included. + +> To improve the experience here, as a response you can redirect the user to a page and thank them for submitting the form. + +## Part 6: Form Submission with JavaScript Enabled + +Inside `/pages`, you'll create another file called `js-form.js`. This will create a `/js-form` page on your Next.js app. + +Now, as soon as the form is submitted, we prevent the form's default behavior of reloading the page. We'll take the form data, convert it to JSON string, and send it to our server, the API endpoint. Finally, our server will respond with the name submitted. All of this with a basic JavaScript `handleSubmit()` function. + +Here's what this function looks like. It's well documented for you to understand each step: + +```js +export default function PageWithJSbasedForm() { + // Handles the submit event on form submit. + const handleSubmit = async (event) => { + // Stop the form from submitting and refreshing the page. + event.preventDefault() + + // Get data from the form. + const data = { + first: event.target.first.value, + last: event.target.last.value, + } + + // Send the data to the server in JSON format. + const JSONdata = JSON.stringify(data) + + // API endpoint where we send form data. + const endpoint = '/api/form' + + // Form the request for sending data to the server. + const options = { + // The method is POST because we are sending data. + method: 'POST', + // Tell the server we're sending JSON. + headers: { + 'Content-Type': 'application/json', + }, + // Body of the request is the JSON data we created above. + body: JSONdata, + } + + // Send the form data to our forms API on Vercel and get a response. + const response = await fetch(endpoint, options) + + // Get the response data from server as JSON. + // If server returns the name submitted, that means the form works. + const result = await response.json() + alert(`Is this your full name: ${result.data}`) + } + return ( + // We pass the event to the handleSubmit() function on submit. + <form onSubmit={handleSubmit}> + <label htmlFor="first">First Name</label> + <input type="text" id="first" name="first" required /> + + <label htmlFor="last">Last Name</label> + <input type="text" id="last" name="last" required /> + + <button type="submit">Submit</button> + </form> + ) +} +``` + +It's a Next.js page with a React function component called `PageWithJSbasedForm` with a `<form>` element written in JSX. There's no action on the `<form>` element. Instead, we use the `onSubmit` event handler to send data to our `{handleSubmit}` function. + +The `handleSubmit()` function processes your form data through a series of steps: + +- The `event.preventDefault()` stops the `<form>` element from refreshing the entire page. +- We created a JavaScript object called `data` with the `first` and `last` values from the form. +- JSON is a language-agnostic data transfer format. So we use `JSON.stringify(data)` to convert the data to JSON. +- We then use `fetch()` to send the data to our `/api/form` endpoint using JSON and HTTP `POST` method. +- Server sends back a response with the name submitted. Woohoo! 🥳 + +## Conclusion + +This guide has covered the following: + +- The basic HTML `form` element +- Understanding forms with React.js +- Validating forms data with and without JavaScript +- Using Next.js API Routes to handle `req` and `res` from the client and server + +For more details go through [Next.js Learn Course](https://nextjs.org/learn/basics/create-nextjs-app). diff --git a/docs/manifest.json b/docs/manifest.json index 8c54700ef3054..d2f8c5cca5921 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -22,6 +22,12 @@ "destination": "/docs/basic-features/data-fetching/overview" } }, + { + "path": "/docs/basic-features/data-fetching/index", + "redirect": { + "destination": "/docs/basic-features/data-fetching/overview" + } + }, { "title": "Overview", "path": "/docs/basic-features/data-fetching/overview.md" @@ -156,6 +162,15 @@ "title": "Testing", "path": "/docs/testing.md" }, + { + "title": "Guides", + "routes": [ + { + "title": "Building Forms", + "path": "/docs/guides/building-forms.md" + } + ] + }, { "title": "Advanced Features", "routes": [ @@ -252,6 +267,10 @@ "title": "Debugging", "path": "/docs/advanced-features/debugging.md" }, + { + "title": "Error Handling", + "path": "/docs/advanced-features/error-handling.md" + }, { "title": "Source Maps", "path": "/docs/advanced-features/source-maps.md" @@ -274,7 +293,26 @@ }, { "title": "React 18", - "path": "/docs/advanced-features/react-18.md" + "routes": [ + { + "path": "/docs/advanced-features/react-18", + "redirect": { + "destination": "/docs/advanced-features/react-18/overview" + } + }, + { + "title": "Overview", + "path": "/docs/advanced-features/react-18/overview.md" + }, + { + "title": "Streaming SSR", + "path": "/docs/advanced-features/react-18/streaming.md" + }, + { + "title": "React Server Components", + "path": "/docs/advanced-features/react-18/server-components.md" + } + ] } ] }, @@ -343,6 +381,10 @@ "title": "next/server", "path": "/docs/api-reference/next/server.md" }, + { + "title": "next/streaming", + "path": "/docs/api-reference/next/streaming.md" + }, { "title": "Edge Runtime", "path": "/docs/api-reference/edge-runtime.md" diff --git a/docs/middleware.md b/docs/middleware.md index 327f61179675e..d204f99a6c4da 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -4,6 +4,16 @@ description: Learn how to use Middleware in Next.js to run code before a request # Middleware +<details open> + <summary><b>Version History</b></summary> + +| Version | Changes | +| --------- | ------------------------------------------------------------------------------------------ | +| `v12.0.9` | Enforce absolute URLs in Edge Runtime ([PR](https://github.com/vercel/next.js/pull/33410)) | +| `v12.0.0` | Middleware (Beta) added. | + +</details> + Middleware enables you to use code over configuration. This gives you full flexibility in Next.js, because you can run code before a request is completed. Based on the user's incoming request, you can modify the response by rewriting, redirecting, adding headers, or even streaming HTML. ## Usage @@ -30,6 +40,28 @@ export function middleware(req: NextRequest, ev: NextFetchEvent) { In this example, we use the standard Web API Response ([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response)). +## API + +Middleware is created by using a `middleware` function that lives inside a `_middleware` file. Its API is based upon the native [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) objects. + +These native Web API objects are extended to give you more control over how you manipulate and configure a response, based on the incoming requests. + +The function signature: + +```ts +import type { NextFetchEvent } from 'next/server' +import type { NextRequest } from 'next/server' + +export type Middleware = ( + request: NextRequest, + event: NextFetchEvent +) => Promise<Response | undefined> | Response | undefined +``` + +The function can be a default export and as such, does **not** have to be named `middleware`. Though this is a convention. Also note that you only need to make the function `async` if you are running asynchronous code. + +[Read the full Middleware API reference.](/docs/api-reference/next/server.md) + ## Examples Middleware can be used for anything that shares logic for a set of pages, including: diff --git a/docs/migrating/from-gatsby.md b/docs/migrating/from-gatsby.md index 36d27b38a8b1e..d8388b283c75f 100644 --- a/docs/migrating/from-gatsby.md +++ b/docs/migrating/from-gatsby.md @@ -217,7 +217,7 @@ export default function Home() { ## Site Configuration -With Gatsby, your site's metadata (name, description, etc) is located inside `gatsby-config.js`. This is then exposed through the GraphQL API and consumed through a `pageQuery` or a static query inside a component. +With Gatsby, your site's metadata (name, description, etc.) is located inside `gatsby-config.js`. This is then exposed through the GraphQL API and consumed through a `pageQuery` or a static query inside a component. With Next.js, we recommend creating a config file similar to below. You can then import this file anywhere without having to use GraphQL to access your site's metadata. diff --git a/docs/testing.md b/docs/testing.md index 6b0bc97e2699b..2b3c6f692bca0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -232,7 +232,7 @@ Run `npm run build` and `npm run start`, then run `npm run test:e2e` in another ### Running Playwright on Continuous Integration (CI) -Playwright will by default run your tests in the [headed mode](https://playwright.dev/docs/ci). To install all the Playwright dependencies, run `npx playwright install-deps`. +Playwright will by default run your tests in the [headless mode]https://playwright.dev/docs/ci#running-headed). To install all the Playwright dependencies, run `npx playwright install-deps`. You can learn more about Playwright and Continuous Integration from these resources: @@ -325,7 +325,7 @@ module.exports = { // Handle image imports // https://jestjs.io/docs/webpack#handling-static-assets - '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `<rootDir>/__mocks__/fileMock.js`, + '^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`, // Handle module aliases '^@/components/(.*)$': '<rootDir>/components/$1', @@ -354,7 +354,12 @@ Stylesheets and images aren't used in the tests but importing them may cause err ```js // __mocks__/fileMock.js -module.exports = 'test-file-stub' +module.exports = { + src: '/img.jpg', + height: 24, + width: 24, + blurDataURL: 'data:image/png;base64,imagedata', +} ``` ```js @@ -362,13 +367,6 @@ module.exports = 'test-file-stub' module.exports = {} ``` -If you're running into the issue `"Failed to parse src "test-file-stub" on 'next/image'"`, add a '/' to your fileMock. - -```js -// __mocks__/fileMock.js -module.exports = '/test-file-stub' -``` - For more information on handling static assets, please refer to the [Jest Docs](https://jestjs.io/docs/webpack#handling-static-assets). **Optional: Extend Jest with custom matchers** diff --git a/errors/api-routes-body-size-limit.md b/errors/api-routes-response-size-limit.md similarity index 93% rename from errors/api-routes-body-size-limit.md rename to errors/api-routes-response-size-limit.md index 5697768c4a089..2b557cc852ac2 100644 --- a/errors/api-routes-body-size-limit.md +++ b/errors/api-routes-response-size-limit.md @@ -1,4 +1,4 @@ -# API Routes Body Size Limited to 4MB +# API Routes Response Size Limited to 4MB #### Why This Error Occurred diff --git a/errors/client-flush-effects.md b/errors/client-flush-effects.md new file mode 100644 index 0000000000000..64f107b499903 --- /dev/null +++ b/errors/client-flush-effects.md @@ -0,0 +1,24 @@ +# `useFlushEffects` can not be called on the client + +#### Why This Error Occurred + +The `useFlushEffects` hook was executed while rendering a component on the client, or in another unsupported environment. + +#### Possible Ways to Fix It + +The `useFlushEffects` hook can only be called while _server rendering a client component_. As a best practice, we recommend creating a wrapper hook: + +```jsx +// lib/use-style-libraries.js +import { useFlushEffects } from 'next/streaming' + +export default function useStyleLibraries() { + if (typeof window === 'undefined') { + // eslint-disable-next-line react-hooks/rules-of-hooks + useFlushEffects([ + /* ... */ + ]) + } + /* ... */ +} +``` diff --git a/errors/css-npm.md b/errors/css-npm.md index 3e4e81f834a79..2a2a89d395a1a 100644 --- a/errors/css-npm.md +++ b/errors/css-npm.md @@ -24,4 +24,4 @@ know the correct behavior: - Should the file be consumed as Global CSS or CSS Modules? - If Global, in what order does the file need to be injected? - If Modules, what are the emitted class names? As-is, camel-case, snake case? -- Etc... +- Etc. diff --git a/errors/deleting-query-params-in-middlewares.md b/errors/deleting-query-params-in-middlewares.md index 676c5d903a3de..847efbb0bddc4 100644 --- a/errors/deleting-query-params-in-middlewares.md +++ b/errors/deleting-query-params-in-middlewares.md @@ -28,7 +28,7 @@ import { NextResponse } from 'next/server' export default function middleware(request: NextRequest) { const nextUrl = request.nextUrl nextUrl.pathname = '/dest' - return NextResponse.rewrite(url) + return NextResponse.rewrite(nextUrl) } ``` diff --git a/errors/failed-loading-swc.md b/errors/failed-loading-swc.md index 8dad371dd4cb4..f1cc951c52fe8 100644 --- a/errors/failed-loading-swc.md +++ b/errors/failed-loading-swc.md @@ -8,7 +8,9 @@ SWC requires a binary be downloaded that is compatible specific to your system. #### Possible Ways to Fix It -You might need to allow optional packages to be installed by your package manager (remove `--no-optional` flag) for the package to download correctly. +When on an M1 Mac and switching from a Node.js version without M1 support e.g. v14 to a version with e.g. v16, you may need a different swc dependency which can require re-installing `node_modules` (`npm i --force` or `yarn install --force`). + +Alternatively, you might need to allow optional packages to be installed by your package manager (remove `--no-optional` flag) for the package to download correctly. If SWC continues to fail to load you can opt-out by disabling `swcMinify` in your `next.config.js` or by adding a `.babelrc` to your project with the following content: diff --git a/errors/google-font-preconnect.md b/errors/google-font-preconnect.md index de629ac4e1bf3..d95d72dba9bdb 100644 --- a/errors/google-font-preconnect.md +++ b/errors/google-font-preconnect.md @@ -12,6 +12,9 @@ Add `rel="preconnect"` to the Google Font domain `<link>` tag: <link rel="preconnect" href="https://fonts.gstatic.com" /> ``` +Note: a **separate** link with `dns-prefetch` can be used as a fallback for browsers that don't support `preconnect` although this is not required. + ### Useful Links - [Preconnect to required origins](https://web.dev/uses-rel-preconnect/) +- [Preconnect and dns-prefetch](https://web.dev/preconnect-and-dns-prefetch/#resolve-domain-name-early-with-reldns-prefetch) diff --git a/errors/ignored-compiler-options.md b/errors/ignored-compiler-options.md new file mode 100644 index 0000000000000..edab33451d6ec --- /dev/null +++ b/errors/ignored-compiler-options.md @@ -0,0 +1,9 @@ +# ignored-compiler-options + +#### Why This Error Occurred + +Options under the `compiler` key in `next.config.js` only apply to the new Rust based compiler and will be ignored if Babel is used instead. This message will appear if Next.js detects a Babel configuration file while `compiler` options are configured in `next.config.js` + +### Useful Links + +- [Next.js Compiler](https://nextjs.org/docs/advanced-features/compiler) diff --git a/errors/invalid-api-status-body.md b/errors/invalid-api-status-body.md index b0d5fd60fd99d..45f86c0b3684e 100644 --- a/errors/invalid-api-status-body.md +++ b/errors/invalid-api-status-body.md @@ -1,4 +1,4 @@ -Invalid API Route Status/Body Response +# Invalid API Route Status/Body Response #### Why This Error Occurred @@ -22,7 +22,7 @@ After ```js export default function handler(req, res) { - res.status(204).send() + res.status(204).end() } ``` diff --git a/errors/invalid-images-config.md b/errors/invalid-images-config.md index 5817244392221..b409f60568300 100644 --- a/errors/invalid-images-config.md +++ b/errors/invalid-images-config.md @@ -27,6 +27,10 @@ module.exports = { minimumCacheTTL: 60, // ordered list of acceptable optimized image formats (mime types) formats: ['image/webp'], + // enable dangerous use of SVG images + dangerouslyAllowSVG: false, + // set the Content-Security-Policy header + contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, } ``` diff --git a/errors/manifest.json b/errors/manifest.json index 930a16a147dbc..d80ef5787008c 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -32,14 +32,23 @@ "title": "404-get-initial-props", "path": "/errors/404-get-initial-props.md" }, - { "title": "amp-bind-jsx-alt", "path": "/errors/amp-bind-jsx-alt.md" }, + { + "title": "amp-bind-jsx-alt", + "path": "/errors/amp-bind-jsx-alt.md" + }, { "title": "amp-export-validation", "path": "/errors/amp-export-validation.md" }, { - "title": "api-routes-body-size-limit", - "path": "/errors/api-routes-body-size-limit.md" + "path": "/errors/api-routes-body-size-limit.md", + "redirect": { + "destination": "/docs/messages/api-routes-response-size-limit" + } + }, + { + "title": "api-routes-response-size-limit", + "path": "/errors/api-routes-response-size-limit.md" }, { "title": "api-routes-static-export", @@ -89,9 +98,18 @@ "title": "conflicting-ssg-paths", "path": "/errors/conflicting-ssg-paths.md" }, - { "title": "css-global", "path": "/errors/css-global.md" }, - { "title": "css-modules-npm", "path": "/errors/css-modules-npm.md" }, - { "title": "css-npm", "path": "/errors/css-npm.md" }, + { + "title": "css-global", + "path": "/errors/css-global.md" + }, + { + "title": "css-modules-npm", + "path": "/errors/css-modules-npm.md" + }, + { + "title": "css-npm", + "path": "/errors/css-npm.md" + }, { "title": "custom-error-no-custom-404", "path": "/errors/custom-error-no-custom-404.md" @@ -100,7 +118,10 @@ "title": "doc-crossorigin-deprecated", "path": "/errors/doc-crossorigin-deprecated.md" }, - { "title": "duplicate-sass", "path": "/errors/duplicate-sass.md" }, + { + "title": "duplicate-sass", + "path": "/errors/duplicate-sass.md" + }, { "title": "empty-configuration", "path": "/errors/empty-configuration.md" @@ -121,7 +142,10 @@ "title": "export-all-in-page", "path": "/errors/export-all-in-page.md" }, - { "title": "export-image-api", "path": "/errors/export-image-api.md" }, + { + "title": "export-image-api", + "path": "/errors/export-image-api.md" + }, { "title": "export-no-custom-routes", "path": "/errors/export-no-custom-routes.md" @@ -154,7 +178,10 @@ "title": "gssp-component-member", "path": "/errors/gssp-component-member.md" }, - { "title": "gssp-export", "path": "/errors/gssp-export.md" }, + { + "title": "gssp-export", + "path": "/errors/gssp-export.md" + }, { "title": "gssp-mixed-not-found-redirect", "path": "/errors/gssp-mixed-not-found-redirect.md" @@ -163,12 +190,18 @@ "title": "gssp-no-mutating-res", "path": "/errors/gssp-no-mutating-res.md" }, - { "title": "head-build-id", "path": "/errors/head-build-id.md" }, + { + "title": "head-build-id", + "path": "/errors/head-build-id.md" + }, { "title": "href-interpolation-failed", "path": "/errors/href-interpolation-failed.md" }, - { "title": "improper-devtool", "path": "/errors/improper-devtool.md" }, + { + "title": "improper-devtool", + "path": "/errors/improper-devtool.md" + }, { "title": "incompatible-href-as", "path": "/errors/incompatible-href-as.md" @@ -177,8 +210,14 @@ "title": "inline-script-id", "path": "/errors/inline-script-id.md" }, - { "title": "install-sass", "path": "/errors/install-sass.md" }, - { "title": "install-sharp", "path": "/errors/install-sharp.md" }, + { + "title": "install-sass", + "path": "/errors/install-sass.md" + }, + { + "title": "install-sharp", + "path": "/errors/install-sharp.md" + }, { "title": "invalid-assetprefix", "path": "/errors/invalid-assetprefix.md" @@ -251,7 +290,10 @@ "title": "link-passhref", "path": "/errors/link-passhref.md" }, - { "title": "manifest.json", "path": "/errors/manifest.json" }, + { + "title": "manifest.json", + "path": "/errors/manifest.json" + }, { "title": "minification-disabled", "path": "/errors/minification-disabled.md" @@ -264,7 +306,10 @@ "title": "missing-env-value", "path": "/errors/missing-env-value.md" }, - { "title": "multi-tabs", "path": "/errors/multi-tabs.md" }, + { + "title": "multi-tabs", + "path": "/errors/multi-tabs.md" + }, { "title": "nested-reserved-page", "path": "/errors/nested-reserved-page.md" @@ -305,8 +350,14 @@ "title": "next-start-serverless", "path": "/errors/next-start-serverless.md" }, - { "title": "no-cache", "path": "/errors/no-cache.md" }, - { "title": "no-css-tags", "path": "/errors/no-css-tags.md" }, + { + "title": "no-cache", + "path": "/errors/no-cache.md" + }, + { + "title": "no-css-tags", + "path": "/errors/no-css-tags.md" + }, { "title": "no-document-import-in-page", "path": "/errors/no-document-import-in-page.md" @@ -379,17 +430,26 @@ "title": "popstate-state-empty", "path": "/errors/popstate-state-empty.md" }, - { "title": "postcss-function", "path": "/errors/postcss-function.md" }, + { + "title": "postcss-function", + "path": "/errors/postcss-function.md" + }, { "title": "postcss-ignored-plugin", "path": "/errors/postcss-ignored-plugin.md" }, - { "title": "postcss-shape", "path": "/errors/postcss-shape.md" }, + { + "title": "postcss-shape", + "path": "/errors/postcss-shape.md" + }, { "title": "prefetch-true-deprecated", "path": "/errors/prefetch-true-deprecated.md" }, - { "title": "prerender-error", "path": "/errors/prerender-error.md" }, + { + "title": "prerender-error", + "path": "/errors/prerender-error.md" + }, { "title": "production-start-no-build-id", "path": "/errors/production-start-no-build-id.md" @@ -402,7 +462,10 @@ "title": "public-next-folder-conflict", "path": "/errors/public-next-folder-conflict.md" }, - { "title": "react-version", "path": "/errors/react-version.md" }, + { + "title": "react-version", + "path": "/errors/react-version.md" + }, { "title": "render-no-starting-slash", "path": "/errors/render-no-starting-slash.md" @@ -427,13 +490,22 @@ "title": "static-dir-deprecated", "path": "/errors/static-dir-deprecated.md" }, - { "title": "threw-undefined", "path": "/errors/threw-undefined.md" }, + { + "title": "threw-undefined", + "path": "/errors/threw-undefined.md" + }, { "title": "undefined-webpack-config", "path": "/errors/undefined-webpack-config.md" }, - { "title": "url-deprecated", "path": "/errors/url-deprecated.md" }, - { "title": "webpack5", "path": "/errors/webpack5.md" }, + { + "title": "url-deprecated", + "path": "/errors/url-deprecated.md" + }, + { + "title": "webpack5", + "path": "/errors/webpack5.md" + }, { "title": "client-side-exception-occurred", "path": "/errors/client-side-exception-occurred.md" @@ -446,8 +518,14 @@ "title": "link-multiple-children", "path": "/errors/link-multiple-children.md" }, - { "title": "no-img-element", "path": "/errors/no-img-element.md" }, - { "title": "no-head-element", "path": "/errors/no-head-element.md" }, + { + "title": "no-img-element", + "path": "/errors/no-img-element.md" + }, + { + "title": "no-head-element", + "path": "/errors/no-head-element.md" + }, { "title": "non-dynamic-getstaticpaths-usage", "path": "/errors/non-dynamic-getstaticpaths-usage.md" @@ -481,8 +559,16 @@ "path": "/errors/no-script-in-document-page.md" }, { - "title": "script-in-head-component", - "path": "/errors/no-script-in-head-component.md" + "title": "script-component-in-head-component", + "path": "/errors/no-script-component-in-head-component.md" + }, + { + "title": "script-tags-in-head-component", + "path": "/errors/no-script-tags-in-head-component.md" + }, + { + "title": "stylesheets-in-head-component", + "path": "/errors/no-stylesheets-in-head-component.md" }, { "title": "max-custom-routes-reached", @@ -535,6 +621,22 @@ { "title": "deleting-query-params-in-middlewares", "path": "/errors/deleting-query-params-in-middlewares.md" + }, + { + "title": "ignored-compiler-options", + "path": "/errors/ignored-compiler-options.md" + }, + { + "title": "opening-an-issue", + "path": "/errors/opening-an-issue.md" + }, + { + "title": "multiple-flush-effects", + "path": "/errors/multiple-flush-effects.md" + }, + { + "title": "client-flush-effects", + "path": "/errors/client-flush-effects.md" } ] } diff --git a/errors/middleware-relative-urls.md b/errors/middleware-relative-urls.md index dabdf33060855..856bc94afaf3a 100644 --- a/errors/middleware-relative-urls.md +++ b/errors/middleware-relative-urls.md @@ -2,11 +2,11 @@ #### Why This Error Occurred -You are using a Middleware function that uses `Response.redirect(url)`, `NextResponse.redirect(url)` or `NextResponse.rewrite(url)` where `url` is a relative or an invalid URL. Currently this will work, but building a request with `new Request(url)` or running `fetch(url)` when `url` is a relative URL will **not** work. For this reason and to bring consistency to Next.js Middleware, this behavior will be deprecated soon in favor of always using absolute URLs. +You are using a Middleware function that uses `Response.redirect(url)`, `NextResponse.redirect(url)` or `NextResponse.rewrite(url)` where `url` is a relative or an invalid URL. Prior to Next.js 12.1, we allowed passing relative URLs. However, constructing a request with `new Request(url)` or running `fetch(url)` when `url` is a relative URL **does not** work. For this reason and to bring consistency to Next.js Middleware, this behavior has been deprecated and now removed. #### Possible Ways to Fix It -To fix this warning you must always pass absolute URL for redirecting and rewriting. There are several ways to get the absolute URL but the recommended way is to clone `NextURL` and mutate it: +To fix this error you must always pass absolute URL for redirecting and rewriting. There are several ways to get the absolute URL but the recommended way is to clone `NextURL` and mutate it: ```typescript import type { NextRequest } from 'next/server' diff --git a/errors/multiple-flush-effects.md b/errors/multiple-flush-effects.md new file mode 100644 index 0000000000000..51a9d7a58a0db --- /dev/null +++ b/errors/multiple-flush-effects.md @@ -0,0 +1,9 @@ +# The `useFlushEffects` hook cannot be used more than once. + +#### Why This Error Occurred + +The `useFlushEffects` hook is being used more than once while a page is being rendered. + +#### Possible Ways to Fix It + +The `useFlushEffects` hook should only be called inside the body of the `pages/_app` component, before returning any `<Suspense>` boundaries. Restructure your application to prevent extraneous calls. diff --git a/errors/next-script-for-ga.md b/errors/next-script-for-ga.md index 1d968090e1f15..0785fcca17708 100644 --- a/errors/next-script-for-ga.md +++ b/errors/next-script-for-ga.md @@ -13,7 +13,7 @@ If you are using the [gtag.js](https://developers.google.com/analytics/devguides ```jsx import Script from 'next/script' -const Home = () => { +function Home() { return ( <div class="container"> <!-- Global site tag (gtag.js) - Google Analytics --> @@ -44,7 +44,7 @@ If you are using the [analytics.js](https://developers.google.com/analytics/devg ```jsx import Script from 'next/script' -const Home = () => { +function Home() { return ( <div class="container"> <Script id="google-analytics" strategy="afterInteractive"> @@ -70,7 +70,7 @@ If you are using the [alternative async variant](https://developers.google.com/a ```jsx import Script from 'next/script' -const Home = () => { +function Home() { return ( <div class="container"> <Script id="google-analytics" strategy="afterInteractive"> diff --git a/errors/no-cache.md b/errors/no-cache.md index caea73084f3ab..b3b0214ca31ff 100644 --- a/errors/no-cache.md +++ b/errors/no-cache.md @@ -80,7 +80,10 @@ Using GitHub's [actions/cache](https://github.com/actions/cache), add the follow ```yaml uses: actions/cache@v2 with: - path: ${{ github.workspace }}/.next/cache + # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node + path: | + ~/.npm + ${{ github.workspace }}/.next/cache # Generate a new cache whenever packages or source files change. key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} # If source files changed but packages didn't, rebuild from a prior cache. diff --git a/errors/no-script-in-head-component.md b/errors/no-script-component-in-head-component.md similarity index 100% rename from errors/no-script-in-head-component.md rename to errors/no-script-component-in-head-component.md diff --git a/errors/no-script-tags-in-head-component.md b/errors/no-script-tags-in-head-component.md new file mode 100644 index 0000000000000..278ab907b8926 --- /dev/null +++ b/errors/no-script-tags-in-head-component.md @@ -0,0 +1,36 @@ +# No Script Tags In Head Component + +### Why This Error Occurred + +A `<script>` tag was added using the `next/head` component. + +We don't recommend this pattern because it will potentially break when used with Suspense and/or streaming. In these contexts, `next/head` tags aren't: + +- guaranteed to be included in the initial SSR response, so loading could be delayed until client-side rendering, regressing performance. + +- loaded in any particular order. The order that the app's Suspense boundaries resolve will determine the loading order of your scripts. + +### Possible Ways to Fix It + +#### Script component + +Use the Script component with the right loading strategy to defer loading of the script until necessary. You can see the possible strategies [here](https://nextjs.org/docs/basic-features/script/.) + +```jsx +import Script from 'next/script' + +const Home = () => { + return ( + <div class="container"> + <Script src="https://third-party-script.js"></Script> + <div>Home Page</div> + </div> + ) +} + +export default Home +``` + +### Useful Links + +- [Script component docs](https://nextjs.org/docs/basic-features/script/) diff --git a/errors/no-stylesheets-in-head-component.md b/errors/no-stylesheets-in-head-component.md new file mode 100644 index 0000000000000..319cae672bfeb --- /dev/null +++ b/errors/no-stylesheets-in-head-component.md @@ -0,0 +1,42 @@ +# No Stylesheets In Head Component + +### Why This Error Occurred + +A `<link rel="stylesheet">` tag was added using the `next/head` component. + +We don't recommend this pattern because it will potentially break when used with Suspense and/or streaming. In these contexts, `next/head` tags aren't: + +- guaranteed to be included in the initial SSR response, so loading could be delayed until client-side rendering, regressing performance. + +- loaded in any particular order. The order that the app's Suspense boundaries resolve will determine the loading order of your stylesheets. + +### Possible Ways to Fix It + +#### Document + +Add the stylesheet in a custom `Document` component. + +```jsx +// pages/_document.js +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + <Html> + <Head> + <link rel="stylesheet" href="..." /> + </Head> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ) +} +``` + +Note that the functional syntax for `Document` above is preferred over the `class` syntax, so that it will be compatible with React Server Components down the line. + +### Useful Links + +- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document) diff --git a/errors/no-sync-scripts.md b/errors/no-sync-scripts.md index 72e9275b6914b..b8a200e8b58d8 100644 --- a/errors/no-sync-scripts.md +++ b/errors/no-sync-scripts.md @@ -13,7 +13,7 @@ Use the Script component with the right loading strategy to defer loading of the ```jsx import Script from 'next/script' -const Home = () => { +function Home() { return ( <div class="container"> <Script src="https://third-party-script.js"></Script> diff --git a/errors/opening-an-issue.md b/errors/opening-an-issue.md new file mode 100644 index 0000000000000..34a08b391843a --- /dev/null +++ b/errors/opening-an-issue.md @@ -0,0 +1,28 @@ +# Opening a new Issue + +#### Why This Message Occurred + +When `next info` was run, Next.js detected that it's was not on the latest canary release. + +`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. + +Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue. + +Run the following in the codebase: + +```bash +npm install next@canary +``` + +or + +```bash +yarn add next@canary +``` + +And go through the prepared reproduction steps once again, and check if the issue still exists. + +### Useful Links + +- [Video: How to Contribute to Open Source (Next.js)](https://www.youtube.com/watch?v=cuoNzXFLitc) +- [Contributing to Next.js](https://github.com/vercel/next.js/blob/canary/contributing.md) diff --git a/errors/page-without-valid-component.md b/errors/page-without-valid-component.md index 09d94fee00c35..965bce9fdb6d6 100644 --- a/errors/page-without-valid-component.md +++ b/errors/page-without-valid-component.md @@ -14,3 +14,5 @@ For each, you'll want to check if the file is meant to be a page. If the file is not meant to be a page, and instead, is a shared component or file, move the file to a different folder like `components` or `lib`. If the file is meant to be a page, double check you have an `export default` with the React Component instead of an `export`. If you're already using `export default`, make sure the returned value is a valid React Component. + +If you need to support a different file extension for a page component (such as `.mdx`) or would like to include non-page files in the `pages` directory, configure [Custom Page Extensions](https://nextjs.org/docs/api-reference/next.config.js/custom-page-extensions) in the `next.config.js`. diff --git a/errors/promise-in-next-config.md b/errors/promise-in-next-config.md index f39ce7786fc78..84ac204fa85c9 100644 --- a/errors/promise-in-next-config.md +++ b/errors/promise-in-next-config.md @@ -14,6 +14,8 @@ module.exports = { #### Possible Ways to Fix It -Check your `next.config.js` for `async` or `return Promise` +In Next.js versions above `12.0.10`, `module.exports = async () =>` is supported. + +For older versions, you can check your `next.config.js` for `async` or `return Promise`. Potentially a plugin is returning a `Promise` from the webpack function. diff --git a/errors/react-hydration-error.md b/errors/react-hydration-error.md index 38ea8e05540b9..5b0ae7b6a500a 100644 --- a/errors/react-hydration-error.md +++ b/errors/react-hydration-error.md @@ -41,8 +41,9 @@ Common causes with css-in-js libraries: - When using Styled Components / Emotion - When css-in-js libraries are not set up for pre-rendering (SSR/SSG) it will often lead to a hydration mismatch. In general this means the application has to follow the Next.js example for the library. For example if `pages/_document` is missing and the Babel plugin is not added. - - Possible fix for Styled Components: https://github.com/vercel/next.js/tree/canary/examples/with-styled-components - - If you want to leverage Styled Components with the new Next.js Compiler in Next.js 12 there is an [experimental flag available](https://github.com/vercel/next.js/discussions/30174#discussion-3643870) + - Possible fix for Styled Components: + - If you want to leverage Styled Components with SWC in Next.js 12.1+ you need to [add it to your Next.js config under compiler options](https://nextjs.org/docs/advanced-features/compiler#styled-components): https://github.com/vercel/next.js/tree/canary/examples/with-styled-components + - If you want to use Styled Components with Babel, you need `pages/_document` and the Babel plugin: https://github.com/vercel/next.js/tree/canary/examples/with-styled-components-babel - Possible fix for Emotion: https://github.com/vercel/next.js/tree/canary/examples/with-emotion - When using other css-in-js libraries - Similar to Styled Components / Emotion css-in-js libraries generally need configuration specified in their examples in the [examples directory](https://github.com/vercel/next.js/tree/canary/examples) diff --git a/errors/swc-disabled.md b/errors/swc-disabled.md index 701f260c785b4..ce026dd0e882e 100644 --- a/errors/swc-disabled.md +++ b/errors/swc-disabled.md @@ -10,8 +10,6 @@ When an application has custom Babel configuration Next.js will automatically op Many of the integrations with external libraries that currently require custom Babel transformations will be ported to Rust-based SWC transforms in the near future. These include but are not limited to: -- Styled Components - Emotion -- Relay In order to prioritize transforms that will help you adopt SWC please provide your `.babelrc` on [the feedback thread](https://github.com/vercel/next.js/discussions/30174). diff --git a/examples/active-class-name/components/ActiveLink.js b/examples/active-class-name/components/ActiveLink.js index 0db59b6a9227a..7ada49c52056e 100644 --- a/examples/active-class-name/components/ActiveLink.js +++ b/examples/active-class-name/components/ActiveLink.js @@ -1,20 +1,46 @@ +import { useState, useEffect } from 'react' import { useRouter } from 'next/router' import PropTypes from 'prop-types' import Link from 'next/link' import React, { Children } from 'react' const ActiveLink = ({ children, activeClassName, ...props }) => { - const { asPath } = useRouter() + const { asPath, isReady } = useRouter() + const child = Children.only(children) const childClassName = child.props.className || '' + const [className, setClassName] = useState(childClassName) + + useEffect(() => { + // Check if the router fields are updated client-side + if (isReady) { + // Dynamic route will be matched via props.as + // Static route will be matched via props.href + const linkPathname = new URL(props.as || props.href, location.href) + .pathname + + // Using URL().pathname to get rid of query and hash + const activePathname = new URL(asPath, location.href).pathname + + const newClassName = + linkPathname === activePathname + ? `${childClassName} ${activeClassName}`.trim() + : childClassName - // pages/index.js will be matched via props.href - // pages/about.js will be matched via props.href - // pages/[slug].js will be matched via props.as - const className = - asPath === props.href || asPath === props.as - ? `${childClassName} ${activeClassName}`.trim() - : childClassName + if (newClassName !== className) { + setClassName(newClassName) + } + } + }, [ + asPath, + isReady, + props.as, + props.href, + childClassName, + activeClassName, + setClassName, + className, + ]) return ( <Link {...props}> diff --git a/examples/active-class-name/components/Nav.js b/examples/active-class-name/components/Nav.js index 2c710bb42468f..921cc15120bfd 100644 --- a/examples/active-class-name/components/Nav.js +++ b/examples/active-class-name/components/Nav.js @@ -22,6 +22,11 @@ const Nav = () => ( <a className="nav-link">About</a> </ActiveLink> </li> + <li> + <ActiveLink activeClassName="active" href="/blog"> + <a className="nav-link">Blog</a> + </ActiveLink> + </li> <li> <ActiveLink activeClassName="active" href="/[slug]" as="/dynamic-route"> <a className="nav-link">Dynamic Route</a> diff --git a/examples/active-class-name/next.config.js b/examples/active-class-name/next.config.js new file mode 100644 index 0000000000000..55e464ca31bfd --- /dev/null +++ b/examples/active-class-name/next.config.js @@ -0,0 +1,10 @@ +module.exports = { + async rewrites() { + return [ + { + source: '/blog', + destination: '/news', + }, + ] + }, +} diff --git a/examples/active-class-name/pages/news.js b/examples/active-class-name/pages/news.js new file mode 100644 index 0000000000000..74a8005a4c2c0 --- /dev/null +++ b/examples/active-class-name/pages/news.js @@ -0,0 +1,10 @@ +import Nav from '../components/Nav' + +const News = () => ( + <> + <Nav /> + <p>Hello, I'm the news page</p> + </> +) + +export default News diff --git a/examples/blog-starter-typescript/lib/markdownToHtml.ts b/examples/blog-starter-typescript/lib/markdownToHtml.ts index 4917e91044899..9b486c82f8b68 100644 --- a/examples/blog-starter-typescript/lib/markdownToHtml.ts +++ b/examples/blog-starter-typescript/lib/markdownToHtml.ts @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown: string) { diff --git a/examples/blog-starter-typescript/package.json b/examples/blog-starter-typescript/package.json index 96c0f3882ba9e..96af9fe2d0734 100644 --- a/examples/blog-starter-typescript/package.json +++ b/examples/blog-starter-typescript/package.json @@ -13,8 +13,8 @@ "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", - "remark": "13.0.0", - "remark-html": "13.0.1", + "remark": "14.0.2", + "remark-html": "15.0.1", "typescript": "^4.2.4" }, "devDependencies": { diff --git a/examples/blog-starter/lib/markdownToHtml.js b/examples/blog-starter/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/blog-starter/lib/markdownToHtml.js +++ b/examples/blog-starter/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/blog-starter/package.json b/examples/blog-starter/package.json index fed5332dfbe15..ee9d9ba4dc956 100644 --- a/examples/blog-starter/package.json +++ b/examples/blog-starter/package.json @@ -6,14 +6,14 @@ "start": "next start" }, "dependencies": { - "classnames": "2.2.6", - "date-fns": "2.16.1", - "gray-matter": "4.0.2", + "classnames": "2.3.1", + "date-fns": "2.28.0", + "gray-matter": "4.0.3", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", - "remark": "13.0.0", - "remark-html": "13.0.2" + "remark": "14.0.2", + "remark-html": "15.0.1" }, "devDependencies": { "autoprefixer": "^10.4.0", diff --git a/examples/blog-starter/postcss.config.js b/examples/blog-starter/postcss.config.js index 9fb517645aeea..3fa0a9514dc9d 100644 --- a/examples/blog-starter/postcss.config.js +++ b/examples/blog-starter/postcss.config.js @@ -1,3 +1,8 @@ +// If you want to use other PostCSS plugins, see the following: +// https://tailwindcss.com/docs/using-with-preprocessors module.exports = { - plugins: ['tailwindcss', 'autoprefixer'], + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, } diff --git a/examples/cms-agilitycms/components/hero-post.js b/examples/cms-agilitycms/components/hero-post.js index 141b7e641751a..d6e8869ddc2ba 100644 --- a/examples/cms-agilitycms/components/hero-post.js +++ b/examples/cms-agilitycms/components/hero-post.js @@ -20,7 +20,7 @@ export default function HeroPost({ slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-agilitycms/components/more-stories.js b/examples/cms-agilitycms/components/more-stories.js index 12c3cbc50f939..a4e0fdf761af8 100644 --- a/examples/cms-agilitycms/components/more-stories.js +++ b/examples/cms-agilitycms/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ title, posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> {title} </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-agilitycms/package.json b/examples/cms-agilitycms/package.json index 1eceba4208e19..8660055b38cbd 100644 --- a/examples/cms-agilitycms/package.json +++ b/examples/cms-agilitycms/package.json @@ -8,19 +8,17 @@ "dependencies": { "@agility/content-fetch": "^0.8.1", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "isomorphic-unfetch": "3.0.0", "next": "latest", "react": "^17.0.2", "react-datocms": "1.1.0", "react-dom": "^17.0.2", - "react-intersection-observer": "^8.26.1", - "remark": "11.0.2", - "remark-html": "10.0.0" + "react-intersection-observer": "^8.26.1" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-agilitycms/tailwind.config.js b/examples/cms-agilitycms/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-agilitycms/tailwind.config.js +++ b/examples/cms-agilitycms/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-builder-io/components/hero-post.js b/examples/cms-builder-io/components/hero-post.js index 680afd9829011..0a7b8f68f7fbd 100644 --- a/examples/cms-builder-io/components/hero-post.js +++ b/examples/cms-builder-io/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} slug={slug} url={coverImage} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-builder-io/components/more-stories.js b/examples/cms-builder-io/components/more-stories.js index bcf78e019cd28..45714977feea9 100644 --- a/examples/cms-builder-io/components/more-stories.js +++ b/examples/cms-builder-io/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.data.slug} diff --git a/examples/cms-builder-io/lib/constants.js b/examples/cms-builder-io/lib/constants.js index 442de6348b0f0..ab533b7f34926 100644 --- a/examples/cms-builder-io/lib/constants.js +++ b/examples/cms-builder-io/lib/constants.js @@ -2,7 +2,7 @@ export const EXAMPLE_PATH = 'cms-builder-io' export const CMS_NAME = 'Builder.io' export const CMS_URL = 'https://builder.io/' export const HOME_OG_IMAGE_URL = - 'https://og-image.now.sh/Next.js%20Blog%20example%20with%20**Builder.io**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F79c8b61740bc41d5ae722c000ddb5915' + 'https://og-image.vercel.app/Next.js%20Blog%20example%20with%20**Builder.io**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fcdn.builder.io%2Fapi%2Fv1%2Fimage%2Fassets%252FYJIGb4i01jvw0SRdL5Bt%252F79c8b61740bc41d5ae722c000ddb5915' export const BUILDER_CONFIG = { apiKey: process.env.NEXT_PUBLIC_BUILDER_API_KEY, postsModel: 'post', diff --git a/examples/cms-builder-io/package.json b/examples/cms-builder-io/package.json index 8b6de9713f828..e21a633a39c88 100644 --- a/examples/cms-builder-io/package.json +++ b/examples/cms-builder-io/package.json @@ -9,7 +9,7 @@ "@builder.io/react": "^1.1.47", "@builder.io/widgets": "^1.2.21", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" diff --git a/examples/cms-buttercms/components/hero-post.js b/examples/cms-buttercms/components/hero-post.js index a7a43667a0161..f45ed8853b127 100644 --- a/examples/cms-buttercms/components/hero-post.js +++ b/examples/cms-buttercms/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} url={coverImage} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-buttercms/components/more-stories.js b/examples/cms-buttercms/components/more-stories.js index 6f90f9b01c7a6..b12d7771a65a9 100644 --- a/examples/cms-buttercms/components/more-stories.js +++ b/examples/cms-buttercms/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-buttercms/package.json b/examples/cms-buttercms/package.json index 765980f7272f8..cce90c70a65b0 100644 --- a/examples/cms-buttercms/package.json +++ b/examples/cms-buttercms/package.json @@ -6,16 +6,16 @@ "start": "next start" }, "dependencies": { - "buttercms": "1.2.7", + "buttercms": "1.2.8", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-buttercms/tailwind.config.js b/examples/cms-buttercms/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-buttercms/tailwind.config.js +++ b/examples/cms-buttercms/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-contentful/components/hero-post.js b/examples/cms-contentful/components/hero-post.js index 1d93fecf116f1..5a1551333ae49 100644 --- a/examples/cms-contentful/components/hero-post.js +++ b/examples/cms-contentful/components/hero-post.js @@ -16,10 +16,10 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} slug={slug} url={coverImage.url} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> - <Link as={`/posts/${slug}`} href="/posts/[slug]"> + <Link href={`/posts/${slug}`}> <a className="hover:underline">{title}</a> </Link> </h3> diff --git a/examples/cms-contentful/components/more-stories.js b/examples/cms-contentful/components/more-stories.js index dcdd9b4e6ae7b..57fdbb6c4659c 100644 --- a/examples/cms-contentful/components/more-stories.js +++ b/examples/cms-contentful/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-contentful/contentful/export.json b/examples/cms-contentful/contentful/export.json index 952c6d6c75047..5d4bc27cc8590 100644 --- a/examples/cms-contentful/contentful/export.json +++ b/examples/cms-contentful/contentful/export.json @@ -151,7 +151,77 @@ "type": "RichText", "localized": false, "required": true, - "validations": [], + "validations": [ + { + "enabledMarks": ["bold", "italic", "underline", "code"], + "message": "Only bold, italic, underline, and code marks are allowed" + }, + { + "enabledNodeTypes": [ + "heading-1", + "heading-2", + "heading-3", + "heading-4", + "heading-5", + "heading-6", + "ordered-list", + "unordered-list", + "hr", + "blockquote", + "embedded-entry-block", + "embedded-asset-block", + "hyperlink", + "entry-hyperlink", + "asset-hyperlink", + "embedded-entry-inline" + ], + "message": "Only heading 1, heading 2, heading 3, heading 4, heading 5, heading 6, ordered list, unordered list, horizontal rule, quote, block entry, asset, link to Url, link to entry, link to asset, and inline entry nodes are allowed" + }, + { + "nodes": { + "asset-hyperlink": [ + { + "size": { + "max": 10 + }, + "message": null + } + ], + "embedded-asset-block": [ + { + "size": { + "max": 10 + }, + "message": null + } + ], + "embedded-entry-block": [ + { + "size": { + "max": 10 + }, + "message": null + } + ], + "embedded-entry-inline": [ + { + "size": { + "max": 10 + }, + "message": null + } + ], + "entry-hyperlink": [ + { + "size": { + "max": 10 + }, + "message": null + } + ] + } + } + ], "disabled": false, "omitted": false }, diff --git a/examples/cms-contentful/package.json b/examples/cms-contentful/package.json index e712efd3d7a73..37e8e75f9625f 100644 --- a/examples/cms-contentful/package.json +++ b/examples/cms-contentful/package.json @@ -15,9 +15,9 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", + "autoprefixer": "10.4.2", "contentful-import": "^7.8.7", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-contentful/tailwind.config.js b/examples/cms-contentful/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-contentful/tailwind.config.js +++ b/examples/cms-contentful/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-cosmic/components/hero-post.js b/examples/cms-cosmic/components/hero-post.js index 453b1816ee4e8..e8f7823c2b039 100644 --- a/examples/cms-cosmic/components/hero-post.js +++ b/examples/cms-cosmic/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} url={coverImage.imgix_url} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-cosmic/components/more-stories.js b/examples/cms-cosmic/components/more-stories.js index 2e25326c352da..35bb95e0ab66b 100644 --- a/examples/cms-cosmic/components/more-stories.js +++ b/examples/cms-cosmic/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-cosmic/lib/markdownToHtml.js b/examples/cms-cosmic/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/cms-cosmic/lib/markdownToHtml.js +++ b/examples/cms-cosmic/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/cms-cosmic/package.json b/examples/cms-cosmic/package.json index c3a4765e7de3b..83f9962545e69 100644 --- a/examples/cms-cosmic/package.json +++ b/examples/cms-cosmic/package.json @@ -8,19 +8,19 @@ "dependencies": { "classnames": "2.3.1", "cosmicjs": "^3.2.43", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "lazysizes": "^5.2.1-rc2", "next": "latest", "react": "^17.0.2", "react-datocms": "1.6.3", "react-dom": "^17.0.2", "react-imgix": "^9.0.2", - "remark": "12.0.0", - "remark-html": "11.0.2" + "remark": "14.0.2", + "remark-html": "15.0.1" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-cosmic/tailwind.config.js b/examples/cms-cosmic/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-cosmic/tailwind.config.js +++ b/examples/cms-cosmic/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-datocms/components/hero-post.js b/examples/cms-datocms/components/hero-post.js index 133dc33e58692..0ca3e0cc4978c 100644 --- a/examples/cms-datocms/components/hero-post.js +++ b/examples/cms-datocms/components/hero-post.js @@ -20,7 +20,7 @@ export default function HeroPost({ slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-datocms/components/more-stories.js b/examples/cms-datocms/components/more-stories.js index 13a09bd8f729e..4bab320f65374 100644 --- a/examples/cms-datocms/components/more-stories.js +++ b/examples/cms-datocms/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-datocms/lib/markdownToHtml.js b/examples/cms-datocms/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/cms-datocms/lib/markdownToHtml.js +++ b/examples/cms-datocms/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/cms-datocms/package.json b/examples/cms-datocms/package.json index 7f4a105c08694..74e35d356bd20 100644 --- a/examples/cms-datocms/package.json +++ b/examples/cms-datocms/package.json @@ -7,17 +7,17 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-datocms": "1.6.3", "react-dom": "^17.0.2", - "remark": "12.0.0", - "remark-html": "11.0.2" + "remark": "14.0.2", + "remark-html": "15.0.1" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-datocms/tailwind.config.js b/examples/cms-datocms/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-datocms/tailwind.config.js +++ b/examples/cms-datocms/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-drupal/components/hero-post.js b/examples/cms-drupal/components/hero-post.js index c5aa6d0a76609..cf3e657a9def6 100644 --- a/examples/cms-drupal/components/hero-post.js +++ b/examples/cms-drupal/components/hero-post.js @@ -18,7 +18,7 @@ export default function HeroPost({ <CoverImage title={title} coverImage={coverImage} slug={slug} /> )} </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={slug}> diff --git a/examples/cms-drupal/package.json b/examples/cms-drupal/package.json index 7e36cfe305c20..bb693224fb396 100644 --- a/examples/cms-drupal/package.json +++ b/examples/cms-drupal/package.json @@ -7,15 +7,15 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "next-drupal": "latest", "react": "17.0.2", "react-dom": "17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-drupal/tailwind.config.js b/examples/cms-drupal/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-drupal/tailwind.config.js +++ b/examples/cms-drupal/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-ghost/components/hero-post.js b/examples/cms-ghost/components/hero-post.js index 1d786d96d2be6..6a91725afd642 100644 --- a/examples/cms-ghost/components/hero-post.js +++ b/examples/cms-ghost/components/hero-post.js @@ -22,7 +22,7 @@ export default function HeroPost({ height={1216} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-ghost/components/more-stories.js b/examples/cms-ghost/components/more-stories.js index 92eefcf51c36f..07c12287d7cfa 100644 --- a/examples/cms-ghost/components/more-stories.js +++ b/examples/cms-ghost/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-ghost/package.json b/examples/cms-ghost/package.json index 0a850fbd7f0cf..98c5eb6488f8e 100644 --- a/examples/cms-ghost/package.json +++ b/examples/cms-ghost/package.json @@ -8,15 +8,15 @@ "dependencies": { "@tryghost/content-api": "^1.4.13", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "lazysizes": "^5.3.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-ghost/tailwind.config.js b/examples/cms-ghost/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-ghost/tailwind.config.js +++ b/examples/cms-ghost/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-graphcms/components/cover-image.js b/examples/cms-graphcms/components/cover-image.js index a4cda9cc8ba75..0acaa9c23b99e 100644 --- a/examples/cms-graphcms/components/cover-image.js +++ b/examples/cms-graphcms/components/cover-image.js @@ -18,7 +18,7 @@ export default function CoverImage({ title, url, slug }) { return ( <div className="sm:mx-0"> {slug ? ( - <Link as={`/posts/${slug}`} href="/posts/[slug]"> + <Link href={`/posts/${slug}`}> <a aria-label={title}>{image}</a> </Link> ) : ( diff --git a/examples/cms-graphcms/components/hero-post.js b/examples/cms-graphcms/components/hero-post.js index 9f39ead6849ec..776866d8dc76e 100644 --- a/examples/cms-graphcms/components/hero-post.js +++ b/examples/cms-graphcms/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage slug={slug} title={title} url={coverImage.url} /> </div> - <div className="mb-20 md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 md:mb-28"> + <div className="mb-20 md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 md:mb-28"> <div> <h3 className="mb-4 text-4xl leading-tight lg:text-6xl"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-graphcms/package.json b/examples/cms-graphcms/package.json index 244df95bf3fe9..daf83ed66dafe 100644 --- a/examples/cms-graphcms/package.json +++ b/examples/cms-graphcms/package.json @@ -7,14 +7,14 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-graphcms/tailwind.config.js b/examples/cms-graphcms/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-graphcms/tailwind.config.js +++ b/examples/cms-graphcms/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-kontent/components/hero-post.js b/examples/cms-kontent/components/hero-post.js index 8536e825c5d58..76495802b5793 100644 --- a/examples/cms-kontent/components/hero-post.js +++ b/examples/cms-kontent/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} src={coverImage} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-kontent/components/more-stories.js b/examples/cms-kontent/components/more-stories.js index dcdd9b4e6ae7b..57fdbb6c4659c 100644 --- a/examples/cms-kontent/components/more-stories.js +++ b/examples/cms-kontent/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-kontent/lib/markdownToHtml.js b/examples/cms-kontent/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/cms-kontent/lib/markdownToHtml.js +++ b/examples/cms-kontent/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/cms-kontent/package.json b/examples/cms-kontent/package.json index c18982d1da729..26e8bde64b13c 100644 --- a/examples/cms-kontent/package.json +++ b/examples/cms-kontent/package.json @@ -8,18 +8,18 @@ "dependencies": { "@kentico/kontent-delivery": "^9.2.0", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "gray-matter": "4.0.3", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", - "remark": "11.0.2", - "remark-html": "10.0.0", + "remark": "14.0.2", + "remark-html": "15.0.1", "rxjs": "^6.6.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-kontent/tailwind.config.js b/examples/cms-kontent/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-kontent/tailwind.config.js +++ b/examples/cms-kontent/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-prepr/README.md b/examples/cms-prepr/README.md index 8c5d571130c30..b44762576f2f4 100644 --- a/examples/cms-prepr/README.md +++ b/examples/cms-prepr/README.md @@ -4,10 +4,10 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas ## Demo -- **Live**: [https://next-blog-prepr.now.sh/](https://next-blog-prepr.now.sh/) -- **Preview Mode**: [https://next-blog-prepr.now.sh/api/preview...](https://next-blog-prepr.now.sh/api/preview?secret=237864ihasdhj283768&slug=discover-enjoy-amsterdam) +- **Live**: [https://next-blog-prepr.vercel.app/](https://next-blog-prepr.vercel.app/) +- **Preview Mode**: [https://next-blog-prepr.vercel.app/api/preview...](https://next-blog-prepr.vercel.app/api/preview?secret=237864ihasdhj283768&slug=discover-enjoy-amsterdam) -### [https://next-blog-prepr.now.sh/](https://next-blog-prepr.now.sh/) +### [https://next-blog-prepr.vercel.app/](https://next-blog-prepr.vercel.app/) ### Related examples diff --git a/examples/cms-prepr/components/hero-post.js b/examples/cms-prepr/components/hero-post.js index c2ff575ee26aa..5fb5c6909be75 100644 --- a/examples/cms-prepr/components/hero-post.js +++ b/examples/cms-prepr/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage slug={slug} title={title} url={coverImage} /> </div> - <div className="mb-20 md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 md:mb-28"> + <div className="mb-20 md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 md:mb-28"> <div> <h3 className="mb-4 text-4xl leading-tight lg:text-6xl"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-prepr/package.json b/examples/cms-prepr/package.json index 9ae6095e47797..e2c40a1a57eaa 100644 --- a/examples/cms-prepr/package.json +++ b/examples/cms-prepr/package.json @@ -8,14 +8,14 @@ "dependencies": { "@preprio/nodejs-sdk": "^1.1.0", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-prepr/tailwind.config.js b/examples/cms-prepr/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-prepr/tailwind.config.js +++ b/examples/cms-prepr/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-prismic/components/hero-post.js b/examples/cms-prismic/components/hero-post.js index 8d181d29dfa8c..b594f5420020d 100644 --- a/examples/cms-prismic/components/hero-post.js +++ b/examples/cms-prismic/components/hero-post.js @@ -21,7 +21,7 @@ export default function HeroPost({ url={coverImage.url} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-prismic/components/more-stories.js b/examples/cms-prismic/components/more-stories.js index cfe7b62e4150b..2a29128c8117b 100644 --- a/examples/cms-prismic/components/more-stories.js +++ b/examples/cms-prismic/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map(({ node }) => ( <PostPreview key={node._meta.uid} diff --git a/examples/cms-prismic/package.json b/examples/cms-prismic/package.json index 9f844983b733e..c3d4d9d25d5d0 100644 --- a/examples/cms-prismic/package.json +++ b/examples/cms-prismic/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "prismic-javascript": "3.0.2", "prismic-reactjs": "1.3.4", @@ -15,8 +15,8 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-prismic/tailwind.config.js b/examples/cms-prismic/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-prismic/tailwind.config.js +++ b/examples/cms-prismic/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-sanity/README.md b/examples/cms-sanity/README.md index 7dfcb0a530ea2..e7b5d268d7f9a 100644 --- a/examples/cms-sanity/README.md +++ b/examples/cms-sanity/README.md @@ -2,6 +2,11 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Sanity](https://www.sanity.io/) as the data source. +You'll get: + +- Sanity Studio running on localhost +- Sub-second as-you-type previews in Next.js + ## Demo ### [https://next-blog-sanity.vercel.app/](https://next-blog-sanity.vercel.app/) @@ -85,11 +90,30 @@ SANITY_API_TOKEN=... SANITY_PREVIEW_SECRET=... ``` -### Step 5. Prepare project for previewing +### Step 5. Prepare the project for previewing + +5.1. Install the `@sanity/production-preview` plugin with `sanity install @sanity/production-preview`. + +5.2. Create a file called `resolveProductionUrl.js` (we'll get back to that file in a bit). + +5.3. Open your studio's sanity.json, and add the following entry to the parts-array: -Go to https://www.sanity.io/docs/preview-content-on-site and follow the three steps on that page. It should be done inside the studio project generated in Step 2. +```diff +{ + "plugins": [ + "@sanity/production-preview" + ], + "parts": [ + //... ++ { ++ "implements": "part:@sanity/production-preview/resolve-production-url", ++ "path": "./resolveProductionUrl.js" ++ } + ] +} +``` -When you get to the second step about creating a file called `resolveProductionUrl.js`, copy the following instead: +Now, go back to `resolveProductionUrl.js` and add a function that will receive the full document that was selected for previewing: ```js const previewSecret = 'MY_SECRET' // Copy the string you used for SANITY_PREVIEW_SECRET @@ -100,6 +124,8 @@ export default function resolveProductionUrl(document) { } ``` +For more information on live previewing check the [full guide.](https://www.sanity.io/guides/nextjs-live-preview) + ### Step 6. Copy the schema file After initializing your Sanity studio project there should be a `schemas` folder. @@ -168,3 +194,8 @@ To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [ Alternatively, you can deploy using our template by clicking on the Deploy button below. [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-sanity&project-name=cms-sanity&repository-name=cms-sanity&env=NEXT_PUBLIC_SANITY_PROJECT_ID,SANITY_API_TOKEN,SANITY_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Sanity&envLink=https://vercel.link/cms-sanity-env) + +#### Next steps + +- Invalidate your routes in production [on-demand](https://nextjs.org/blog/next-12-1#on-demand-incremental-static-regeneration-beta) with GROQ powered webhooks +- Mount your preview inside the Sanity Studio for comfortable side-by-side editing diff --git a/examples/cms-sanity/components/cover-image.js b/examples/cms-sanity/components/cover-image.js index 60bf2e8847a35..818e8eef5e47e 100644 --- a/examples/cms-sanity/components/cover-image.js +++ b/examples/cms-sanity/components/cover-image.js @@ -5,15 +5,19 @@ import { urlForImage } from '../lib/sanity' export default function CoverImage({ title, slug, image: source }) { const image = source ? ( - <Image - width={2000} - height={1000} - alt={`Cover Image for ${title}`} - src={urlForImage(source).height(1000).width(2000).url()} + <div className={cn('shadow-small', { 'hover:shadow-medium transition-shadow duration-200': slug, })} - /> + > + <Image + layout="responsive" + width={2000} + height={1000} + alt={`Cover Image for ${title}`} + src={urlForImage(source).height(1000).width(2000).url()} + /> + </div> ) : ( <div style={{ paddingTop: '50%', backgroundColor: '#ddd' }} /> ) diff --git a/examples/cms-sanity/components/hero-post.js b/examples/cms-sanity/components/hero-post.js index f198fb3f54d7e..4d81ed0850ac9 100644 --- a/examples/cms-sanity/components/hero-post.js +++ b/examples/cms-sanity/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage slug={slug} title={title} image={coverImage} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-sanity/components/more-stories.js b/examples/cms-sanity/components/more-stories.js index dcdd9b4e6ae7b..57fdbb6c4659c 100644 --- a/examples/cms-sanity/components/more-stories.js +++ b/examples/cms-sanity/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-sanity/components/post-body.js b/examples/cms-sanity/components/post-body.js index 98a958318f08a..2e0cb68a48dc4 100644 --- a/examples/cms-sanity/components/post-body.js +++ b/examples/cms-sanity/components/post-body.js @@ -1,10 +1,10 @@ import markdownStyles from './markdown-styles.module.css' -import BlockContent from '@sanity/block-content-to-react' +import { PortableText } from '@portabletext/react' export default function PostBody({ content }) { return ( - <div className="max-w-2xl mx-auto"> - <BlockContent blocks={content} className={markdownStyles.markdown} /> + <div className={`max-w-2xl mx-auto ${markdownStyles.markdown}`}> + <PortableText value={content} /> </div> ) } diff --git a/examples/cms-sanity/lib/sanity.js b/examples/cms-sanity/lib/sanity.js index 76cedf4cb57dc..1936305c132cf 100644 --- a/examples/cms-sanity/lib/sanity.js +++ b/examples/cms-sanity/lib/sanity.js @@ -1,7 +1,5 @@ -import { - createImageUrlBuilder, - createPreviewSubscriptionHook, -} from 'next-sanity' +import createImageUrlBuilder from '@sanity/image-url' +import { createPreviewSubscriptionHook } from 'next-sanity' import { sanityConfig } from './config' export const imageBuilder = createImageUrlBuilder(sanityConfig) diff --git a/examples/cms-sanity/next.config.js b/examples/cms-sanity/next.config.js new file mode 100644 index 0000000000000..da5f2c88763ef --- /dev/null +++ b/examples/cms-sanity/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + images: { + domains: ['cdn.sanity.io'], + }, +} diff --git a/examples/cms-sanity/package.json b/examples/cms-sanity/package.json index 0f27d6bc0035f..8b576f61ae8c1 100644 --- a/examples/cms-sanity/package.json +++ b/examples/cms-sanity/package.json @@ -6,17 +6,18 @@ "start": "next start" }, "dependencies": { - "@sanity/block-content-to-react": "2.0.7", + "@portabletext/react": "^1.0.3", + "@sanity/image-url": "^1.0.1", "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", - "next-sanity": "0.3.0", + "next-sanity": "0.5.0", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.7", + "tailwindcss": "^3.0.23" } } diff --git a/examples/cms-sanity/tailwind.config.js b/examples/cms-sanity/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-sanity/tailwind.config.js +++ b/examples/cms-sanity/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-storyblok/components/hero-post.js b/examples/cms-storyblok/components/hero-post.js index 2cab6b993289e..eadefeaac135a 100644 --- a/examples/cms-storyblok/components/hero-post.js +++ b/examples/cms-storyblok/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} url={coverImage} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-storyblok/components/more-stories.js b/examples/cms-storyblok/components/more-stories.js index 2ea20c3f6b254..583c01eda2657 100644 --- a/examples/cms-storyblok/components/more-stories.js +++ b/examples/cms-storyblok/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-storyblok/lib/markdownToHtml.js b/examples/cms-storyblok/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/cms-storyblok/lib/markdownToHtml.js +++ b/examples/cms-storyblok/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/cms-storyblok/package.json b/examples/cms-storyblok/package.json index db15be01bd488..654627156f08e 100644 --- a/examples/cms-storyblok/package.json +++ b/examples/cms-storyblok/package.json @@ -7,17 +7,17 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", - "remark": "12.0.0", - "remark-html": "11.0.2", + "remark": "14.0.2", + "remark-html": "15.0.1", "storyblok-js-client": "2.5.0" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-storyblok/tailwind.config.js b/examples/cms-storyblok/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-storyblok/tailwind.config.js +++ b/examples/cms-storyblok/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-strapi/components/hero-post.js b/examples/cms-strapi/components/hero-post.js index a246a08473175..910933b96169d 100644 --- a/examples/cms-strapi/components/hero-post.js +++ b/examples/cms-strapi/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} url={coverImage.url} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-strapi/components/more-stories.js b/examples/cms-strapi/components/more-stories.js index 13a09bd8f729e..4bab320f65374 100644 --- a/examples/cms-strapi/components/more-stories.js +++ b/examples/cms-strapi/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-strapi/package.json b/examples/cms-strapi/package.json index 147d835ceb423..86e7e8363ee79 100644 --- a/examples/cms-strapi/package.json +++ b/examples/cms-strapi/package.json @@ -7,7 +7,7 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -15,8 +15,8 @@ "remark-html": "^15.0.0" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-strapi/tailwind.config.js b/examples/cms-strapi/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-strapi/tailwind.config.js +++ b/examples/cms-strapi/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-takeshape/components/hero-post.js b/examples/cms-takeshape/components/hero-post.js index 209d62c118fcd..70d417ef57928 100644 --- a/examples/cms-takeshape/components/hero-post.js +++ b/examples/cms-takeshape/components/hero-post.js @@ -16,7 +16,7 @@ export default function HeroPost({ <div className="mb-8 md:mb-16"> <CoverImage title={title} coverImage={coverImage} slug={slug} /> </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-takeshape/components/more-stories.js b/examples/cms-takeshape/components/more-stories.js index dcdd9b4e6ae7b..57fdbb6c4659c 100644 --- a/examples/cms-takeshape/components/more-stories.js +++ b/examples/cms-takeshape/components/more-stories.js @@ -6,7 +6,7 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map((post) => ( <PostPreview key={post.slug} diff --git a/examples/cms-takeshape/lib/markdownToHtml.js b/examples/cms-takeshape/lib/markdownToHtml.js index aebc42164b8ee..735fed2df3832 100644 --- a/examples/cms-takeshape/lib/markdownToHtml.js +++ b/examples/cms-takeshape/lib/markdownToHtml.js @@ -1,4 +1,4 @@ -import remark from 'remark' +import { remark } from 'remark' import html from 'remark-html' export default async function markdownToHtml(markdown) { diff --git a/examples/cms-takeshape/package.json b/examples/cms-takeshape/package.json index a73269bd0f5c0..b628d0b9a9b9f 100644 --- a/examples/cms-takeshape/package.json +++ b/examples/cms-takeshape/package.json @@ -7,17 +7,17 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2", - "remark": "11.0.2", - "remark-html": "10.0.0", + "remark": "14.0.2", + "remark-html": "15.0.1", "takeshape-routing": "4.86.0" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-takeshape/tailwind.config.js b/examples/cms-takeshape/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-takeshape/tailwind.config.js +++ b/examples/cms-takeshape/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-umbraco-heartcore/.gitignore b/examples/cms-umbraco-heartcore/.gitignore old mode 100755 new mode 100644 diff --git a/examples/cms-umbraco-heartcore/README.md b/examples/cms-umbraco-heartcore/README.md index 9cae47c1b16d8..3803070e46d07 100755 --- a/examples/cms-umbraco-heartcore/README.md +++ b/examples/cms-umbraco-heartcore/README.md @@ -33,7 +33,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e ## How to use -Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash npx create-next-app --example cms-umbraco-heartcore cms-umbraco-heartcore-app @@ -91,7 +91,7 @@ yarn install yarn dev ``` -Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/zeit/next.js/discussions). +Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). ### Step 5. Try preview mode diff --git a/examples/cms-umbraco-heartcore/components/alert.js b/examples/cms-umbraco-heartcore/components/alert.js index 74b887c1a0460..b924cb097f169 100755 --- a/examples/cms-umbraco-heartcore/components/alert.js +++ b/examples/cms-umbraco-heartcore/components/alert.js @@ -27,7 +27,7 @@ export default function Alert({ preview }) { <> The source code for this blog is{' '} <a - href={`https://github.com/zeit/next.js/tree/canary/examples/${EXAMPLE_PATH}`} + href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`} className="underline hover:text-success duration-200 transition-colors" > available on GitHub diff --git a/examples/cms-umbraco-heartcore/components/footer.js b/examples/cms-umbraco-heartcore/components/footer.js index dbde8ff306efd..da9eed88ec263 100755 --- a/examples/cms-umbraco-heartcore/components/footer.js +++ b/examples/cms-umbraco-heartcore/components/footer.js @@ -17,7 +17,7 @@ export default function Footer() { Read Documentation </a> <a - href={`https://github.com/zeit/next.js/tree/canary/examples/${EXAMPLE_PATH}`} + href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`} className="mx-3 font-bold hover:underline" > View on GitHub diff --git a/examples/cms-umbraco-heartcore/lib/constants.js b/examples/cms-umbraco-heartcore/lib/constants.js index 667ffdf297a37..920e3c83f3834 100755 --- a/examples/cms-umbraco-heartcore/lib/constants.js +++ b/examples/cms-umbraco-heartcore/lib/constants.js @@ -2,4 +2,4 @@ export const EXAMPLE_PATH = 'cms-umbraco-heartcore' export const CMS_NAME = 'Umbraco Heartcore' export const CMS_URL = 'https://umbraco.com/heartcore' export const HOME_OG_IMAGE_URL = - 'https://og-image.now.sh/Next.js%20Blog%20Example%20with%20**Umbraco%20Heartcore**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https://media.umbraco.io/demo-headless/8d8a349dde73ca6/u_heartcore_heart_lockup_tagline_dark.svg' + 'https://og-image.vercel.app/Next.js%20Blog%20Example%20with%20**Umbraco%20Heartcore**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https://media.umbraco.io/demo-headless/8d8a349dde73ca6/u_heartcore_heart_lockup_tagline_dark.svg' diff --git a/examples/cms-umbraco-heartcore/package.json b/examples/cms-umbraco-heartcore/package.json index b3c3fa7cf8069..daf83ed66dafe 100755 --- a/examples/cms-umbraco-heartcore/package.json +++ b/examples/cms-umbraco-heartcore/package.json @@ -7,14 +7,14 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.27.0", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-umbraco-heartcore/tailwind.config.js b/examples/cms-umbraco-heartcore/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100755 --- a/examples/cms-umbraco-heartcore/tailwind.config.js +++ b/examples/cms-umbraco-heartcore/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/cms-wordpress/components/hero-post.js b/examples/cms-wordpress/components/hero-post.js index 302ff1e74e1ba..46d376e81ad63 100644 --- a/examples/cms-wordpress/components/hero-post.js +++ b/examples/cms-wordpress/components/hero-post.js @@ -18,7 +18,7 @@ export default function HeroPost({ <CoverImage title={title} coverImage={coverImage} slug={slug} /> )} </div> - <div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28"> + <div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28"> <div> <h3 className="mb-4 text-4xl lg:text-6xl leading-tight"> <Link href={`/posts/${slug}`}> diff --git a/examples/cms-wordpress/components/more-stories.js b/examples/cms-wordpress/components/more-stories.js index d77cd124e75c5..951fe76a1500a 100644 --- a/examples/cms-wordpress/components/more-stories.js +++ b/examples/cms-wordpress/components/more-stories.js @@ -6,14 +6,14 @@ export default function MoreStories({ posts }) { <h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight"> More Stories </h2> - <div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32"> + <div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32"> {posts.map(({ node }) => ( <PostPreview key={node.slug} title={node.title} - coverImage={node.featuredImage?.node} + coverImage={node.featuredImage} date={node.date} - author={node.author?.node} + author={node.author} slug={node.slug} excerpt={node.excerpt} /> diff --git a/examples/cms-wordpress/lib/api.js b/examples/cms-wordpress/lib/api.js index cabad8f8113fb..9ec551c9d7a67 100644 --- a/examples/cms-wordpress/lib/api.js +++ b/examples/cms-wordpress/lib/api.js @@ -70,18 +70,14 @@ export async function getAllPostsForHome(preview) { slug date featuredImage { - node { - sourceUrl - } + sourceUrl } author { - node { - name - firstName - lastName - avatar { - url - } + name + firstName + lastName + avatar { + url } } } @@ -125,14 +121,10 @@ export async function getPostAndMorePosts(slug, preview, previewData) { slug date featuredImage { - node { - sourceUrl - } + sourceUrl } author { - node { - ...AuthorFields - } + ...AuthorFields } categories { edges { @@ -164,9 +156,7 @@ export async function getPostAndMorePosts(slug, preview, previewData) { excerpt content author { - node { - ...AuthorFields - } + ...AuthorFields } } } diff --git a/examples/cms-wordpress/next.config.js b/examples/cms-wordpress/next.config.js new file mode 100644 index 0000000000000..de6202d75136e --- /dev/null +++ b/examples/cms-wordpress/next.config.js @@ -0,0 +1,8 @@ +module.exports = { + images: { + domains: [ + // "[yourapp].wpengine.com" (Update this to be your Wordpress application name in order to load images connected to your posts) + 'secure.gravatar.com', + ], + }, +} diff --git a/examples/cms-wordpress/package.json b/examples/cms-wordpress/package.json index 244df95bf3fe9..daf83ed66dafe 100644 --- a/examples/cms-wordpress/package.json +++ b/examples/cms-wordpress/package.json @@ -7,14 +7,14 @@ }, "dependencies": { "classnames": "2.3.1", - "date-fns": "2.22.1", + "date-fns": "2.28.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "autoprefixer": "10.2.6", - "postcss": "8.3.5", - "tailwindcss": "^2.2.4" + "autoprefixer": "10.4.2", + "postcss": "8.4.5", + "tailwindcss": "^3.0.15" } } diff --git a/examples/cms-wordpress/pages/index.js b/examples/cms-wordpress/pages/index.js index e6c987fb8870e..2d01e152485e0 100644 --- a/examples/cms-wordpress/pages/index.js +++ b/examples/cms-wordpress/pages/index.js @@ -22,9 +22,9 @@ export default function Index({ allPosts: { edges }, preview }) { {heroPost && ( <HeroPost title={heroPost.title} - coverImage={heroPost.featuredImage?.node} + coverImage={heroPost.featuredImage} date={heroPost.date} - author={heroPost.author?.node} + author={heroPost.author} slug={heroPost.slug} excerpt={heroPost.excerpt} /> @@ -38,6 +38,7 @@ export default function Index({ allPosts: { edges }, preview }) { export async function getStaticProps({ preview = false }) { const allPosts = await getAllPostsForHome(preview) + return { props: { allPosts, preview }, } diff --git a/examples/cms-wordpress/pages/posts/[slug].js b/examples/cms-wordpress/pages/posts/[slug].js index b57d69536ff98..dc06089f58310 100644 --- a/examples/cms-wordpress/pages/posts/[slug].js +++ b/examples/cms-wordpress/pages/posts/[slug].js @@ -36,14 +36,14 @@ export default function Post({ post, posts, preview }) { diff --git a/examples/cms-wordpress/tailwind.config.js b/examples/cms-wordpress/tailwind.config.js index 4c03f3f935e85..21253ff3793ee 100644 --- a/examples/cms-wordpress/tailwind.config.js +++ b/examples/cms-wordpress/tailwind.config.js @@ -1,7 +1,8 @@ module.exports = { - mode: 'jit', - purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], - darkMode: false, // or 'media' or 'class' + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], theme: { extend: { colors: { @@ -32,8 +33,5 @@ module.exports = { }, }, }, - variants: { - extend: {}, - }, plugins: [], } diff --git a/examples/custom-server-actionhero/README.md b/examples/custom-server-actionhero/README.md index c1c4e1c37a3da..62dcb0675609c 100644 --- a/examples/custom-server-actionhero/README.md +++ b/examples/custom-server-actionhero/README.md @@ -80,7 +80,7 @@ module.exports = class CreateChatRoom extends Action { } ``` -3. Tell ActionHero to use the api rather than the file server as the top-level route in `api.config.servers.web.rootEndpointType = 'api'`. This will allows "/" to listen to API requests. Also update `api.config.general.paths.public = [ path.join(__dirname, '/../static') ]`. In this configuration, the next 'static' renderer will take priority over the ActionHero 'public file' api. Note that any static assets (CSS, fonts, etc) will need to be in "./static" rather than "./public". +3. Tell ActionHero to use the api rather than the file server as the top-level route in `api.config.servers.web.rootEndpointType = 'api'`. This will allows "/" to listen to API requests. Also update `api.config.general.paths.public = [ path.join(__dirname, '/../static') ]`. In this configuration, the next 'static' renderer will take priority over the ActionHero 'public file' api. Note that any static assets (CSS, fonts, etc.) will need to be in "./static" rather than "./public". Note that this is where the websocket server, if you enable it, will place the `ActionheroWebsocketClient` library.
diff --git a/examples/with-mysql/next-env.d.ts b/examples/custom-server-hapi/next-env.d.ts similarity index 100% rename from examples/with-mysql/next-env.d.ts rename to examples/custom-server-hapi/next-env.d.ts diff --git a/examples/custom-server-hapi/next-wrapper.js b/examples/custom-server-hapi/next-wrapper.js deleted file mode 100644 index 431191084fa84..0000000000000 --- a/examples/custom-server-hapi/next-wrapper.js +++ /dev/null @@ -1,26 +0,0 @@ -const nextHandlerWrapper = (app) => { - const handler = app.getRequestHandler() - return async ({ raw, url, query }, h) => { - url.query = query - await handler(raw.req, raw.res, url) - return h.close - } -} - -const pathWrapper = - (app, pathName, opts) => - async ({ raw, query, params }, h) => { - const html = await app.render( - raw.req, - raw.res, - pathName, - { ...query, ...params }, - opts - ) - return h.response(html).code(raw.res.statusCode) - } - -module.exports = { - pathWrapper, - nextHandlerWrapper, -} diff --git a/examples/custom-server-hapi/nodemon.json b/examples/custom-server-hapi/nodemon.json new file mode 100644 index 0000000000000..c9fa4132a1550 --- /dev/null +++ b/examples/custom-server-hapi/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["server"], + "exec": "ts-node --project tsconfig.server.json server/server.ts", + "ext": "js ts" +} diff --git a/examples/custom-server-hapi/package.json b/examples/custom-server-hapi/package.json index 6de2563a24e15..60b0362bd7c65 100644 --- a/examples/custom-server-hapi/package.json +++ b/examples/custom-server-hapi/package.json @@ -1,15 +1,24 @@ { "private": true, "scripts": { - "dev": "node server.js", - "build": "next build", - "start": "cross-env NODE_ENV=production node server.js" + "dev": "nodemon", + "build": "next build && tsc --project tsconfig.server.json", + "start": "cross-env NODE_ENV=production node dist/server.js" }, "dependencies": { - "@hapi/hapi": "^18.3.1", - "cross-env": "^5.2.0", + "@hapi/hapi": "^20.2.1", + "cross-env": "^7.0.3", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" + }, + "devDependencies": { + "@types/hapi__hapi": "^20.0.10", + "@types/node": "^16.11.25", + "@types/react": "^17.0.39", + "@types/react-dom": "^17.0.11", + "nodemon": "^2.0.15", + "ts-node": "^10.5.0", + "typescript": "^4.5.5" } } diff --git a/examples/custom-server-hapi/pages/a.js b/examples/custom-server-hapi/pages/a.tsx similarity index 100% rename from examples/custom-server-hapi/pages/a.js rename to examples/custom-server-hapi/pages/a.tsx diff --git a/examples/custom-server-hapi/pages/b.js b/examples/custom-server-hapi/pages/b.tsx similarity index 100% rename from examples/custom-server-hapi/pages/b.js rename to examples/custom-server-hapi/pages/b.tsx diff --git a/examples/custom-server-hapi/pages/index.js b/examples/custom-server-hapi/pages/index.tsx similarity index 100% rename from examples/custom-server-hapi/pages/index.js rename to examples/custom-server-hapi/pages/index.tsx diff --git a/examples/custom-server-hapi/server/next-wrapper.ts b/examples/custom-server-hapi/server/next-wrapper.ts new file mode 100644 index 0000000000000..596aac18c613a --- /dev/null +++ b/examples/custom-server-hapi/server/next-wrapper.ts @@ -0,0 +1,16 @@ +import type { NextServer } from 'next/dist/server/next' +import type { Lifecycle } from '@hapi/hapi' +import type { NextUrlWithParsedQuery } from 'next/dist/server/request-meta' + +const nextHandlerWrapper = (app: NextServer): Lifecycle.Method => { + const handler = app.getRequestHandler() + + return async ({ raw, url, query }, h) => { + const nextUrl = url as unknown as NextUrlWithParsedQuery + nextUrl.query = query + await handler(raw.req, raw.res, nextUrl) + return h.close + } +} + +export { nextHandlerWrapper } diff --git a/examples/custom-server-hapi/server.js b/examples/custom-server-hapi/server/server.ts similarity index 81% rename from examples/custom-server-hapi/server.js rename to examples/custom-server-hapi/server/server.ts index 90101efe9d8ee..ea7dc52ef78ea 100644 --- a/examples/custom-server-hapi/server.js +++ b/examples/custom-server-hapi/server/server.ts @@ -1,6 +1,6 @@ -const next = require('next') -const Hapi = require('@hapi/hapi') -const { /* pathWrapper, */ nextHandlerWrapper } = require('./next-wrapper') +import next from 'next' +import Hapi from '@hapi/hapi' +import { nextHandlerWrapper } from './next-wrapper' const port = parseInt(process.env.PORT, 10) || 3000 const dev = process.env.NODE_ENV !== 'production' diff --git a/examples/with-mysql/tsconfig.json b/examples/custom-server-hapi/tsconfig.json similarity index 84% rename from examples/with-mysql/tsconfig.json rename to examples/custom-server-hapi/tsconfig.json index 4f4c33d494751..fe57f7dbf638a 100644 --- a/examples/with-mysql/tsconfig.json +++ b/examples/custom-server-hapi/tsconfig.json @@ -1,17 +1,14 @@ { "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/components/*": ["components/*"], - "@/lib/*": ["lib/*"] - }, "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": ".", "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, + "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", diff --git a/examples/custom-server-hapi/tsconfig.server.json b/examples/custom-server-hapi/tsconfig.server.json new file mode 100644 index 0000000000000..0cf1e05d407ae --- /dev/null +++ b/examples/custom-server-hapi/tsconfig.server.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "lib": ["es2019"], + "target": "es2019", + "isolatedModules": false, + "noEmit": false + }, + "include": ["server/**/*.ts"] +} diff --git a/examples/data-fetch/pages/preact-stars.js b/examples/data-fetch/pages/preact-stars.js index 0db33607dde37..9039129474eda 100644 --- a/examples/data-fetch/pages/preact-stars.js +++ b/examples/data-fetch/pages/preact-stars.js @@ -12,7 +12,7 @@ function PreactStars({ stars }) { } export async function getStaticProps() { - const res = await fetch('https://api.github.com/repos/developit/preact') + const res = await fetch('https://api.github.com/repos/preactjs/preact') const json = await res.json() return { diff --git a/examples/layout-component/pages/about.js b/examples/layout-component/pages/about.js index 59f76166cad82..feb320214f270 100644 --- a/examples/layout-component/pages/about.js +++ b/examples/layout-component/pages/about.js @@ -13,7 +13,7 @@ export default function About() {

When navigating between pages, we want to persist page state (input - values, scroll position, etc) for a Single-Page Application (SPA) + values, scroll position, etc.) for a Single-Page Application (SPA) experience.

diff --git a/examples/layout-component/pages/contact.js b/examples/layout-component/pages/contact.js index d2888593ec9fd..50167f2bec144 100644 --- a/examples/layout-component/pages/contact.js +++ b/examples/layout-component/pages/contact.js @@ -13,7 +13,7 @@ export default function Contact() {

When navigating between pages, we want to persist page state (input - values, scroll position, etc) for a Single-Page Application (SPA) + values, scroll position, etc.) for a Single-Page Application (SPA) experience.

diff --git a/examples/progressive-web-app/next-env.d.ts b/examples/progressive-web-app/next-env.d.ts new file mode 100644 index 0000000000000..4f11a03dc6cc3 --- /dev/null +++ b/examples/progressive-web-app/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/progressive-web-app/next.config.js b/examples/progressive-web-app/next.config.js index 778ea93311dec..a62a930ff3634 100644 --- a/examples/progressive-web-app/next.config.js +++ b/examples/progressive-web-app/next.config.js @@ -1,3 +1,4 @@ +/** @type {import('next').NextConfig} */ const withPWA = require('next-pwa') const runtimeCaching = require('next-pwa/cache') diff --git a/examples/progressive-web-app/package.json b/examples/progressive-web-app/package.json index c16f55ccc4985..1c8039c1130c0 100644 --- a/examples/progressive-web-app/package.json +++ b/examples/progressive-web-app/package.json @@ -10,5 +10,10 @@ "next-pwa": "^5.4.1", "react": "^17.0.2", "react-dom": "^17.0.2" + }, + "devDependencies": { + "@types/node": "17.0.4", + "@types/react": "17.0.38", + "typescript": "4.5.4" } } diff --git a/examples/progressive-web-app/pages/_app.js b/examples/progressive-web-app/pages/_app.tsx similarity index 90% rename from examples/progressive-web-app/pages/_app.js rename to examples/progressive-web-app/pages/_app.tsx index cca47aae9e44a..3601ddfa987de 100644 --- a/examples/progressive-web-app/pages/_app.js +++ b/examples/progressive-web-app/pages/_app.tsx @@ -1,7 +1,8 @@ import Head from 'next/head' import '../styles/globals.css' +import { AppProps } from 'next/app' -export default function MyApp({ Component, pageProps }) { +export default function MyApp({ Component, pageProps }: AppProps) { return ( <> diff --git a/examples/progressive-web-app/pages/api/hello.ts b/examples/progressive-web-app/pages/api/hello.ts new file mode 100644 index 0000000000000..a42f4634c2b13 --- /dev/null +++ b/examples/progressive-web-app/pages/api/hello.ts @@ -0,0 +1,8 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import { NextApiRequest, NextApiResponse } from 'next' + +const hello = (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).json({ name: 'John Doe' }) +} + +export default hello diff --git a/examples/progressive-web-app/pages/index.js b/examples/progressive-web-app/pages/index.tsx similarity index 100% rename from examples/progressive-web-app/pages/index.js rename to examples/progressive-web-app/pages/index.tsx diff --git a/examples/progressive-web-app/tsconfig.json b/examples/progressive-web-app/tsconfig.json new file mode 100644 index 0000000000000..99710e857874f --- /dev/null +++ b/examples/progressive-web-app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/examples/with-apollo/lib/apolloClient.js b/examples/with-apollo/lib/apolloClient.js index 554bcea87ded5..b6aa661ce2079 100644 --- a/examples/with-apollo/lib/apolloClient.js +++ b/examples/with-apollo/lib/apolloClient.js @@ -36,8 +36,8 @@ export function initializeApollo(initialState = null) { // Get existing cache, loaded during client side data fetching const existingCache = _apolloClient.extract() - // Merge the existing cache into data passed from getStaticProps/getServerSideProps - const data = merge(initialState, existingCache, { + // Merge the initialState from getStaticProps/getServerSideProps in the existing cache + const data = merge(existingCache, initialState, { // combine arrays using object equality (like in sets) arrayMerge: (destinationArray, sourceArray) => [ ...sourceArray, diff --git a/examples/with-docker-multi-env/.dockerignore b/examples/with-docker-multi-env/.dockerignore new file mode 100644 index 0000000000000..0d8280377a554 --- /dev/null +++ b/examples/with-docker-multi-env/.dockerignore @@ -0,0 +1,8 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +docker +.git diff --git a/examples/with-docker-multi-env/.env b/examples/with-docker-multi-env/.env new file mode 100644 index 0000000000000..1affd3c0a776c --- /dev/null +++ b/examples/with-docker-multi-env/.env @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost diff --git a/examples/with-docker-multi-env/.env.development.sample b/examples/with-docker-multi-env/.env.development.sample new file mode 100644 index 0000000000000..357551fe6e464 --- /dev/null +++ b/examples/with-docker-multi-env/.env.development.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-development.com diff --git a/examples/with-docker-multi-env/.env.production.sample b/examples/with-docker-multi-env/.env.production.sample new file mode 100644 index 0000000000000..a566eb6948ed4 --- /dev/null +++ b/examples/with-docker-multi-env/.env.production.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-production.com diff --git a/examples/with-docker-multi-env/.env.staging.sample b/examples/with-docker-multi-env/.env.staging.sample new file mode 100644 index 0000000000000..b1c8d9f9b19d4 --- /dev/null +++ b/examples/with-docker-multi-env/.env.staging.sample @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=https://api-staging.com diff --git a/examples/with-docker-multi-env/.gitignore b/examples/with-docker-multi-env/.gitignore new file mode 100644 index 0000000000000..10d8c913baeaa --- /dev/null +++ b/examples/with-docker-multi-env/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# 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 +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# lock files +yarn.lock +package-lock.json diff --git a/examples/with-docker-multi-env/Makefile b/examples/with-docker-multi-env/Makefile new file mode 100644 index 0000000000000..759e426b4a7a1 --- /dev/null +++ b/examples/with-docker-multi-env/Makefile @@ -0,0 +1,35 @@ +.PHONY: build-development +build-development: ## Build the development docker image. + docker compose -f docker/development/docker-compose.yml build + +.PHONY: start-development +start-development: ## Start the development docker container. + docker compose -f docker/development/docker-compose.yml up -d + +.PHONY: stop-development +stop-development: ## Stop the development docker container. + docker compose -f docker/development/docker-compose.yml down + +.PHONY: build-staging +build-staging: ## Build the staging docker image. + docker compose -f docker/staging/docker-compose.yml build + +.PHONY: start-staging +start-staging: ## Start the staging docker container. + docker compose -f docker/staging/docker-compose.yml up -d + +.PHONY: stop-staging +stop-staging: ## Stop the staging docker container. + docker compose -f docker/staging/docker-compose.yml down + +.PHONY: build-production +build-production: ## Build the production docker image. + docker compose -f docker/production/docker-compose.yml build + +.PHONY: start-production +start-production: ## Start the production docker container. + docker compose -f docker/production/docker-compose.yml up -d + +.PHONY: stop-production +stop-production: ## Stop the production docker container. + docker compose -f docker/production/docker-compose.yml down diff --git a/examples/with-docker-multi-env/README.md b/examples/with-docker-multi-env/README.md new file mode 100644 index 0000000000000..2799adfd136ae --- /dev/null +++ b/examples/with-docker-multi-env/README.md @@ -0,0 +1,62 @@ +# With Docker - Multiple Deployment Environments + +This examples shows how to use Docker with Next.js and deploy to multiple environment with different env values. Based on the [deployment documentation](https://nextjs.org/docs/deployment#docker-image). + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example with-docker-multi-env nextjs-docker-multi-env +# or +yarn create next-app --example with-docker-multi-env nextjs-docker-multi-env +``` + +Enter the values in the `.env.development.sample`, `.env.staging.sample`, `.env.production.sample` files to be used for each environments. + +## Using Docker and Makefile + +### Development environment - for doing testing + +``` +make build-development +make start-development +``` + +Open http://localhost:3001 + +### Staging environment - for doing UAT testing + +``` +make build-staging +make start-staging +``` + +Open http://localhost:3002 + +### Production environment - for users + +``` +make build-production +make start-production +``` + +Open http://localhost:3003 + +## Running Locally + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[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`. + +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. diff --git a/examples/with-docker-multi-env/docker/development/Dockerfile b/examples/with-docker-multi-env/docker/development/Dockerfile new file mode 100644 index 0000000000000..d5cce6d2d12c1 --- /dev/null +++ b/examples/with-docker-multi-env/docker/development/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-alpine 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 + +WORKDIR /app +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.development.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json + +# 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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/development/docker-compose.yml b/examples/with-docker-multi-env/docker/development/docker-compose.yml new file mode 100644 index 0000000000000..cdf32d5e953d3 --- /dev/null +++ b/examples/with-docker-multi-env/docker/development/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-development: + build: + context: ../../ + dockerfile: docker/development/Dockerfile + image: with-docker-multi-env-development + ports: + - '3001:3000' diff --git a/examples/with-docker-multi-env/docker/production/Dockerfile b/examples/with-docker-multi-env/docker/production/Dockerfile new file mode 100644 index 0000000000000..d26bdb2fef663 --- /dev/null +++ b/examples/with-docker-multi-env/docker/production/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-alpine 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 + +WORKDIR /app +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.production.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json + +# 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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/production/docker-compose.yml b/examples/with-docker-multi-env/docker/production/docker-compose.yml new file mode 100644 index 0000000000000..b903f1ddd8353 --- /dev/null +++ b/examples/with-docker-multi-env/docker/production/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-production: + build: + context: ../../ + dockerfile: docker/production/Dockerfile + image: with-docker-multi-env-production + ports: + - '3003:3000' diff --git a/examples/with-docker-multi-env/docker/staging/Dockerfile b/examples/with-docker-multi-env/docker/staging/Dockerfile new file mode 100644 index 0000000000000..beb44b1b39622 --- /dev/null +++ b/examples/with-docker-multi-env/docker/staging/Dockerfile @@ -0,0 +1,45 @@ +# 1. Install dependencies only when needed +FROM node:16-alpine 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 + +WORKDIR /app +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 2. Rebuild the source code only when needed +FROM node:16-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +COPY .env.staging.sample .env.production +RUN yarn build + +# 3. Production image, copy all the files and run next +FROM node:16-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json + +# 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 + + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/examples/with-docker-multi-env/docker/staging/docker-compose.yml b/examples/with-docker-multi-env/docker/staging/docker-compose.yml new file mode 100644 index 0000000000000..231ef1e61a718 --- /dev/null +++ b/examples/with-docker-multi-env/docker/staging/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + with-docker-multi-env-staging: + build: + context: ../../ + dockerfile: docker/staging/Dockerfile + image: with-docker-multi-env-staging + ports: + - '3002:3000' diff --git a/examples/with-docker-multi-env/next.config.js b/examples/with-docker-multi-env/next.config.js new file mode 100644 index 0000000000000..0568ecc9e5401 --- /dev/null +++ b/examples/with-docker-multi-env/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + outputStandalone: true, + }, +} diff --git a/examples/with-docker-multi-env/package.json b/examples/with-docker-multi-env/package.json new file mode 100644 index 0000000000000..f9170ae254fa3 --- /dev/null +++ b/examples/with-docker-multi-env/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } +} diff --git a/examples/with-docker-multi-env/pages/_app.js b/examples/with-docker-multi-env/pages/_app.js new file mode 100644 index 0000000000000..1e1cec92425c8 --- /dev/null +++ b/examples/with-docker-multi-env/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/progressive-web-app/pages/api/hello.js b/examples/with-docker-multi-env/pages/api/hello.js similarity index 70% rename from examples/progressive-web-app/pages/api/hello.js rename to examples/with-docker-multi-env/pages/api/hello.js index 1424b47343d64..441a0a1006e5a 100644 --- a/examples/progressive-web-app/pages/api/hello.js +++ b/examples/with-docker-multi-env/pages/api/hello.js @@ -1,7 +1,5 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -const hello = (req, res) => { +export default function hello(req, res) { res.status(200).json({ name: 'John Doe' }) } - -export default hello diff --git a/examples/with-docker-multi-env/pages/index.js b/examples/with-docker-multi-env/pages/index.js new file mode 100644 index 0000000000000..51e835d5e8720 --- /dev/null +++ b/examples/with-docker-multi-env/pages/index.js @@ -0,0 +1,66 @@ +import Head from 'next/head' +import styles from '../styles/Home.module.css' + +export default function Home() { + return ( +

+ ) +} diff --git a/examples/with-docker-multi-env/public/favicon.ico b/examples/with-docker-multi-env/public/favicon.ico new file mode 100644 index 0000000000000..4965832f2c9b0 Binary files /dev/null and b/examples/with-docker-multi-env/public/favicon.ico differ diff --git a/examples/with-docker-multi-env/public/vercel.svg b/examples/with-docker-multi-env/public/vercel.svg new file mode 100644 index 0000000000000..fbf0e25a651c2 --- /dev/null +++ b/examples/with-docker-multi-env/public/vercel.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/examples/with-docker-multi-env/styles/Home.module.css b/examples/with-docker-multi-env/styles/Home.module.css new file mode 100644 index 0000000000000..42e7e6009497f --- /dev/null +++ b/examples/with-docker-multi-env/styles/Home.module.css @@ -0,0 +1,122 @@ +.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-multi-env/styles/globals.css b/examples/with-docker-multi-env/styles/globals.css new file mode 100644 index 0000000000000..e5e2dcc23baf1 --- /dev/null +++ b/examples/with-docker-multi-env/styles/globals.css @@ -0,0 +1,16 @@ +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/.dockerignore b/examples/with-docker/.dockerignore index d862f96f1b93e..c5500558baf2e 100644 --- a/examples/with-docker/.dockerignore +++ b/examples/with-docker/.dockerignore @@ -3,4 +3,5 @@ Dockerfile node_modules npm-debug.log README.md -.next \ No newline at end of file +.next +.git diff --git a/examples/with-docker/Dockerfile b/examples/with-docker/Dockerfile index 57635340bb16f..67bec4d370921 100644 --- a/examples/with-docker/Dockerfile +++ b/examples/with-docker/Dockerfile @@ -7,24 +7,35 @@ COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile # If using npm with a `package-lock.json` comment out above and use below instead -# COPY package.json package-lock.json / -# RUN npm install +# COPY package.json package-lock.json ./ +# RUN npm ci # Rebuild the source code only when needed FROM node:16-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . + +# 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 yarn build +# If using npm comment out above and use below instead +# RUN npm run build + # Production image, copy all the files and run next FROM node:16-alpine AS runner WORKDIR /app ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nextjs -u 1001 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs # You only need to copy next.config.js if you are NOT using the default configuration # COPY --from=builder /app/next.config.js ./ @@ -42,9 +53,4 @@ EXPOSE 3000 ENV PORT 3000 -# 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. -# ENV NEXT_TELEMETRY_DISABLED 1 - CMD ["node", "server.js"] diff --git a/examples/with-docker/README.md b/examples/with-docker/README.md index 868cb6ce5091a..9ece6c46559f9 100644 --- a/examples/with-docker/README.md +++ b/examples/with-docker/README.md @@ -20,9 +20,23 @@ yarn create next-app --example with-docker nextjs-docker You can view your images created with `docker images`. -## Deploying to Google Cloud Run +### In existing projects + +To add support for Docker to an existing project, just copy the `Dockerfile` into the root of the project and add the following to the `next.config.js` file: + +```js +// next.config.js +module.exports = { + // ... rest of the configuration. + experimental: { + outputStandalone: true, + }, +} +``` -The `start` script in `package.json` has been modified to accept a `PORT` environment variable (for compatibility with Google Cloud Run). +This will build the project as a standalone app inside the Docker image. + +## 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. 1. Run `gcloud auth login` to log in to your account. diff --git a/examples/with-docker/package.json b/examples/with-docker/package.json index ef53005003110..10aec52800331 100644 --- a/examples/with-docker/package.json +++ b/examples/with-docker/package.json @@ -2,8 +2,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", - "start": "next start -p ${PORT:=3000}" + "build": "next build" }, "dependencies": { "next": "latest", diff --git a/examples/with-firebase/context/userContext.js b/examples/with-firebase/context/userContext.js index 6b1a010cebef6..9a7d4be53b0fb 100644 --- a/examples/with-firebase/context/userContext.js +++ b/examples/with-firebase/context/userContext.js @@ -1,5 +1,6 @@ import { useState, useEffect, createContext, useContext } from 'react' -import firebase from '../firebase/clientApp' +import { createFirebaseApp } from '../firebase/clientApp' +import { getAuth, onAuthStateChanged } from 'firebase/auth' export const UserContext = createContext() @@ -9,7 +10,9 @@ export default function UserContextComp({ children }) { useEffect(() => { // Listen authenticated user - const unsubscriber = firebase.auth().onAuthStateChanged(async (user) => { + const app = createFirebaseApp() + const auth = getAuth(app) + const unsubscriber = onAuthStateChanged(auth, async (user) => { try { if (user) { // User is signed in. diff --git a/examples/with-firebase/firebase/clientApp.js b/examples/with-firebase/firebase/clientApp.js index 68c829cfd9741..74f9c8a9aa7d6 100644 --- a/examples/with-firebase/firebase/clientApp.js +++ b/examples/with-firebase/firebase/clientApp.js @@ -1,30 +1,26 @@ -import firebase from 'firebase/app' -import 'firebase/auth' // If you need it -import 'firebase/firestore' // If you need it -import 'firebase/storage' // If you need it -import 'firebase/analytics' // If you need it -import 'firebase/performance' // If you need it - -const clientCredentials = { - apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, - authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, - projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, - storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, -} +import { initializeApp, getApps } from 'firebase/app' +import { getAnalytics } from 'firebase/analytics' +export const createFirebaseApp = () => { + const clientCredentials = { + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, + } -if (!firebase.apps.length) { - firebase.initializeApp(clientCredentials) - // Check that `window` is in scope for the analytics module! - if (typeof window !== 'undefined') { - // Enable analytics. https://firebase.google.com/docs/analytics/get-started - if ('measurementId' in clientCredentials) { - firebase.analytics() - firebase.performance() + if (getApps().length <= 0) { + const app = initializeApp(clientCredentials) + // Check that `window` is in scope for the analytics module! + if (typeof window !== 'undefined') { + // Enable analytics. https://firebase.google.com/docs/analytics/get-started + if ('measurementId' in clientCredentials) { + getAnalytics() + } } + return app } } - -export default firebase diff --git a/examples/with-firebase/package.json b/examples/with-firebase/package.json index a4c22ebd170b8..607bc4b02e105 100644 --- a/examples/with-firebase/package.json +++ b/examples/with-firebase/package.json @@ -6,8 +6,8 @@ "start": "next start" }, "dependencies": { - "firebase": "7.17.0", - "firebase-admin": "9.0.0", + "firebase": "9.1.1", + "firebase-admin": "9.12.0", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" diff --git a/examples/with-firebase/pages/index.js b/examples/with-firebase/pages/index.js index 534e2f4874996..4993df6402319 100644 --- a/examples/with-firebase/pages/index.js +++ b/examples/with-firebase/pages/index.js @@ -1,8 +1,8 @@ +import { getFirestore, setDoc, doc } from 'firebase/firestore' import Head from 'next/head' import Link from 'next/link' import { useEffect } from 'react' import { useUser } from '../context/userContext' -import firebase from '../firebase/clientApp' export default function Home() { // Our custom hook to get context values @@ -16,12 +16,12 @@ export default function Home() { console.log(user) } // You also have your firebase app initialized - console.log(firebase) }, [loadingUser, user]) const createUser = async () => { - const db = firebase.firestore() - await db.collection('profile').doc(profile.username).set(profile) + const db = getFirestore() + await setDoc(doc(db, 'profile', profile.username), profile) + alert('User created!!') } diff --git a/examples/with-iron-session/pages/index.tsx b/examples/with-iron-session/pages/index.tsx index a76ba96a47d72..ae31b6b5afe91 100644 --- a/examples/with-iron-session/pages/index.tsx +++ b/examples/with-iron-session/pages/index.tsx @@ -24,7 +24,7 @@ export default function Home() { 1. no `getInitialProps` to ensure every page is static
2. `useUser` hook together with ` - swr` for data fetching + swr` for data fetching

Features

diff --git a/examples/with-jest-babel/__mocks__/fileMock.js b/examples/with-jest-babel/__mocks__/fileMock.js index af027da050737..4a271a81f5da0 100644 --- a/examples/with-jest-babel/__mocks__/fileMock.js +++ b/examples/with-jest-babel/__mocks__/fileMock.js @@ -1,3 +1,8 @@ // Read more at "Handling stylesheets and image imports" on https://nextjs.org/docs/testing -module.exports = 'test-file-stub' +module.exports = { + src: '/img.jpg', + height: 24, + width: 24, + blurDataURL: 'data:image/png;base64,imagedata', +} diff --git a/examples/with-jest-babel/jest.config.js b/examples/with-jest-babel/jest.config.js index 5a7adbd4d5f9c..6351201bf42ab 100644 --- a/examples/with-jest-babel/jest.config.js +++ b/examples/with-jest-babel/jest.config.js @@ -16,7 +16,7 @@ module.exports = { // Handle image imports // https://jestjs.io/docs/webpack#handling-static-assets - '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`, + '^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$': `/__mocks__/fileMock.js`, // Handle module aliases '^@/components/(.*)$': '/components/$1', diff --git a/examples/with-material-ui/README.md b/examples/with-material-ui/README.md index 340394d4cea73..48ed4511411d8 100644 --- a/examples/with-material-ui/README.md +++ b/examples/with-material-ui/README.md @@ -1,3 +1,3 @@ -# material-ui example +# Material UI example -This example have been moved here: [mui-org/material-ui](https://github.com/mui-org/material-ui/blob/master/examples/nextjs) +This example have been moved here: [mui/material-ui](https://github.com/mui/material-ui/tree/master/examples/nextjs) diff --git a/examples/with-mysql/.env.example b/examples/with-mysql/.env.example new file mode 100644 index 0000000000000..9205e1aeee0b4 --- /dev/null +++ b/examples/with-mysql/.env.example @@ -0,0 +1 @@ +DATABASE_URL=mysql://:@/?sslaccept=strict diff --git a/examples/with-mysql/.env.local.example b/examples/with-mysql/.env.local.example deleted file mode 100644 index 2062d45cde42c..0000000000000 --- a/examples/with-mysql/.env.local.example +++ /dev/null @@ -1,7 +0,0 @@ -# Example .env.local file for MySQL Database credentials - -MYSQL_HOST= -MYSQL_DATABASE= -MYSQL_USERNAME= -MYSQL_PASSWORD= -MYSQL_PORT= diff --git a/examples/with-mysql/README.md b/examples/with-mysql/README.md index d8d75d5e4578d..a853b6584dea6 100644 --- a/examples/with-mysql/README.md +++ b/examples/with-mysql/README.md @@ -1,82 +1,102 @@ -# MySQL Example +# Next.js + MySQL -This is an example of using [MySQL](https://www.mysql.com/) in a Next.js project. +This is a [Next.js](https://nextjs.org/) project that uses [Prisma](https://www.prisma.io/) to connect to a [PlanetScale](https://planetscale.com/) MySQL database and [Tailwind CSS](https://tailwindcss.com/) for styling. ## Demo -### [https://next-mysql.vercel.app](https://next-mysql.vercel.app/) +https://next-mysql.vercel.app -## Deploy your own +## Prerequisites + +- [Node.js](https://nodejs.org/en/download/) +- [PlanetScale CLI](https://github.com/planetscale/cli) +- Authenticate the CLI with the following command: + +```sh +pscale auth login +``` + +## Set up the database + +Create a new database with the following command: -Once you have access to [the environment variables you'll need](#step-5-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): +```sh +pscale database create +``` -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-mysql&project-name=nextjs-mysql&repository-name=nextjs-mysql&env=MYSQL_HOST,MYSQL_DATABASE,MYSQL_USERNAME,MYSQL_PASSWORD&envDescription=Required%20to%20connect%20the%20app%20with%20MySQL&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-mysql%23step-2-set-up-environment-variables&demo-title=Next.js%20%2B%20MySQL%20Demo&demo-description=A%20simple%20app%20demonstrating%20Next.js%20and%20MySQL%20&demo-url=https%3A%2F%2Fnext-mysql.vercel.app%2F) +> A branch, `main`, was automatically created when you created your database, so you can use that for `BRANCH_NAME` in the steps below. -## How to use +## Set up the starter Next.js app Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-mysql next-mysql-app +npx create-next-app --example with-mysql nextjs-mysql # or -yarn create next-app --example with-mysql next-mysql-app +yarn create next-app --example with-mysql nextjs-mysql ``` -## Configuration - -### Step 1. Set up a MySQL database +Next, you'll need to create a database username and password through the CLI to connect to your application. If you'd prefer to use the dashboard for this step, you can find those instructions in the [Connection Strings documentation](https://docs.planetscale.com/concepts/connection-strings#creating-a-password) and then come back here to finish setup. -Set up a MySQL server either locally or any cloud provider. +First, create your `.env` file by renaming the `.env.example` file to `.env`: -### Step 2. Set up environment variables +```sh +mv .env.example .env +``` -Copy the `env.local.example` file in this directory to `.env.local` (which will be ignored by Git): +Next, using the PlanetScale CLI, create a new username and password for the branch of your database: -```bash -cp .env.local.example .env.local +```sh +pscale password create ``` -Set each variable on `.env.local`: - -- `MYSQL_HOST` - Your MySQL host URL. -- `MYSQL_DATABASE` - The name of the MySQL database you want to use. -- `MYSQL_USERNAME` - The name of the MySQL user with access to database. -- `MYSQL_PASSWORD` - The passowrd of the MySQL user. +> The `PASSWORD_NAME` value represents the name of the username and password being generated. You can have multiple credentials for a branch, so this gives you a way to categorize them. To manage your passwords in the dashboard, go to your database overview page, click "Settings", and then click "Passwords". -### Step 3. Run migration script +Take note of the values returned to you, as you won't be able to see this password again. -You'll need to run a migration to create the necessary table for the example. +```text +Password production-password was successfully created. +Please save the values below as they will not be shown again -```bash -npm run migrate -# or -yarn migrate + NAME USERNAME ACCESS HOST URL ROLE PLAIN TEXT + --------------------- -------------- ----------------------------------- ------------------ ------------------------------------------------------- + production-password xxxxxxxxxxxxx xxxxxx.us-east-2.psdb.cloud Can Read & Write pscale_pw_xxxxxxx ``` -### Step 4. Run Next.js in development mode +You'll use these properties to construct your connection string, which will be the value for `DATABASE_URL` in your `.env` file. Update the `DATABASE_URL` property with your connection string in the following format: -```bash -npm install -npm run dev -# or -yarn install -yarn dev +```text +mysql://:@/?sslaccept=strict ``` -Your app should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). +Push the database schema to your PlanetScale database using Prisma. + +`npx prisma db push` + +Run the seed script to populate your database with `Product` and `Category` data. + +`npm run seed` -## Deploy on Vercel +## Run the App -You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +Run the app with following command: -#### Deploy Your Local Project +`npm run dev` -To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). +Open your browser at [localhost:3000](localhost:3000) to see the running application. + +## Deploy your own + +After you've got your application running locally, it's time to deploy it. To do so, you'll need to promote your database branch (`main` by default) to be the production branch ([read the branching documentation for more information](https://docs.planetscale.com/concepts/branching)). + +```sh +pscale branch promote +``` -**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. +Now that your branch has been promoted to production, you can either use the existing password you generated earlier for running locally or create a new password. Regardless, you'll need a password in the deployment steps below. -#### Deploy from Our Template +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): -Alternatively, you can deploy using our template by clicking on the Deploy button below. +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-mysql&project-name=with-mysql&repository-name=with-mysql&env=DATABASE_URL) -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-mysql&project-name=nextjs-mysql&repository-name=nextjs-mysql&env=MYSQL_HOST,MYSQL_DATABASE,MYSQL_USERNAME,MYSQL_PASSWORD&envDescription=Required%20to%20connect%20the%20app%20with%20MySQL&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-mysql%23step-2-set-up-environment-variables&demo-title=Next.js%20%2B%20MySQL%20Demo&demo-description=A%20simple%20app%20demonstrating%20Next.js%20and%20MySQL%20&demo-url=https%3A%2F%2Fnext-mysql.vercel.app%2F) +> Make sure to update the `DATABASE_URL` variable during this setup process. diff --git a/examples/with-mysql/components/Product.js b/examples/with-mysql/components/Product.js new file mode 100644 index 0000000000000..16f32b38399bc --- /dev/null +++ b/examples/with-mysql/components/Product.js @@ -0,0 +1,31 @@ +import Image from 'next/image' + +export default function Product({ product }) { + const { name, description, price, image, category } = product + + return ( +
+ {name} +
+
{name}
+

{description}

+

${price}

+
+
+ + {category.name} + +
+
+ ) +} diff --git a/examples/with-mysql/components/button-link/index.tsx b/examples/with-mysql/components/button-link/index.tsx deleted file mode 100644 index 88e46586e476e..0000000000000 --- a/examples/with-mysql/components/button-link/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import Link from 'next/link' -import cn from 'clsx' - -function ButtonLink({ href = '/', className = '', children }) { - return ( - - - {children} - - - ) -} - -export default ButtonLink diff --git a/examples/with-mysql/components/button/index.tsx b/examples/with-mysql/components/button/index.tsx deleted file mode 100644 index ca8714a1b9611..0000000000000 --- a/examples/with-mysql/components/button/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import cn from 'clsx' - -function Button({ - onClick = console.log, - className = '', - children = null, - type = null, - disabled = false, -}) { - return ( - - ) -} - -export default Button diff --git a/examples/with-mysql/components/container/index.tsx b/examples/with-mysql/components/container/index.tsx deleted file mode 100644 index 4c5b421a04c0e..0000000000000 --- a/examples/with-mysql/components/container/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function Container({ className = '', children }) { - return
{children}
-} - -export default Container diff --git a/examples/with-mysql/components/edit-entry-form/index.tsx b/examples/with-mysql/components/edit-entry-form/index.tsx deleted file mode 100644 index f84307d00fea6..0000000000000 --- a/examples/with-mysql/components/edit-entry-form/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useState, useEffect } from 'react' -import Router, { useRouter } from 'next/router' - -import Button from '../button' - -export default function EntryForm() { - const [_title, setTitle] = useState('') - const [_content, setContent] = useState('') - const [submitting, setSubmitting] = useState(false) - const router = useRouter() - const { id, title, content } = router.query - - useEffect(() => { - if (typeof title === 'string') { - setTitle(title) - } - if (typeof content === 'string') { - setContent(content) - } - }, [title, content]) - - async function submitHandler(e) { - e.preventDefault() - setSubmitting(true) - try { - const res = await fetch('/api/edit-entry', { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id, - title: _title, - content: _content, - }), - }) - const json = await res.json() - setSubmitting(false) - if (!res.ok) throw Error(json.message) - Router.push('/') - } catch (e) { - throw Error(e.message) - } - } - - return ( -
-
- - setTitle(e.target.value)} - /> -
-
- -