diff --git a/.alexrc b/.alexrc index 157d1da8cca5302..47862f2c80a10d5 100644 --- a/.alexrc +++ b/.alexrc @@ -16,6 +16,7 @@ "hook", "hooks", "host-hostess", - "invalid" + "invalid", + "remains" ] } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 569ca335626aefe..6e0fcfef8433c92 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,9 +2,9 @@ # https://help.github.com/en/articles/about-code-owners * @timneutkens @ijjk @shuding @huozhi -/.github/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia -/docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @lfades @molebox -/examples/ @timneutkens @ijjk @shuding @leerob @lfades @steven-tey +/.github/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @balazsorban44 +/docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @balazsorban44 +/examples/ @timneutkens @ijjk @shuding @leerob @steven-tey @balazsorban44 # SWC Build (@padmaia) diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 7ae30a4ff2bb6af..11d4c02555a88c3 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -68,7 +68,7 @@ jobs: id: cache-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} lint: runs-on: ubuntu-latest @@ -84,7 +84,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - run: ./scripts/check-manifests.js - run: yarn lint @@ -119,7 +119,7 @@ jobs: if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - name: Check if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -157,7 +157,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - run: rm -rf .git && mv .git-bak .git if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -190,7 +190,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -230,7 +230,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -281,7 +281,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -335,7 +335,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -386,7 +386,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -440,7 +440,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -481,7 +481,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -525,7 +525,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -566,7 +566,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -611,7 +611,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: pnpm/action-setup@v2.2.1 with: @@ -659,7 +659,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -709,7 +709,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: @@ -751,7 +751,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -795,7 +795,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -828,7 +828,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: @@ -864,7 +864,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 with: @@ -894,7 +894,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - uses: actions/download-artifact@v2 with: @@ -1284,7 +1284,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - name: Setup node uses: actions/setup-node@v2 @@ -1336,7 +1336,7 @@ jobs: id: restore-build with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - name: Setup node if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -1413,7 +1413,7 @@ jobs: if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: path: ./* - key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + key: ${{ github.sha }}-${{ github.run_number }} - name: Set Git Short sha Env if: ${{needs.build.outputs.docsChange != 'docs only change'}} diff --git a/docs/advanced-features/react-18/server-components.md b/docs/advanced-features/react-18/server-components.md index 265dd4cb22e4723..4426f7eda2e7c0d 100644 --- a/docs/advanced-features/react-18/server-components.md +++ b/docs/advanced-features/react-18/server-components.md @@ -32,7 +32,11 @@ To run a component on the server, append `.server.js` to the end of the filename 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: +Server components can import server components and client components. + +Client components **cannot** import server components. + +Components without a `server` or `client` extension will be treated as shared components and can be imported by server components and client components. For example: ```jsx // pages/home.server.js diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index 9c0e78dfd784e0d..c5ae4ffbc56812d 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -113,8 +113,6 @@ export default function Page() { } ``` -> **Note:** When navigating to the same page in Next.js, the page's state **will not** be reset by default, as the top-level React component is the same. You can manually ensure the state is updated using `useEffect`. - Redirecting the user to `pages/login.js`, useful for pages behind [authentication](/docs/authentication): ```jsx @@ -138,6 +136,58 @@ export default function Page() { } ``` +#### Resetting state after navigation + +When navigating to the same page in Next.js, the page's state **will not** be reset by default as react does not unmount unless the parent component has changed. + +```jsx +// pages/[slug].js +import Link from 'next/link' +import { useState } from 'react' +import { useRouter } from 'next/router' + +export default function Page(props) { + const router = useRouter() + const [count, setCount] = useState(0) + return ( +
+ ) +} +``` + +In the above example, navigating between `/one` and `/two` **will not** reset the count . The `useState` is maintained between renders because the top-level React component, `Page`, is the same. + +If you do not want this behavior, you have a couple of options: + +1. Manually ensure each state is updated using `useEffect`. In the above example, that could look like: + +```jsx +useEffect(() => { + setCount(0) +}, [router.query.slug]) +``` + +2. Use a React `key` to [tell React to remount the component](https://reactjs.org/docs/lists-and-keys.html#keys). To do this for all pages, you can use a custom app: + +```jsx +// pages/_app.js +import { useRouter } from 'next/router' + +export default function MyApp({ Component, pageProps }) { + const router = useRouter() + returnHello World
+test
- <_JSXStyle id={"713499aa363d6373"}>{"p.jsx-713499aa363d6373 a.jsx-713499aa363d6373 span.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373 span{background:blue}p.jsx-713499aa363d6373 a[title=\"'w ' ' t'\"].jsx-713499aa363d6373{margin:auto}p.jsx-713499aa363d6373 span:not(.test){color:green}p.jsx-713499aa363d6373,h1.jsx-713499aa363d6373{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;-moz-animation:hahaha 3s ease forwards infinite;-o-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;-moz-animation-name:hahaha;-o-animation-name:hahaha;animation-name:hahaha;-webkit-animation-delay:100ms;-moz-animation-delay:100ms;-o-animation-delay:100ms;animation-delay:100ms}p.jsx-713499aa363d6373{-webkit-animation:hahaha 1s,hehehe 2s;-moz-animation:hahaha 1s,hehehe 2s;-o-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-713499aa363d6373:hover{color:red}p.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover{color:red}.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover p.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373+a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373~a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373>a.jsx-713499aa363d6373{color:red}@keyframes hahaha{from{top:0}to{top:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-713499aa363d6373{color:red}}.test.jsx-713499aa363d6373{display:block}.inline-flex.jsx-713499aa363d6373{display:-webkit-inline-box;display:-webkit-inline-flex;display:-moz-inline-box;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-713499aa363d6373{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.test.jsx-713499aa363d6373{-webkit-box-shadow:0 0 10px black,inset 0 0 5px black;-moz-box-shadow:0 0 10px black,inset 0 0 5px black;box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=\",\"].jsx-713499aa363d6373{display:inline-block}.test.is-status.jsx-713499aa363d6373 .test.jsx-713499aa363d6373{color:red}.a-selector.jsx-713499aa363d6373:hover,.a-selector.jsx-713499aa363d6373:focus{outline:none}"} + <_JSXStyle id={"713499aa363d6373"}>{"p.jsx-713499aa363d6373 a.jsx-713499aa363d6373 span.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373 span{background:blue}p.jsx-713499aa363d6373 a[title=\"'w ' ' t'\"].jsx-713499aa363d6373{margin:auto}p.jsx-713499aa363d6373 span:not(.test){color:green}p.jsx-713499aa363d6373,h1.jsx-713499aa363d6373{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;-moz-animation:hahaha 3s ease forwards infinite;-o-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;-moz-animation-name:hahaha;-o-animation-name:hahaha;animation-name:hahaha;-webkit-animation-delay:100ms;-moz-animation-delay:100ms;-o-animation-delay:100ms;animation-delay:100ms}p.jsx-713499aa363d6373{-webkit-animation:hahaha 1s,hehehe 2s;-moz-animation:hahaha 1s,hehehe 2s;-o-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-713499aa363d6373:hover{color:red}p.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover{color:red}.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover p.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373+a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373~a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373>a.jsx-713499aa363d6373{color:red}@-webkit-keyframes hahaha{from{top:0}to{top:100}}@-moz-keyframes hahaha{from{top:0}to{top:100}}@-o-keyframes hahaha{from{top:0}to{top:100}}@keyframes hahaha{from{top:0}to{top:100}}@-webkit-keyframes hehehe{from{left:0}to{left:100}}@-moz-keyframes hehehe{from{left:0}to{left:100}}@-o-keyframes hehehe{from{left:0}to{left:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-713499aa363d6373{color:red}}.test.jsx-713499aa363d6373{display:block}.inline-flex.jsx-713499aa363d6373{display:-webkit-inline-box;display:-webkit-inline-flex;display:-moz-inline-box;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-713499aa363d6373{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.test.jsx-713499aa363d6373{-webkit-box-shadow:0 0 10px black,inset 0 0 5px black;-moz-box-shadow:0 0 10px black,inset 0 0 5px black;box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=\",\"].jsx-713499aa363d6373{display:inline-block}.test.is-status.jsx-713499aa363d6373 .test.jsx-713499aa363d6373{color:red}.a-selector.jsx-713499aa363d6373:hover,.a-selector.jsx-713499aa363d6373:focus{outline:none}"}test
- <_JSXStyle id={"768337a97aceabd1"}>{'html.jsx-768337a97aceabd1{background-image:-webkit-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:-moz-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:-o-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:linear-gradient(0deg,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg)}p{color:blue}p{color:blue}p,a.jsx-768337a97aceabd1{color:blue}.foo+a{color:red}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif}p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1{color:red}*.jsx-768337a97aceabd1{color:blue}[href="woot"].jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 a.jsx-768337a97aceabd1 span.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 span{background:blue}p.jsx-768337a97aceabd1 a[title="\'w \' \' t\'"].jsx-768337a97aceabd1{margin:auto}p.jsx-768337a97aceabd1 span:not(.test){color:green}p.jsx-768337a97aceabd1,h1.jsx-768337a97aceabd1{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;-moz-animation:hahaha 3s ease forwards infinite;-o-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;-moz-animation-name:hahaha;-o-animation-name:hahaha;animation-name:hahaha;-webkit-animation-delay:100ms;-moz-animation-delay:100ms;-o-animation-delay:100ms;animation-delay:100ms}p.jsx-768337a97aceabd1{-webkit-animation:hahaha 1s,hehehe 2s;-moz-animation:hahaha 1s,hehehe 2s;-o-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-768337a97aceabd1:hover{color:red}p.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover{color:red}.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1+a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1~a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1>a.jsx-768337a97aceabd1{color:red}@keyframes hahaha{from{top:0}to{top:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-768337a97aceabd1{color:red}}.test.jsx-768337a97aceabd1{display:block}.inline-flex.jsx-768337a97aceabd1{display:-webkit-inline-box;display:-webkit-inline-flex;display:-moz-inline-box;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-768337a97aceabd1{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.test.jsx-768337a97aceabd1{-webkit-box-shadow:0 0 10px black,inset 0 0 5px black;-moz-box-shadow:0 0 10px black,inset 0 0 5px black;box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=","].jsx-768337a97aceabd1{display:inline-block}.test.is-status.jsx-768337a97aceabd1 .test.jsx-768337a97aceabd1{color:red}.a-selector.jsx-768337a97aceabd1:hover,.a-selector.jsx-768337a97aceabd1:focus{outline:none}@media(min-width:1px)and (max-width:768px){[class*="grid__col--"].jsx-768337a97aceabd1{margin-top:12px;margin-bottom:12px}}@media(max-width:64em){.test.jsx-768337a97aceabd1{margin-bottom:1em}@supports(-moz-appearance:none)and (display:contents){.test.jsx-768337a97aceabd1{margin-bottom:2rem}}}'} + <_JSXStyle id={"768337a97aceabd1"}>{'html.jsx-768337a97aceabd1{background-image:-webkit-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:-moz-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:-o-linear-gradient(bottom,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg);background-image:linear-gradient(0deg,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg)}p{color:blue}p{color:blue}p,a.jsx-768337a97aceabd1{color:blue}.foo+a{color:red}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif}p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1{color:red}*.jsx-768337a97aceabd1{color:blue}[href="woot"].jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 a.jsx-768337a97aceabd1 span.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 span{background:blue}p.jsx-768337a97aceabd1 a[title="\'w \' \' t\'"].jsx-768337a97aceabd1{margin:auto}p.jsx-768337a97aceabd1 span:not(.test){color:green}p.jsx-768337a97aceabd1,h1.jsx-768337a97aceabd1{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;-moz-animation:hahaha 3s ease forwards infinite;-o-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;-moz-animation-name:hahaha;-o-animation-name:hahaha;animation-name:hahaha;-webkit-animation-delay:100ms;-moz-animation-delay:100ms;-o-animation-delay:100ms;animation-delay:100ms}p.jsx-768337a97aceabd1{-webkit-animation:hahaha 1s,hehehe 2s;-moz-animation:hahaha 1s,hehehe 2s;-o-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-768337a97aceabd1:hover{color:red}p.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover{color:red}.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1+a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1~a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1>a.jsx-768337a97aceabd1{color:red}@-webkit-keyframes hahaha{from{top:0}to{top:100}}@-moz-keyframes hahaha{from{top:0}to{top:100}}@-o-keyframes hahaha{from{top:0}to{top:100}}@keyframes hahaha{from{top:0}to{top:100}}@-webkit-keyframes hehehe{from{left:0}to{left:100}}@-moz-keyframes hehehe{from{left:0}to{left:100}}@-o-keyframes hehehe{from{left:0}to{left:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-768337a97aceabd1{color:red}}.test.jsx-768337a97aceabd1{display:block}.inline-flex.jsx-768337a97aceabd1{display:-webkit-inline-box;display:-webkit-inline-flex;display:-moz-inline-box;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-768337a97aceabd1{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.test.jsx-768337a97aceabd1{-webkit-box-shadow:0 0 10px black,inset 0 0 5px black;-moz-box-shadow:0 0 10px black,inset 0 0 5px black;box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=","].jsx-768337a97aceabd1{display:inline-block}.test.is-status.jsx-768337a97aceabd1 .test.jsx-768337a97aceabd1{color:red}.a-selector.jsx-768337a97aceabd1:hover,.a-selector.jsx-768337a97aceabd1:focus{outline:none}@media(min-width:1px)and (max-width:768px){[class*="grid__col--"].jsx-768337a97aceabd1{margin-top:12px;margin-bottom:12px}}@media(max-width:64em){.test.jsx-768337a97aceabd1{margin-bottom:1em}@supports(-moz-appearance:none)and (display:contents){.test.jsx-768337a97aceabd1{margin-bottom:2rem}}}'}extends React.Component
{ )}
extends React.Component
{
const styles: { [k: string]: React.CSSProperties } = {
error: {
- color: '#000',
- background: '#fff',
fontFamily:
'-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif',
height: '100vh',
@@ -97,7 +113,6 @@ const styles: { [k: string]: React.CSSProperties } = {
h1: {
display: 'inline-block',
- borderRight: '1px solid rgba(0, 0, 0,.3)',
margin: 0,
marginRight: '20px',
padding: '10px 23px 10px 0',
diff --git a/packages/next/server/api-utils/node.ts b/packages/next/server/api-utils/node.ts
index 310c0afd12f4246..62e18957f552ba7 100644
--- a/packages/next/server/api-utils/node.ts
+++ b/packages/next/server/api-utils/node.ts
@@ -442,7 +442,7 @@ function sendJson(res: NextApiResponse, jsonBody: any): void {
res.setHeader('Content-Type', 'application/json; charset=utf-8')
// Use send to handle request
- res.send(jsonBody)
+ res.send(JSON.stringify(jsonBody))
}
function isNotValidData(str: string): boolean {
diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts
index 27153a073db99af..6a049172c5ae949 100644
--- a/packages/next/server/config.ts
+++ b/packages/next/server/config.ts
@@ -616,6 +616,24 @@ function assignDefaults(userConfig: { [key: string]: any }) {
result.pageExtensions = pageExtensions
}
+ if (result.devIndicators?.buildActivityPosition) {
+ const { buildActivityPosition } = result.devIndicators
+ const allowedValues = [
+ 'top-left',
+ 'top-right',
+ 'bottom-left',
+ 'bottom-right',
+ ]
+
+ if (!allowedValues.includes(buildActivityPosition)) {
+ throw new Error(
+ `Invalid "devIndicator.buildActivityPosition" provided, expected one of ${allowedValues.join(
+ ', '
+ )}, received ${buildActivityPosition}`
+ )
+ }
+ }
+
return result
}
diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx
index 6d58f10145cbd12..6f9191547645c00 100644
--- a/packages/next/server/render.tsx
+++ b/packages/next/server/render.tsx
@@ -201,12 +201,17 @@ function renderFlight(AppMod: any, ComponentMod: any, props: any) {
const AppServer = isServerComponent
? (App as React.ComponentType)
: React.Fragment
+ const { router: _, ...rest } = props
- return (
- extends App {
static async getInitialProps({ Component, AppTree, ctx }: AppContext) {
let pageProps = {}
@@ -32,15 +35,20 @@ class MyApp extends App {
const { Component, pageProps, html, router } = this.props
const href = router.pathname === '/' ? '/another' : '/'
- return html && router.pathname !== '/hello' ? (
- <>
-
+ This is the native onLoad which doesn't work as many places as
+ onLoadingComplete
+ {msg} static streaming hello nextjs hello nextjspage: {pathname}
diff --git a/test/integration/build-indicator/test/index.test.js b/test/integration/build-indicator/test/index.test.js
index 56e31eaf32b04da..713451d2faa995c 100644
--- a/test/integration/build-indicator/test/index.test.js
+++ b/test/integration/build-indicator/test/index.test.js
@@ -24,6 +24,37 @@ const installCheckVisible = (browser) => {
}
describe('Build Activity Indicator', () => {
+ it('should validate buildActivityPosition config', async () => {
+ let stderr = ''
+ const configPath = join(appDir, 'next.config.js')
+ await fs.writeFile(
+ configPath,
+ `
+ module.exports = {
+ devIndicators: {
+ buildActivityPosition: 'ttop-leff'
+ }
+ }
+ `
+ )
+ const app = await launchApp(appDir, await findPort(), {
+ onStderr(msg) {
+ stderr += msg
+ },
+ }).catch((err) => {
+ console.error('got err', err)
+ })
+ await fs.remove(configPath)
+
+ expect(stderr).toContain(
+ `Invalid "devIndicator.buildActivityPosition" provided, expected one of top-left, top-right, bottom-left, bottom-right, received ttop-leff`
+ )
+
+ if (app) {
+ await killApp(app)
+ }
+ })
+
describe('Enabled', () => {
beforeAll(async () => {
await fs.remove(nextConfig)
diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js
index 527f96e36ab7adb..d5b6cbeeb92b34b 100644
--- a/test/integration/build-output/test/index.test.js
+++ b/test/integration/build-output/test/index.test.js
@@ -162,7 +162,7 @@ describe('Build Output', () => {
/ \/slow-static\/.+\/.+(?: \(\d+ ms\))?| \[\+\d+ more paths\]/g
)
- expect(matches).toEqual([
+ for (const check of [
// summary
expect.stringMatching(
/\/\[propsDuration\]\/\[renderDuration\] \(\d+ ms\)/
@@ -178,7 +178,12 @@ describe('Build Output', () => {
expect.stringMatching(/\/10\/10$/),
// max of 7 preview paths
' [+2 more paths]',
- ])
+ ]) {
+ // the order isn't guaranteed on the timing tests as while() is being
+ // used in the render so can block the thread of other renders sharing
+ // the same worker
+ expect(matches).toContainEqual(check)
+ }
})
it('should not emit extracted comments', async () => {
diff --git a/test/integration/image-component/default/pages/on-load.js b/test/integration/image-component/default/pages/on-load.js
new file mode 100644
index 000000000000000..81b9b4cb0172559
--- /dev/null
+++ b/test/integration/image-component/default/pages/on-load.js
@@ -0,0 +1,101 @@
+import { useState } from 'react'
+import Image from 'next/image'
+
+const Page = () => {
+ // Hoisted state to count each image load callback
+ const [idToCount, setIdToCount] = useState({})
+ const [clicked, setClicked] = useState(false)
+
+ return (
+ Test onLoad
+
+ >
+ )
+}
+
+export default Page
diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js
index fc09281dec0c132..a9d2ab902bad483 100644
--- a/test/integration/image-component/default/test/index.test.js
+++ b/test/integration/image-component/default/test/index.test.js
@@ -289,6 +289,80 @@ function runTests(mode) {
}
})
+ it('should callback native onLoad in most cases', async () => {
+ let browser = await webdriver(appPort, '/on-load')
+
+ await browser.eval(
+ `document.getElementById("footer").scrollIntoView({behavior: "smooth"})`
+ )
+
+ await check(
+ () => browser.eval(`document.getElementById("img1").currentSrc`),
+ /test(.*)jpg/
+ )
+ await check(
+ () => browser.eval(`document.getElementById("img2").currentSrc`),
+ /test(.*).png/
+ )
+ await check(
+ () => browser.eval(`document.getElementById("img3").currentSrc`),
+ /test\.svg/
+ )
+ await check(
+ () => browser.eval(`document.getElementById("img4").currentSrc`),
+ /test(.*)ico/
+ )
+ await check(
+ () => browser.eval(`document.getElementById("msg1").textContent`),
+ 'loaded 1 img1 with native onLoad'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("msg2").textContent`),
+ 'loaded 1 img2 with native onLoad'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("msg3").textContent`),
+ 'loaded 1 img3 with native onLoad'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("msg4").textContent`),
+ 'loaded 1 img4 with native onLoad'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("msg8").textContent`),
+ 'loaded 1 img8 with native onLoad'
+ )
+ await check(
+ () =>
+ browser.eval(
+ `document.getElementById("img8").getAttribute("data-nimg")`
+ ),
+ 'intrinsic'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("img8").currentSrc`),
+ /wide.png/
+ )
+ await browser.eval('document.getElementById("toggle").click()')
+ // The normal `onLoad()` is triggered by lazy placeholder image
+ // so ideally this would be "2" instead of "3" count
+ await check(
+ () => browser.eval(`document.getElementById("msg8").textContent`),
+ 'loaded 3 img8 with native onLoad'
+ )
+ await check(
+ () =>
+ browser.eval(
+ `document.getElementById("img8").getAttribute("data-nimg")`
+ ),
+ 'fixed'
+ )
+ await check(
+ () => browser.eval(`document.getElementById("img8").currentSrc`),
+ /test-rect.jpg/
+ )
+ })
+
it('should work with image with blob src', async () => {
let browser
try {
diff --git a/test/integration/pnpm-support/app-multi-page/.npmrc b/test/integration/pnpm-support/app-multi-page/.npmrc
new file mode 100644
index 000000000000000..84db07921228255
--- /dev/null
+++ b/test/integration/pnpm-support/app-multi-page/.npmrc
@@ -0,0 +1,2 @@
+# put modules outside of the app folder, to simulate what happens in a pnpm workspace
+virtual-store-dir=../pnpm
\ No newline at end of file
diff --git a/test/integration/pnpm-support/app-multi-page/next.config.js b/test/integration/pnpm-support/app-multi-page/next.config.js
new file mode 100644
index 000000000000000..d6cec9e13193299
--- /dev/null
+++ b/test/integration/pnpm-support/app-multi-page/next.config.js
@@ -0,0 +1,9 @@
+const path = require('path')
+
+module.exports = {
+ experimental: {
+ outputStandalone: true,
+ // pnpm virtual-store-dir is outside the app directory
+ outputFileTracingRoot: path.resolve(__dirname, '../'),
+ },
+}
diff --git a/test/integration/pnpm-support/app/next.config.js b/test/integration/pnpm-support/app/next.config.js
new file mode 100644
index 000000000000000..0568ecc9e54015b
--- /dev/null
+++ b/test/integration/pnpm-support/app/next.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ experimental: {
+ outputStandalone: true,
+ },
+}
diff --git a/test/integration/pnpm-support/test/index.test.js b/test/integration/pnpm-support/test/index.test.js
index fb50ef49fe7a093..0bb49f0fabce98b 100644
--- a/test/integration/pnpm-support/test/index.test.js
+++ b/test/integration/pnpm-support/test/index.test.js
@@ -13,8 +13,33 @@ const APP_DIRS = {
'app-multi-page': path.join(__dirname, '..', 'app-multi-page'),
}
+// runs a command showing logs and returning the stdout
+const runCommand = (cwd, cmd, args) => {
+ const proc = execa(cmd, [...args], {
+ cwd,
+ stdio: [process.stdin, 'pipe', process.stderr],
+ })
+
+ let stdout = ''
+ proc.stdout.on('data', (data) => {
+ const s = data.toString()
+ process.stdout.write(s)
+ stdout += s
+ })
+ return new Promise((resolve, reject) => {
+ proc.on('exit', (code) => {
+ if (code === 0) {
+ return resolve({ ...proc, stdout })
+ }
+ reject(
+ new Error(`Command ${cmd} ${args.join(' ')} failed with code ${code}`)
+ )
+ })
+ })
+}
+
const runNpm = (cwd, ...args) => execa('npm', [...args], { cwd })
-const runPnpm = (cwd, ...args) => execa('npx', ['pnpm', ...args], { cwd })
+const runPnpm = (cwd, ...args) => runCommand(cwd, 'npx', ['pnpm', ...args])
async function usingTempDir(fn) {
const folder = path.join(os.tmpdir(), Math.random().toString(36).substring(2))
@@ -134,16 +159,66 @@ describe('pnpm support', () => {
expect(packageJson.pnpm.overrides[dependency]).toMatch(/^file:/)
}
- const { stdout, stderr } = await runPnpm(appDir, 'run', 'build')
- console.log(stdout, stderr)
+ const { stdout } = await runPnpm(appDir, 'run', 'build')
+
expect(stdout).toMatch(/Compiled successfully/)
})
})
+ it('should execute client-side JS on each page in outputStandalone', async () => {
+ await usingPnpmCreateNextApp(APP_DIRS['app-multi-page'], async (appDir) => {
+ const { stdout } = await runPnpm(appDir, 'run', 'build')
+
+ expect(stdout).toMatch(/Compiled successfully/)
+
+ let appPort
+ let appProcess
+ let browser
+ try {
+ appPort = await findPort()
+ const standaloneDir = path.resolve(appDir, '.next/standalone/app')
+
+ // simulate what happens in a Dockerfile
+ await fs.remove(path.join(appDir, 'node_modules'))
+ await fs.copy(
+ path.resolve(appDir, './.next/static'),
+ path.resolve(standaloneDir, './.next/static'),
+ { overwrite: true }
+ )
+ appProcess = execa('node', ['server.js'], {
+ cwd: standaloneDir,
+ env: {
+ PORT: appPort,
+ },
+ stdio: 'inherit',
+ })
+
+ await waitFor(1000)
+
+ await renderViaHTTP(appPort, '/')
+
+ browser = await webdriver(appPort, '/', {
+ waitHydration: false,
+ })
+ expect(await browser.waitForElementByCss('#world').text()).toBe('World')
+ await browser.close()
+
+ browser = await webdriver(appPort, '/about', {
+ waitHydration: false,
+ })
+ expect(await browser.waitForElementByCss('#world').text()).toBe('World')
+ await browser.close()
+ } finally {
+ await killProcess(appProcess.pid)
+ await waitFor(5000)
+ }
+ })
+ })
+
it('should execute client-side JS on each page', async () => {
await usingPnpmCreateNextApp(APP_DIRS['app-multi-page'], async (appDir) => {
- const { stdout, stderr } = await runPnpm(appDir, 'run', 'build')
- console.log(stdout, stderr)
+ const { stdout } = await runPnpm(appDir, 'run', 'build')
+
expect(stdout).toMatch(/Compiled successfully/)
let appPort
@@ -151,7 +226,11 @@ describe('pnpm support', () => {
let browser
try {
appPort = await findPort()
- appProcess = runPnpm(appDir, 'run', 'start', '--', '--port', appPort)
+ appProcess = execa('pnpm', ['run', 'start', '--', '--port', appPort], {
+ cwd: appDir,
+ stdio: 'inherit',
+ })
+
await waitFor(5000)
await renderViaHTTP(appPort, '/')
diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.server.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.server.js
index 0d80a5a8abb31ec..15ab2f9fdc7166d 100644
--- a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.server.js
+++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.server.js
@@ -1,6 +1,6 @@
export default function AppServer({ children }) {
return (
-