diff --git a/.github/actions/next-stats-action/package.json b/.github/actions/next-stats-action/package.json index 65586812c447a..a9fb8c4e081e7 100644 --- a/.github/actions/next-stats-action/package.json +++ b/.github/actions/next-stats-action/package.json @@ -13,6 +13,7 @@ "node-fetch": "^2.6.0", "prettier": "^1.18.2", "pretty-bytes": "^5.3.0", - "pretty-ms": "^5.0.0" + "pretty-ms": "^5.0.0", + "semver": "7.3.4" } } diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js index f24fe427faf87..0b39ed8a2e67b 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -74,6 +74,7 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { logger('Release detected, resetting mainRepo to last stable tag') const lastStableTag = await getLastStable(mainRepoDir, actionInfo.prRef) if (!lastStableTag) throw new Error('failed to get last stable tag') + console.log('using latestStable', lastStableTag) await checkoutRef(lastStableTag, mainRepoDir) /* eslint-disable-next-line */ diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js index e5ee6d2c4b22a..534ab34229e53 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -3,6 +3,7 @@ const fs = require('fs-extra') const exec = require('../util/exec') const { remove } = require('fs-extra') const logger = require('../util/logger') +const semver = require('semver') module.exports = (actionInfo) => { return { @@ -22,8 +23,9 @@ module.exports = (actionInfo) => { const curTag = tags[i] // stable doesn't include `-canary` or `-beta` if (!curTag.includes('-') && !ref.includes(curTag)) { - lastStableTag = curTag - break + if (!lastStableTag || semver.gt(curTag, lastStableTag)) { + lastStableTag = curTag + } } } return lastStableTag diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 3132eafa2aec2..176952e1e6a65 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -15,11 +15,14 @@ jobs: docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }} steps: - uses: actions/checkout@v2 + with: + fetch-depth: 25 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - run: yarn install --frozen-lockfile --check-files - run: node run-tests.js --timings --write-timings -g 1/1 - name: Check docs only change - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'docs-only') + run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - run: echo ${{steps.docs-change.outputs.DOCS_CHANGE}} - uses: actions/cache@v2 @@ -47,13 +50,13 @@ jobs: NEXT_TELEMETRY_DISABLED: 1 steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - run: ./check-pre-compiled.sh - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testUnit: name: Test Unit @@ -65,14 +68,14 @@ jobs: HEADLESS: true steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - run: node run-tests.js --timings --type unit -g 1/1 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testIntegration: name: Test Integration @@ -89,7 +92,7 @@ jobs: steps: - run: echo ${{needs.build.outputs.docsChange}} - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* @@ -97,10 +100,10 @@ jobs: # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testElectron: name: Test Electron @@ -113,7 +116,7 @@ jobs: TEST_ELECTRON: 1 steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* @@ -121,13 +124,13 @@ jobs: # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: yarn add -W --dev spectron@7.0.0 electron@5.0.0 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testYarnPnP: runs-on: ubuntu-latest @@ -136,15 +139,17 @@ jobs: YARN_COMPRESSION_LEVEL: '0' steps: - uses: actions/checkout@v2 + with: + fetch-depth: 25 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'docs-only') + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - run: yarn install --frozen-lockfile --check-files - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs-only'}} + if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} - run: bash ./test-pnp.sh - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs-only'}} + if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} testsPass: name: thank you, next @@ -160,22 +165,24 @@ jobs: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 HEADLESS: true - NEXT_WEBPACK5: 1 + NEXT_PRIVATE_TEST_WEBPACK5_MODE: 1 steps: - uses: actions/checkout@v2 + with: + fetch-depth: 25 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'docs-only') + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx,worker-loader}/test/index.test.js test/acceptance/*.test.js - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + - run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} testLegacyReact: name: React 16 + Webpack 4 (Basic, Production, Acceptance) @@ -187,27 +194,29 @@ jobs: steps: - uses: actions/checkout@v2 + with: + fetch-depth: 25 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'docs-only') + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: cat package.json | jq '.resolutions.react = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: cat package.json | jq '.resolutions."react-dom" = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: yarn list react react-dom - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx,worker-loader}/test/index.test.js test/acceptance/*.test.js - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} testFirefox: name: Test Firefox (production) @@ -219,13 +228,13 @@ jobs: NEXT_TELEMETRY_DISABLED: 1 steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - run: node run-tests.js test/integration/production/test/index.test.js - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testSafari: name: Test Safari (production) @@ -240,13 +249,13 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production/test/index.test.js' - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testSafariOld: name: Test Safari 10.1 (nav) @@ -262,18 +271,18 @@ jobs: BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: - uses: actions/cache@v2 - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js' - if: ${{needs.build.outputs.docsChange != 'docs-only'}} + if: ${{needs.build.outputs.docsChange != 'docs only change'}} publishRelease: name: Potentially publish release runs-on: ubuntu-latest - needs: [testsPass] + needs: build env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml index 243138f438bea..4c4874cdb9f48 100644 --- a/.github/workflows/pull_request_stats.yml +++ b/.github/workflows/pull_request_stats.yml @@ -10,7 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'docs-only') + with: + fetch-depth: 25 + + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - uses: ./.github/actions/next-stats-action - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs-only' }} + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} diff --git a/.vscode/launch.json b/.vscode/launch.json index e7cd5522232f3..3a6858c2fd0dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -42,6 +42,16 @@ "port": 9229, "skipFiles": ["/**"], "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + }, + { + "name": "Launch this example", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "debug", "dev", "${fileDirname}"], + "skipFiles": ["/**"], + "port": 9229 } ] } diff --git a/contributing.md b/contributing.md index 8196675bc0004..6bfc13c554a6f 100644 --- a/contributing.md +++ b/contributing.md @@ -132,7 +132,7 @@ Description ## Deploy your own -Deploy the example using [Vercel](https://vercel.com/now): +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/DIRECTORY_NAME&project-name=DIRECTORY_NAME&repository-name=DIRECTORY_NAME) diff --git a/docs/advanced-features/automatic-static-optimization.md b/docs/advanced-features/automatic-static-optimization.md index b4b4dadd1ec0c..17a3ea787c3b9 100644 --- a/docs/advanced-features/automatic-static-optimization.md +++ b/docs/advanced-features/automatic-static-optimization.md @@ -25,13 +25,13 @@ During prerendering, the router's `query` object will be empty since we do not h `next build` will emit `.html` files for statically optimized pages. For example, the result for the page `pages/about.js` would be: ```bash -.next/server/static/${BUILD_ID}/about.html +.next/server/pages/about.html ``` And if you add `getServerSideProps` to the page, it will then be JavaScript, like so: ```bash -.next/server/static/${BUILD_ID}/about.js +.next/server/pages/about.js ``` ## Caveats diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md index b1a0e3800cefc..329cddd452cf7 100644 --- a/docs/advanced-features/measuring-performance.md +++ b/docs/advanced-features/measuring-performance.md @@ -1,14 +1,19 @@ --- -description: Measure and track page performance using Next.js's build-in performance relayer +description: Measure and track page performance using Next.js Analytics --- # Measuring performance -Next.js has a built-in relayer that allows you to analyze and measure the performance of +[Next.js Analytics](https://nextjs.org/analytics) allows you to analyze and measure the performance of pages using different metrics. -To measure any of the supported metrics, you will need to create a [custom -App](/docs/advanced-features/custom-app.md) component and define a `reportWebVitals` function: +You can start collecting your [Real Experience Score](https://vercel.com/docs/analytics#metrics) with zero-configuration on [Vercel deployments](https://vercel.com/docs/analytics). There's also support for Analytics if you're [self-hosting](https://vercel.com/docs/analytics#self-hosted). + +The rest of this documentation describes the built-in relayer Next.js Analytics uses. + +## Build Your Own + +First, you will need to create a [custom App](/docs/advanced-features/custom-app.md) component and define a `reportWebVitals` function: ```js // pages/_app.js diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index 79ac857448dc0..465d4354e48fc 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -21,6 +21,7 @@ description: Next.js has the preview mode for statically generated pages. You ca
  • ButterCMS Example (Demo)
  • Storyblok Example (Demo)
  • GraphCMS Example (Demo)
  • +
  • Kontent Example (Demo)
  • diff --git a/docs/api-reference/next.config.js/custom-page-extensions.md b/docs/api-reference/next.config.js/custom-page-extensions.md index 436af982cc58f..f457b9083d177 100644 --- a/docs/api-reference/next.config.js/custom-page-extensions.md +++ b/docs/api-reference/next.config.js/custom-page-extensions.md @@ -14,6 +14,8 @@ module.exports = { } ``` +> **Note**: configuring `pageExtensions` also affects `_document.js`, `_app.js` as well as files under `pages/api/`. For example, setting `pageExtensions: ['page.tsx', 'page.ts']` means the following files: `_document.tsx`, `_app.tsx`, `pages/users.tsx` and `pages/api/users.ts` will have to be renamed to `_document.page.tsx`, `_app.page.tsx`, `pages/users.page.tsx` and `pages/api/users.page.ts` respectively. + ## Related
    diff --git a/docs/api-reference/next.config.js/headers.md b/docs/api-reference/next.config.js/headers.md index 85b513c163ecd..73623b08a2893 100644 --- a/docs/api-reference/next.config.js/headers.md +++ b/docs/api-reference/next.config.js/headers.md @@ -6,6 +6,13 @@ description: Add custom HTTP headers to your Next.js app. > This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. +
    + Examples + +
    + Headers allow you to set custom HTTP headers for an incoming request path. To set custom HTTP headers you can use the `headers` key in `next.config.js`: @@ -224,3 +231,7 @@ 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`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) function. diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index 56de35581fb5c..7b65b5273983d 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -402,3 +402,26 @@ function Page({ router }) { export default withRouter(Page) ``` + +### Typescript + +To use class components with `withRouter`, the component needs to accept a router prop: + +```tsx +import React from 'react' +import { withRouter, NextRouter } from 'next/router' + +interface WithRouterProps { + router: NextRouter +} + +interface MyComponentProps extends WithRouterProps {} + +class MyComponent extends React.Component { + render() { + return

    {this.props.router.pathname}

    + } +} + +export default withRouter(MyComponent) +``` diff --git a/docs/api-routes/introduction.md b/docs/api-routes/introduction.md index e3f7e5021424b..144a32c5836c6 100644 --- a/docs/api-routes/introduction.md +++ b/docs/api-routes/introduction.md @@ -19,13 +19,11 @@ API routes provide a straightforward solution to build your **API** with Next.js Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated as an API endpoint instead of a `page`. They are server-side only bundles and won't increase your client-side bundle size. -For example, the following API route `pages/api/user.js` handles a `json` response: +For example, the following API route `pages/api/user.js` returns a `json` response with a status code of `200`: ```js export default function handler(req, res) { - res.statusCode = 200 - res.setHeader('Content-Type', 'application/json') - res.end(JSON.stringify({ name: 'John Doe' })) + res.status(200).json({ name: 'John Doe' }) } ``` diff --git a/docs/basic-features/data-fetching.md b/docs/basic-features/data-fetching.md index 362e8e0bfea39..f82f7cac5e110 100644 --- a/docs/basic-features/data-fetching.md +++ b/docs/basic-features/data-fetching.md @@ -289,7 +289,7 @@ Since Next.js compiles your code into a separate directory you can't use `__dirn Instead you can use `process.cwd()` which gives you the directory where Next.js is being executed. ```jsx -import fs from 'fs' +import { promises as fs } from 'fs' import path from 'path' // posts will be populated at build time by getStaticProps() @@ -311,11 +311,11 @@ function Blog({ posts }) { // direct database queries. See the "Technical details" section. export async function getStaticProps() { const postsDirectory = path.join(process.cwd(), 'posts') - const filenames = fs.readdirSync(postsDirectory) + const filenames = await fs.readdir(postsDirectory) - const posts = filenames.map((filename) => { + const posts = filenames.map(async (filename) => { const filePath = path.join(postsDirectory, filename) - const fileContents = fs.readFileSync(filePath, 'utf8') + const fileContents = await fs.readFile(filePath, 'utf8') // Generally you would parse/transform the contents // For example you can transform markdown to HTML here @@ -329,7 +329,7 @@ export async function getStaticProps() { // will receive `posts` as a prop at build time return { props: { - posts, + posts: await Promise.all(posts), }, } } diff --git a/docs/migrating/from-create-react-app.md b/docs/migrating/from-create-react-app.md index 03c977693b1de..83a0eb481d7a1 100644 --- a/docs/migrating/from-create-react-app.md +++ b/docs/migrating/from-create-react-app.md @@ -48,7 +48,7 @@ Create React App uses the `public` directory for the [entry HTML file](https://c ## Creating Routes & Linking -With Create React App, you're likely using React Router. Instead of using a third-party library, Next.js includes it's own [file-system based routing](/docs/routing/introduction.md). +With Create React App, you're likely using React Router. Instead of using a third-party library, Next.js includes its own [file-system based routing](/docs/routing/introduction.md). - Convert all `Route` components to new files in the `pages` directory. - For routes that require dynamic content (e.g. `/blog/:slug`), you can use [Dynamic Routes](/docs/routing/dynamic-routes.md) with Next.js (e.g. `pages/blog/[slug].js`). The value of `slug` is accessible through a [query parameter](/docs/routing/dynamic-routes.md). For example, the route `/blog/first-post` would forward the query object `{ 'slug': 'first-post' }` to `pages/blog/[slug].js` ([learn more here](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation)). diff --git a/errors/no-router-instance.md b/errors/no-router-instance.md index 504ce29ecd803..2beaafbc71cbb 100644 --- a/errors/no-router-instance.md +++ b/errors/no-router-instance.md @@ -2,8 +2,12 @@ #### Why This Error Occurred -During SSR you might have tried to access a router method `push`, `replace`, `back`, which is not supported. +During Pre-rendering (SSR or SSG) you tried to access a router method `push`, `replace`, `back`, which is not supported. #### Possible Ways to Fix It -Move any calls to router methods to `componentDidMount` or add a check such as `typeof window !== 'undefined'` before calling the methods +In a function Component you can move the code into the `useEffect` hook. + +In a class Component, move any calls to router methods to the `componentDidMount` lifecycle method. + +This way the calls to the router methods are only executed in the browser. diff --git a/examples/amp-story/README.md b/examples/amp-story/README.md index 79cb3ae066180..008980bf3214f 100644 --- a/examples/amp-story/README.md +++ b/examples/amp-story/README.md @@ -13,9 +13,9 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu 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 amp-story amp-app +npx create-next-app --example amp-story amp-story-app # or -yarn create next-app --example amp-story amp-app +yarn create next-app --example amp-story amp-story-app ``` Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/auth0/README.md b/examples/auth0/README.md index 8b014bd5d026f..90cd8b521b4c9 100644 --- a/examples/auth0/README.md +++ b/examples/auth0/README.md @@ -16,9 +16,9 @@ Read more: [https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/]( 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 auth0 auth0 +npx create-next-app --example auth0 auth0-app # or -yarn create next-app --example auth0 auth0 +yarn create next-app --example auth0 auth0-app ``` ## Configuring Auth0 diff --git a/examples/blog-starter/lib/api.js b/examples/blog-starter/lib/api.js index f684833c7c130..08da07eb78ab8 100644 --- a/examples/blog-starter/lib/api.js +++ b/examples/blog-starter/lib/api.js @@ -38,6 +38,6 @@ export function getAllPosts(fields = []) { const posts = slugs .map((slug) => getPostBySlug(slug, fields)) // sort posts by date in descending order - .sort((post1, post2) => (post1.date > post2.date ? '-1' : '1')) + .sort((post1, post2) => (post1.date > post2.date ? -1 : 1)) return posts } diff --git a/examples/cms-agilitycms/README.md b/examples/cms-agilitycms/README.md index d7268f693b287..1dd3886700792 100644 --- a/examples/cms-agilitycms/README.md +++ b/examples/cms-agilitycms/README.md @@ -29,6 +29,7 @@ Once you have access to [the environment variables you'll need](#step-15-set-up- - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Ghost](/examples/cms-ghost) - [Blog Starter](/examples/blog-starter) ## How to use diff --git a/examples/cms-buttercms/README.md b/examples/cms-buttercms/README.md index af80185975f04..cfbfada2114c2 100644 --- a/examples/cms-buttercms/README.md +++ b/examples/cms-buttercms/README.md @@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-2-set-up-e - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Ghost](/examples/cms-ghost) - [Blog Starter](/examples/blog-starter) ## How to use diff --git a/examples/cms-contentful/README.md b/examples/cms-contentful/README.md index e89d39a30a2f2..6f8d7171a68ed 100644 --- a/examples/cms-contentful/README.md +++ b/examples/cms-contentful/README.md @@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Ghost](/examples/cms-ghost) - [Blog Starter](/examples/blog-starter) ## How to use diff --git a/examples/cms-cosmic/README.md b/examples/cms-cosmic/README.md index 0962853fd9122..079a5a84f99b8 100644 --- a/examples/cms-cosmic/README.md +++ b/examples/cms-cosmic/README.md @@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Ghost](/examples/cms-ghost) - [Blog Starter](/examples/blog-starter) ## How to use diff --git a/examples/cms-datocms/README.md b/examples/cms-datocms/README.md index f60c0600b59a2..05673ad877fb1 100644 --- a/examples/cms-datocms/README.md +++ b/examples/cms-datocms/README.md @@ -20,6 +20,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Ghost](/examples/cms-ghost) - [Blog Starter](/examples/blog-starter) ## Deploy your own diff --git a/examples/cms-ghost/.env.local.example b/examples/cms-ghost/.env.local.example new file mode 100644 index 0000000000000..e60e609d4d49c --- /dev/null +++ b/examples/cms-ghost/.env.local.example @@ -0,0 +1,2 @@ +GHOST_API_URL= +GHOST_API_KEY= diff --git a/examples/cms-ghost/.gitignore b/examples/cms-ghost/.gitignore new file mode 100644 index 0000000000000..1437c53f70bc2 --- /dev/null +++ b/examples/cms-ghost/.gitignore @@ -0,0 +1,34 @@ +# 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 diff --git a/examples/cms-ghost/README.md b/examples/cms-ghost/README.md new file mode 100644 index 0000000000000..13fb0d60c227a --- /dev/null +++ b/examples/cms-ghost/README.md @@ -0,0 +1,86 @@ +# A statically generated blog example using Next.js and Ghost + +This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Ghost](https://ghost.org/) as the data source. + +> This boilerplate demonstrates simple usage and best practices. If you are looking for a more feature richt Next.js generator for Ghost including the Casper theme, +> check out [next-cms-ghost](https://github.com/styxlab/next-cms-ghost). + +## Deploy your own + +Once you have access to [the environment variables you'll need](#step-2-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-ghost&env=ghost_BUCKET_SLUG,ghost_READ_KEY,ghost_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-ghost-env) + +### Related examples + +- [WordPress](/examples/cms-wordpress) +- [DatoCMS](/examples/cms-datocms) +- [Sanity](/examples/cms-sanity) +- [TakeShape](/examples/cms-takeshape) +- [Prismic](/examples/cms-prismic) +- [Contentful](/examples/cms-contentful) +- [Strapi](/examples/cms-strapi) +- [Agility CMS](/examples/cms-agilitycms) +- [ButterCMS](/examples/cms-buttercms) +- [Storyblok](/examples/cms-storyblok) +- [GraphCMS](/examples/cms-graphcms) +- [Kontent](/examples/cms-kontent) +- [Blog Starter](/examples/blog-starter) + +## 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 cms-ghost cms-ghost-app +# or +yarn create next-app --example cms-ghost cms-ghost-app +``` + +### Setp 1. Run Next.js in development mode + +To get started, no configuration is needed as this example sources the content from a demo Ghost CMS. + +```bash +npm install +npm run dev + +# or + +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/vercel/next.js/discussions). + +### Step 2. Set up environment variables + +If you already have a Ghost CMS running, you should create new Content API keys in the Ghost Admin panel under Integrations -> Add custom integration. +Once your keys are generated, copy them into the environment variables as follows. Your `.env.local` file should look like this: + +```bash +GHOST_API_URL=... +GHOST_API_KEY=... +``` + +Make sure to use the Content API Key. + +### Step 3. Set up Headless Ghost CMS + +If you do not have access to a Ghost CMS, you need to create one for your own content. The demo Ghost CMS is running on Hetzner Cloud, which is described in [Ghost CMS on Hetzner Cloud](https://www.jamify.org/2020/04/07/ghost-cms-on-hetzner-cloud/). Note that a Ghost install on localhost is not sufficient for public deploys, as the images on your local computer are not accessible from outside. + +### Step 4. Deploy on Vercel + +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)). + +#### Deploy Your Local Project + +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. + +#### Deploy from Our Template + +Alternatively, you can deploy using our template by clicking on the Deploy button below. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-ghost&env=GHOST_API_URL,GHOST_API_KEY&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-ghost-env) diff --git a/examples/cms-ghost/components/alert.js b/examples/cms-ghost/components/alert.js new file mode 100644 index 0000000000000..e6fd18b175c8c --- /dev/null +++ b/examples/cms-ghost/components/alert.js @@ -0,0 +1,42 @@ +import Container from './container' +import cn from 'classnames' +import { EXAMPLE_PATH } from '@/lib/constants' + +export default function Alert({ preview }) { + return ( +
    + +
    + {preview ? ( + <> + This is page is a preview.{' '} + + Click here + {' '} + to exit preview mode. + + ) : ( + <> + The source code for this blog is{' '} + + available on GitHub + + . + + )} +
    +
    +
    + ) +} diff --git a/examples/cms-ghost/components/avatar.js b/examples/cms-ghost/components/avatar.js new file mode 100644 index 0000000000000..271037ba41c1d --- /dev/null +++ b/examples/cms-ghost/components/avatar.js @@ -0,0 +1,14 @@ +export default function Avatar({ name, picture }) { + return ( +
    + {picture && ( + {name} + )} +
    {name}
    +
    + ) +} diff --git a/examples/cms-ghost/components/container.js b/examples/cms-ghost/components/container.js new file mode 100644 index 0000000000000..fc1c29dfb0747 --- /dev/null +++ b/examples/cms-ghost/components/container.js @@ -0,0 +1,3 @@ +export default function Container({ children }) { + return
    {children}
    +} diff --git a/examples/cms-ghost/components/cover-image.js b/examples/cms-ghost/components/cover-image.js new file mode 100644 index 0000000000000..5d8a953ad1636 --- /dev/null +++ b/examples/cms-ghost/components/cover-image.js @@ -0,0 +1,29 @@ +import cn from 'classnames' +import Link from 'next/link' +import Image from 'next/image' + +export default function CoverImage({ title, url, slug, width, height }) { + const image = ( + {`Cover + ) + return ( +
    + {slug ? ( + + {image} + + ) : ( + image + )} +
    + ) +} diff --git a/examples/cms-ghost/components/date.js b/examples/cms-ghost/components/date.js new file mode 100644 index 0000000000000..eac5681378bfd --- /dev/null +++ b/examples/cms-ghost/components/date.js @@ -0,0 +1,6 @@ +import { parseISO, format } from 'date-fns' + +export default function Date({ dateString }) { + const date = parseISO(dateString) + return +} diff --git a/examples/cms-ghost/components/footer.js b/examples/cms-ghost/components/footer.js new file mode 100644 index 0000000000000..102872d195df0 --- /dev/null +++ b/examples/cms-ghost/components/footer.js @@ -0,0 +1,30 @@ +import Container from './container' +import { EXAMPLE_PATH } from '@/lib/constants' + +export default function Footer() { + return ( + + ) +} diff --git a/examples/cms-ghost/components/header.js b/examples/cms-ghost/components/header.js new file mode 100644 index 0000000000000..562e7e3eebb6a --- /dev/null +++ b/examples/cms-ghost/components/header.js @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function Header() { + return ( +

    + + Blog + + . +

    + ) +} diff --git a/examples/cms-ghost/components/hero-post.js b/examples/cms-ghost/components/hero-post.js new file mode 100644 index 0000000000000..c912cce27676c --- /dev/null +++ b/examples/cms-ghost/components/hero-post.js @@ -0,0 +1,43 @@ +import Avatar from './avatar' +import Date from './date' +import CoverImage from './cover-image' +import Link from 'next/link' + +export default function HeroPost({ + title, + coverImage, + date, + excerpt, + author, + slug, +}) { + return ( +
    +
    + +
    +
    +
    +

    + + {title} + +

    +
    + +
    +
    +
    +

    {excerpt}

    + +
    +
    +
    + ) +} diff --git a/examples/cms-ghost/components/intro.js b/examples/cms-ghost/components/intro.js new file mode 100644 index 0000000000000..c3003c6196642 --- /dev/null +++ b/examples/cms-ghost/components/intro.js @@ -0,0 +1,28 @@ +import { CMS_NAME, CMS_URL } from '@/lib/constants' + +export default function Intro() { + return ( +
    +

    + Blog. +

    +

    + A statically generated blog example using{' '} + + Next.js + {' '} + and{' '} + + {CMS_NAME} + + . +

    +
    + ) +} diff --git a/examples/cms-ghost/components/layout.js b/examples/cms-ghost/components/layout.js new file mode 100644 index 0000000000000..ff54d53992783 --- /dev/null +++ b/examples/cms-ghost/components/layout.js @@ -0,0 +1,18 @@ +import Alert from './alert' +import Footer from './footer' +import Meta from './meta' +import 'lazysizes' +import 'lazysizes/plugins/parent-fit/ls.parent-fit' + +export default function Layout({ preview, children }) { + return ( + <> + +
    + +
    {children}
    +
    +