diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 2fb9ad7943345..51d848de12dc9 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -77,23 +77,6 @@ jobs: steps: - run: exit 0 - testMacOS: - name: macOS (Basic, Production, Acceptance) - runs-on: macos-latest - env: - NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - HEADLESS: true - - steps: - - uses: actions/checkout@v2 - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - # Installing dependencies again since OS changed - - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files - - run: node run-tests.js test/integration/production/test/index.test.js - - run: node run-tests.js test/integration/basic/test/index.test.js - - run: node run-tests.js test/acceptance/* - testWebpack5: name: webpack 5 (Basic, Production, Acceptance) runs-on: ubuntu-latest diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml new file mode 100644 index 0000000000000..09ff576abd913 --- /dev/null +++ b/.github/workflows/test_macos.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [canary] + +name: Test macOS + +jobs: + testMacOS: + name: macOS (Basic, Production, Acceptance) + runs-on: macos-latest + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + + steps: + - uses: actions/checkout@v2 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files + - run: node run-tests.js test/integration/production/test/index.test.js + - run: node run-tests.js test/integration/basic/test/index.test.js + - run: node run-tests.js test/acceptance/* diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index badf0e9510376..e97d2b2d59a4d 100644 --- a/docs/api-reference/cli.md +++ b/docs/api-reference/cli.md @@ -48,6 +48,14 @@ NODE_OPTIONS='--inspect' next The first load is colored green, yellow, or red. Aim for green for performant applications. +You can enable production profiling for React with the `--profile` flag in `next build`. This requires Next.js 9.5: + +```bash +next build --profile +``` + +After that, you can use the profiler in the same way as you would in development. + ## Development `next dev` starts the application in development mode with hot-code reloading, error reporting, and more: diff --git a/docs/api-reference/next.config.js/runtime-configuration.md b/docs/api-reference/next.config.js/runtime-configuration.md index 4a4a2a5ace6e1..b52e3e8898560 100644 --- a/docs/api-reference/next.config.js/runtime-configuration.md +++ b/docs/api-reference/next.config.js/runtime-configuration.md @@ -6,8 +6,6 @@ description: Add client and server runtime configuration to your Next.js app. > Generally you'll want to use [build-time environment variables](/docs/api-reference/next.config.js/environment-variables.md) to provide your configuration. The reason for this is that runtime configuration adds rendering / initialization overhead and is incompatible with [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). -> Runtime configuration is not available when using the [`serverless` target](/docs/api-reference/next.config.js/build-target.md#serverless-target). - To add runtime configuration to your app open `next.config.js` and add the `publicRuntimeConfig` and `serverRuntimeConfig` configs: ```js diff --git a/docs/basic-features/built-in-css-support.md b/docs/basic-features/built-in-css-support.md index c40249a082631..ee44a2bbf47e0 100644 --- a/docs/basic-features/built-in-css-support.md +++ b/docs/basic-features/built-in-css-support.md @@ -117,6 +117,13 @@ npm install sass Sass support has the same benefits and restrictions as the built-in CSS support detailed above. +> **Note**: Sass supports [two different syntaxes](https://sass-lang.com/documentation/syntax), each with their own extension. +> The `.scss` extension requires you use the [SCSS syntax](https://sass-lang.com/documentation/syntax#scss), +> while the `.sass` extension requires you use the [Indented Syntax ("Sass")](https://sass-lang.com/documentation/syntax#the-indented-syntax). +> +> If you're not sure which to choose, start with the `.scss` extension which is a superset of CSS, and doesn't require you learn the +> Indented Syntax ("Sass"). + ### Customizing Sass Options If you want to configure the Sass compiler you can do so by using `sassOptions` in `next.config.js`. diff --git a/errors/invalid-external-rewrite.md b/errors/invalid-external-rewrite.md new file mode 100644 index 0000000000000..89d114af835bf --- /dev/null +++ b/errors/invalid-external-rewrite.md @@ -0,0 +1,13 @@ +# Invalid External Rewrite + +#### Why This Error Occurred + +A rewrite was defined with both `basePath: false` and an internal `destination`. Rewrites that capture urls outside of the `basePath` must route externally, as they are intended for proxying in the case of incremental adoption of Next.js in a project. + +#### Possible Ways to Fix It + +Look for any rewrite where `basePath` is `false` and make sure its `destination` starts with `http://` or `https://`. + +### Useful Links + +- [Rewrites section in Documentation](https://nextjs.org/docs/api-reference/next.config.js/rewrites) diff --git a/examples/blog-starter-typescript/components/alert.tsx b/examples/blog-starter-typescript/components/alert.tsx index 9e07d7736c30c..f804ff6192868 100644 --- a/examples/blog-starter-typescript/components/alert.tsx +++ b/examples/blog-starter-typescript/components/alert.tsx @@ -18,7 +18,7 @@ const Alert = ({ preview }: Props) => {
{preview ? ( <> - This is page is a preview.{' '} + This page is a preview.{' '} {preview ? ( <> - This is page is a preview.{' '} + This page is a preview.{' '} { - // xa is the access token, which can be retrieved through - // firebase.auth().currentUser.getIdToken() - const { uid, email, xa } = user - const userData = { - id: uid, - email, - token: xa, - } - cookie.set('auth', userData, { - expires: 1, - }) + const userData = mapUserData(user) + setUserCookie(userData) }, }, } diff --git a/examples/with-firebase-authentication/pages/index.js b/examples/with-firebase-authentication/pages/index.js index c137f2888e2d4..0434faa8a443d 100644 --- a/examples/with-firebase-authentication/pages/index.js +++ b/examples/with-firebase-authentication/pages/index.js @@ -51,7 +51,7 @@ const Index = () => {
{error &&
Failed to fetch food!
} - {data ? ( + {data && !error ? (
Your favorite food is {data.food}.
) : (
Loading...
diff --git a/examples/with-firebase-authentication/utils/auth/mapUserData.js b/examples/with-firebase-authentication/utils/auth/mapUserData.js new file mode 100644 index 0000000000000..8036456b72bb6 --- /dev/null +++ b/examples/with-firebase-authentication/utils/auth/mapUserData.js @@ -0,0 +1,8 @@ +export const mapUserData = (user) => { + const { uid, email, xa } = user + return { + id: uid, + email, + token: xa, + } +} diff --git a/examples/with-firebase-authentication/utils/auth/useUser.js b/examples/with-firebase-authentication/utils/auth/useUser.js index 48c58d3cbdf46..cbd58d301e342 100644 --- a/examples/with-firebase-authentication/utils/auth/useUser.js +++ b/examples/with-firebase-authentication/utils/auth/useUser.js @@ -1,9 +1,14 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/router' -import cookies from 'js-cookie' import firebase from 'firebase/app' import 'firebase/auth' import initFirebase from '../auth/initFirebase' +import { + removeUserCookie, + setUserCookie, + getUserFromCookie, +} from './userCookies' +import { mapUserData } from './mapUserData' initFirebase() @@ -17,8 +22,6 @@ const useUser = () => { .signOut() .then(() => { // Sign-out successful. - cookies.remove('auth') - setUser() router.push('/auth') }) .catch((e) => { @@ -27,12 +30,26 @@ const useUser = () => { } useEffect(() => { - const cookie = cookies.get('auth') - if (!cookie) { + // Firebase updates the id token every hour, this + // makes sure the react state and the cookie are + // both kept up to date + firebase.auth().onIdTokenChanged((user) => { + if (user) { + const userData = mapUserData(user) + setUserCookie(userData) + setUser(userData) + } else { + removeUserCookie() + setUser() + } + }) + + const userFromCookie = getUserFromCookie() + if (!userFromCookie) { router.push('/') return } - setUser(JSON.parse(cookie)) + setUser(userFromCookie) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/examples/with-firebase-authentication/utils/auth/userCookies.js b/examples/with-firebase-authentication/utils/auth/userCookies.js new file mode 100644 index 0000000000000..776f02ff46310 --- /dev/null +++ b/examples/with-firebase-authentication/utils/auth/userCookies.js @@ -0,0 +1,19 @@ +import cookies from 'js-cookie' + +export const getUserFromCookie = () => { + const cookie = cookies.get('auth') + if (!cookie) { + return + } + return JSON.parse(cookie) +} + +export const setUserCookie = (user) => { + cookies.set('auth', user, { + // firebase id tokens expire in one hour + // set cookie expiry to match + expires: 1 / 24, + }) +} + +export const removeUserCookie = () => cookies.remove('auth') diff --git a/examples/with-firebase/.env.example b/examples/with-firebase/.env.example deleted file mode 100644 index 266032b8b8e96..0000000000000 --- a/examples/with-firebase/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# TODO. Fill in with Firebase Config -FIREBASE_API_KEY= -FIREBASE_AUTH_DOMAIN= -FIREBASE_DATABASE_URL= -FIREBASE_PROJECT_ID= -FIREBASE_STORAGE_BUCKET= -FIREBASE_MESSAGING_SENDER_ID= -FIREBASE_APP_ID= \ No newline at end of file diff --git a/examples/with-firebase/.env.local.example b/examples/with-firebase/.env.local.example new file mode 100644 index 0000000000000..7e1184d4a4215 --- /dev/null +++ b/examples/with-firebase/.env.local.example @@ -0,0 +1,8 @@ +# TODO. Fill in with Firebase Config +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_DATABASE_URL= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= \ No newline at end of file diff --git a/examples/with-firebase/README.md b/examples/with-firebase/README.md index 96419e36e9124..a34f4e313c173 100644 --- a/examples/with-firebase/README.md +++ b/examples/with-firebase/README.md @@ -1,4 +1,4 @@ -# With Firebase +# Firebase Example This is a simple set up for Firebase for client side applications. @@ -14,8 +14,6 @@ Deploy the example using [Vercel](https://vercel.com): ## How to use -### Using `create-next-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 @@ -24,34 +22,23 @@ npx create-next-app --example with-firebase with-firebase-app yarn create next-app --example with-firebase with-firebase-app ``` -### Download manually +## Configuration -Download the example: +1. [Create a Firebase project](https://console.firebase.google.com/u/0/) and add a new app to it. +2. Create a `.env.local` file and copy the contents of `.env.local.example` into it: ```bash -curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase -cd with-firebase +cp .env.local.example .env.local ``` -### Configuration +3. Set each variable on `.env.local` with your Firebase Configuration (found in "Project settings"). -1. [Create a Firebase project](https://console.firebase.google.com/u/0/) and add a new app to it. -2. Create a `.env` file and copy the contents of `.env.example` into it: +## Deploy on Vercel -```bash -cp .env.example .env -``` +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)). -3. Set each variable on `.env` with your Firebase Configuration (found in "Project settings"). +### Deploy Your Local Project -Install it and run: - -```bash -npm install -npm run dev -# or -yarn -yarn dev -``` +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). -Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. diff --git a/examples/with-firebase/firebase/clientApp.js b/examples/with-firebase/firebase/clientApp.js index 32036c70c7760..7c7f8f7099898 100644 --- a/examples/with-firebase/firebase/clientApp.js +++ b/examples/with-firebase/firebase/clientApp.js @@ -5,13 +5,13 @@ import 'firebase/storage' // If you need it import 'firebase/analytics' // If you need it const clientCredentials = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.FIREBASE_DATABASE_URL, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, + 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, } // Check that `window` is in scope for the analytics module! diff --git a/examples/with-firebase/next.config.js b/examples/with-firebase/next.config.js deleted file mode 100644 index 7bd8ac5a27654..0000000000000 --- a/examples/with-firebase/next.config.js +++ /dev/null @@ -1,14 +0,0 @@ -// On production, variables are set with `now secrets`. On development, they use the .env file -require('dotenv').config() - -module.exports = { - env: { - FIREBASE_API_KEY: process.env.FIREBASE_API_KEY, - FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN, - FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL, - FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID, - FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET, - FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID, - FIREBASE_APP_ID: process.env.FIREBASE_APP_ID, - }, -} diff --git a/examples/with-firebase/package.json b/examples/with-firebase/package.json index 828fa643cbe29..b5ff633b4e95f 100644 --- a/examples/with-firebase/package.json +++ b/examples/with-firebase/package.json @@ -7,12 +7,10 @@ "start": "next start" }, "dependencies": { - "firebase": "7.11.0", + "firebase": "7.17.0", "next": "latest", "react": "^16.13.0", "react-dom": "^16.13.0" }, - "devDependencies": { - "dotenv": "8.2.0" - } + "license": "MIT" } diff --git a/examples/with-glamor/pages/_document.js b/examples/with-glamor/pages/_document.js index 83dadc4a9d030..4bd79e43d38cc 100644 --- a/examples/with-glamor/pages/_document.js +++ b/examples/with-glamor/pages/_document.js @@ -3,7 +3,7 @@ import { renderStatic } from 'glamor/server' class MyDocument extends Document { static async getInitialProps({ renderPage }) { - const page = renderPage() + const page = await renderPage() const { css, ids } = renderStatic(() => page.html || page.errorHtml) return { ...page, css, ids } } diff --git a/examples/with-goober/pages/_document.js b/examples/with-goober/pages/_document.js index 505389b1ee106..bbd38e55885d3 100644 --- a/examples/with-goober/pages/_document.js +++ b/examples/with-goober/pages/_document.js @@ -2,9 +2,8 @@ import Document, { Head, Main, NextScript } from 'next/document' import { extractCss } from 'goober' export default class MyDocument extends Document { - static getInitialProps({ renderPage }) { - const page = renderPage() - + static async getInitialProps({ renderPage }) { + const page = await renderPage() // Extrach the css for each page render const css = extractCss() return { ...page, css } diff --git a/examples/with-mongodb-mongoose/components/Form.js b/examples/with-mongodb-mongoose/components/Form.js index 0336c5fb5c8c2..968df84a86ba6 100644 --- a/examples/with-mongodb-mongoose/components/Form.js +++ b/examples/with-mongodb-mongoose/components/Form.js @@ -33,6 +33,12 @@ const Form = ({ formId, petForm, forNewPet = true }) => { }, body: JSON.stringify(form), }) + + // Throw error with status code in case Fetch API req failed + if (!res.ok) { + throw new Error(res.status) + } + const { data } = await res.json() mutate(`/api/pets/${id}`, data, false) // Update the local data without a revalidation @@ -45,7 +51,7 @@ const Form = ({ formId, petForm, forNewPet = true }) => { /* The POST method adds a new entry in the mongodb database. */ const postData = async (form) => { try { - await fetch('/api/pets', { + const res = await fetch('/api/pets', { method: 'POST', headers: { Accept: contentType, @@ -53,6 +59,12 @@ const Form = ({ formId, petForm, forNewPet = true }) => { }, body: JSON.stringify(form), }) + + // Throw error with status code in case Fetch API req failed + if (!res.ok) { + throw new Error(res.status) + } + router.push('/') } catch (error) { setMessage('Failed to add pet') diff --git a/examples/with-react-native-web/pages/_document.js b/examples/with-react-native-web/pages/_document.js index e55bc7962bfa8..6363c1820fc02 100644 --- a/examples/with-react-native-web/pages/_document.js +++ b/examples/with-react-native-web/pages/_document.js @@ -15,7 +15,7 @@ export default class MyDocument extends Document { static async getInitialProps({ renderPage }) { AppRegistry.registerComponent(config.name, () => Main) const { getStyleElement } = AppRegistry.getApplication(config.name) - const page = renderPage() + const page = await renderPage() const styles = [