From 3a2b88b3077efb4f4cde14e706f0673471d7ad97 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 5 May 2021 14:02:14 +0200 Subject: [PATCH 01/19] demo serving storybook static build with serve (#24812) Unfortunately I couldn't push up changes for that branch as maintainers access was disabled. Closes #18775 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. ## Documentation / Examples - [ ] Make sure the linting passes --- examples/with-storybook/.gitignore | 2 +- examples/with-storybook/package.json | 6 ++++-- examples/with-storybook/public/serve.json | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 examples/with-storybook/public/serve.json diff --git a/examples/with-storybook/.gitignore b/examples/with-storybook/.gitignore index a423604f97788..5f31e17fdd42c 100644 --- a/examples/with-storybook/.gitignore +++ b/examples/with-storybook/.gitignore @@ -34,4 +34,4 @@ yarn-error.log* .vercel # Storybook -/storybook-static \ No newline at end of file +/storybook-static diff --git a/examples/with-storybook/package.json b/examples/with-storybook/package.json index 3b151db4e3b42..2e6727d545bc0 100644 --- a/examples/with-storybook/package.json +++ b/examples/with-storybook/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook" + "build-storybook": "build-storybook -s public/", + "serve-storybook": "serve storybook-static" }, "dependencies": { "next": "latest", @@ -19,6 +20,7 @@ "@storybook/addon-essentials": "6.0.26", "@storybook/addon-links": "6.0.26", "@storybook/react": "6.0.26", - "babel-loader": "^8.0.5" + "babel-loader": "^8.0.5", + "serve": "11.3.2" } } diff --git a/examples/with-storybook/public/serve.json b/examples/with-storybook/public/serve.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/examples/with-storybook/public/serve.json @@ -0,0 +1 @@ +{} From fc9b48b5b8ed1e7e61e27f428269b372d75c5a8a Mon Sep 17 00:00:00 2001 From: Lau Wei Lim Date: Wed, 5 May 2021 20:18:46 +0800 Subject: [PATCH 02/19] Update signin/signup form samples (#24524) Fix warning: Prop `href` did not match. Server: "signin" Client: "/signin" Fix warning: Prop `href` did not match. Server: "signup" Client: "/signup" ## Documentation / Examples - [] Make sure the linting passes --- .../api-routes-apollo-server-and-client-auth/pages/signin.js | 2 +- .../api-routes-apollo-server-and-client-auth/pages/signup.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js index 0e652164cffef..59349fb0e57dc 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js @@ -65,7 +65,7 @@ function SignIn() { label="Password" /> or{' '} - + Sign up diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js index ce16f159e4c94..bcbf81f2410c3 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js @@ -60,7 +60,7 @@ function SignUp() { label="Password" /> or{' '} - + Sign in From 243472f34d58e516c8980eadccb276ff85611f9f Mon Sep 17 00:00:00 2001 From: Rishabh Poddar Date: Wed, 5 May 2021 18:08:21 +0530 Subject: [PATCH 03/19] Bumps version of supertokens dependencies and updates its README (#24571) Co-authored-by: Lee Robinson Co-authored-by: NkxxkN Co-authored-by: NkxxkN --- examples/with-supertokens/README.md | 8 +++++++- examples/with-supertokens/config/supertokensConfig.js | 1 - examples/with-supertokens/package.json | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/with-supertokens/README.md b/examples/with-supertokens/README.md index f8c8232b192f4..e9e856ae58c63 100644 --- a/examples/with-supertokens/README.md +++ b/examples/with-supertokens/README.md @@ -10,7 +10,7 @@ Deploy the example using [Vercel](https://vercel.com): ## 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: +- 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-supertokens with-supertokens-app @@ -18,8 +18,14 @@ npx create-next-app --example with-supertokens with-supertokens-app yarn create next-app --example with-supertokens with-supertokens-app ``` +- Run `yarn install` + +- Run `npm run dev` to start the application on `http://localhost:3000`. + ## Configuration +> Until you do this, social login will not work. But you can still try out email password sign up / in. + - Create a `.env.local` file and copy the content of `.env.local.example` into it: ```bash diff --git a/examples/with-supertokens/config/supertokensConfig.js b/examples/with-supertokens/config/supertokensConfig.js index 720cc8d247049..b6509c888f13e 100644 --- a/examples/with-supertokens/config/supertokensConfig.js +++ b/examples/with-supertokens/config/supertokensConfig.js @@ -49,7 +49,6 @@ export let backendConfig = () => { export let frontendConfig = () => { return { - useReactRouterDom: false, appInfo, recipeList: [ ThirdPartyEmailPasswordReact.init({ diff --git a/examples/with-supertokens/package.json b/examples/with-supertokens/package.json index b529f3023465a..ac40d1262d1a4 100644 --- a/examples/with-supertokens/package.json +++ b/examples/with-supertokens/package.json @@ -11,8 +11,8 @@ "next": "latest", "react": "17.0.1", "react-dom": "17.0.1", - "supertokens-auth-react": "^0.9.0", - "supertokens-node": "^4.3.0" + "supertokens-auth-react": "^0.12.0", + "supertokens-node": "^5.0.0" }, "license": "MIT" } From 9721aaf73a2dbd8c69d208838d751b2254932fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ayd=C4=B1n=20Akan?= Date: Wed, 5 May 2021 16:47:15 +0300 Subject: [PATCH 04/19] react-hook-form example (#21245) Co-authored-by: Tim Neutkens --- examples/with-react-hook-form/.gitignore | 34 +++++++ examples/with-react-hook-form/README.md | 23 +++++ examples/with-react-hook-form/package.json | 16 +++ examples/with-react-hook-form/pages/_app.js | 5 + examples/with-react-hook-form/pages/index.js | 75 ++++++++++++++ .../with-react-hook-form/styles/global.css | 97 +++++++++++++++++++ 6 files changed, 250 insertions(+) create mode 100644 examples/with-react-hook-form/.gitignore create mode 100644 examples/with-react-hook-form/README.md create mode 100644 examples/with-react-hook-form/package.json create mode 100644 examples/with-react-hook-form/pages/_app.js create mode 100644 examples/with-react-hook-form/pages/index.js create mode 100644 examples/with-react-hook-form/styles/global.css diff --git a/examples/with-react-hook-form/.gitignore b/examples/with-react-hook-form/.gitignore new file mode 100644 index 0000000000000..e3b3fe7726885 --- /dev/null +++ b/examples/with-react-hook-form/.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 \ No newline at end of file diff --git a/examples/with-react-hook-form/README.md b/examples/with-react-hook-form/README.md new file mode 100644 index 0000000000000..20ef12175a311 --- /dev/null +++ b/examples/with-react-hook-form/README.md @@ -0,0 +1,23 @@ +# with react-hook-form + +This example shows how to integrate react-hook-form in Next.js + +Form handling doesn't have to be painful. React Hook Form will help you write less code while achieving better performance. For more information, see [react-hook-form](https://react-hook-form.com) + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com/now): + +[![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-react-hook-form&project-name=with-react-hook-form&repository-name=with-react-hook-form) + +## 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-react-hook-form with-react-hook-form-app +# or +yarn create next-app --example with-react-hook-form with-react-hook-form-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/with-react-hook-form/package.json b/examples/with-react-hook-form/package.json new file mode 100644 index 0000000000000..60fa4a2191e6d --- /dev/null +++ b/examples/with-react-hook-form/package.json @@ -0,0 +1,16 @@ +{ + "name": "with-react-hook-form", + "version": "1.0.0", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "17.0.1", + "react-dom": "17.0.1", + "react-hook-form": "7.4.0" + }, + "license": "MIT" +} diff --git a/examples/with-react-hook-form/pages/_app.js b/examples/with-react-hook-form/pages/_app.js new file mode 100644 index 0000000000000..0bd950249faeb --- /dev/null +++ b/examples/with-react-hook-form/pages/_app.js @@ -0,0 +1,5 @@ +import '../styles/global.css' + +export default function MyApp({ Component, pageProps }) { + return +} diff --git a/examples/with-react-hook-form/pages/index.js b/examples/with-react-hook-form/pages/index.js new file mode 100644 index 0000000000000..b063590ee6044 --- /dev/null +++ b/examples/with-react-hook-form/pages/index.js @@ -0,0 +1,75 @@ +import { useState } from 'react' +import { useForm } from 'react-hook-form' + +const IndexPage = () => { + const [user, setUser] = useState() + const { + register, + formState: { errors }, + handleSubmit, + } = useForm() + const onSubmit = ({ username, password, remember }) => { + // You should handle login logic with username, password and remember form data + setUser({ name: username }) + } + + return ( +
+ {user ? ( + Hello, {user.name}! + ) : ( +
+
+

LOGIN

+
+
+ + {errors.username && ( + {errors.username.message} + )} +
+
+ + {errors.password && ( + {errors.password.message} + )} +
+
+ + +
+
+ +
+
+ )} +
+ ) +} + +export default IndexPage diff --git a/examples/with-react-hook-form/styles/global.css b/examples/with-react-hook-form/styles/global.css new file mode 100644 index 0000000000000..9cfcfe629d991 --- /dev/null +++ b/examples/with-react-hook-form/styles/global.css @@ -0,0 +1,97 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); + +:root { + --white: #fff; + --light-border: #ccc; + --danger: #ff0000; + --primary-color: #a4a4f7; + --primary-color-light: #7373f9; + --secondary-color: #f0f8ff; + --shadow-color: #ddd; + font-family: 'Poppins', sans-serif; + font-size: 12px; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.container { + display: flex; + height: 100vh; + justify-content: center; + align-items: center; +} + +.row { + margin: 10px 20px; +} + +.row-remember { + display: flex; + align-items: center; + border-bottom: 1px solid var(--light-border); + padding: 0 0 15px 0; +} + +.container form { + background-color: var(--secondary-color); + padding: 2rem; + border-radius: 5px; + box-shadow: 12px 12px 10px var(--shadow-color); + flex: 1; +} + +@media screen and (min-width: 768px) { + .container form { + flex: 0.5; + max-width: 600px; + } +} + +.form-header { + text-align: center; + font-size: 1.5rem; +} + +.form-field { + width: 100%; + padding: 10px; + outline: none; + border: 1px solid var(--light-border); + border-radius: 5px; +} + +.form-field.has-error { + border: 1px solid var(--danger); +} + +.error-label { + color: var(--danger); +} + +.remember-label { + margin: 0 10px; +} + +.btn { + width: 100%; + height: 3rem; + border: 1px solid var(--light-border); + border-radius: 5px; +} + +.btn:hover { + background-color: var(--primary-color-light); +} + +.login-btn { + background-color: var(--primary-color); + color: var(--white); +} + +.hello-user { + font-size: 2rem; +} From 192d42bcacf20e8b501b9c3e7d325c852f6796ee Mon Sep 17 00:00:00 2001 From: Erfan Mirzapour <52346515+ErfanMirzapour@users.noreply.github.com> Date: Wed, 5 May 2021 18:24:36 +0430 Subject: [PATCH 05/19] docs(response-helpers): Update res.json definition (#24782) --- docs/api-routes/response-helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-routes/response-helpers.md b/docs/api-routes/response-helpers.md index 61baff4d5f1e3..0418b2362e446 100644 --- a/docs/api-routes/response-helpers.md +++ b/docs/api-routes/response-helpers.md @@ -23,7 +23,7 @@ export default function handler(req, res) { The included helpers are: - `res.status(code)` - A function to set the status code. `code` must be a valid [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) -- `res.json(json)` - Sends a JSON response. `json` must be a valid JSON object +- `res.json(body)` - Sends a JSON response. `body` must be a [serialiazable 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". From 54ff3223b402b963baa189334587e2e590054276 Mon Sep 17 00:00:00 2001 From: Vitaly Baev Date: Wed, 5 May 2021 17:27:44 +0300 Subject: [PATCH 06/19] Don't throw 500 error when Content-type is invalid (#24818) Fixes #24768 ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added --- packages/next/next-server/server/api-utils.ts | 7 ++++++- .../api-body-parser/test/index.test.js | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/next/next-server/server/api-utils.ts b/packages/next/next-server/server/api-utils.ts index da33727430bb4..350a87dac212f 100644 --- a/packages/next/next-server/server/api-utils.ts +++ b/packages/next/next-server/server/api-utils.ts @@ -114,7 +114,12 @@ export async function parseBody( req: NextApiRequest, limit: string | number ): Promise { - const contentType = parse(req.headers['content-type'] || 'text/plain') + let contentType + try { + contentType = parse(req.headers['content-type'] || 'text/plain') + } catch { + contentType = parse('text/plain') + } const { type, parameters } = contentType const encoding = parameters.charset || 'utf-8' diff --git a/test/integration/api-body-parser/test/index.test.js b/test/integration/api-body-parser/test/index.test.js index ea90f43bdbcf8..c9a9b7200d310 100644 --- a/test/integration/api-body-parser/test/index.test.js +++ b/test/integration/api-body-parser/test/index.test.js @@ -36,6 +36,13 @@ function runTests() { expect(data).toEqual([{ title: 'Nextjs' }]) killApp(server) }) + + it("should not throw if request's content-type is invalid", async () => { + await startServer() + const status = await makeRequestWithInvalidContentType() + expect(status).toBe(200) + killApp(server) + }) } async function makeRequest() { @@ -50,6 +57,18 @@ async function makeRequest() { return data } +async function makeRequestWithInvalidContentType() { + const status = await fetchViaHTTP(appPort, '/api', null, { + method: 'POST', + headers: { + 'Content-Type': 'application/json;', + }, + body: JSON.stringify([{ title: 'Nextjs' }]), + }).then((res) => res.status) + + return status +} + const startServer = async (optEnv = {}, opts) => { const scriptPath = join(appDir, 'server.js') context.appPort = appPort = await getPort() From 2251658c352274d160c732ba24c9e06d211b74c8 Mon Sep 17 00:00:00 2001 From: Erfan Mirzapour <52346515+ErfanMirzapour@users.noreply.github.com> Date: Thu, 6 May 2021 00:19:40 +0430 Subject: [PATCH 07/19] docs(next/router): Update router.push api (#24833) Documentation page: https://nextjs.org/docs/api-reference/next/router#routerpush Add `locale` as supported options in `route.push()` (#17898) --- docs/api-reference/next/router.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index 4922e9196d632..5e095a553099f 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -74,6 +74,7 @@ router.push(url, as, options) - `options` - Optional object with the following configuration options: - `scroll` - Optional boolean, controls scrolling to the top of the page after navigation. Defaults to `true` - [`shallow`](/docs/routing/shallow-routing.md): Update the path of the current page without rerunning [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation), [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering) or [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md). Defaults to `false` + - `locale` - Optional string, indicates locale of the new page > You don't need to use `router.push` for external URLs. [window.location](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) is better suited for those cases. From cc41a191fea02235f97cf7f53c1dc63071bb0bac Mon Sep 17 00:00:00 2001 From: Erfan Mirzapour <52346515+ErfanMirzapour@users.noreply.github.com> Date: Thu, 6 May 2021 02:50:23 +0430 Subject: [PATCH 08/19] docs(config intro): Fix github link hash (#24838) Doc page: https://nextjs.org/docs/api-reference/next.config.js/introduction [`config-sahred.ts`](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts) file has been updated and the link here now is pointing to the random line! --- docs/api-reference/next.config.js/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next.config.js/introduction.md b/docs/api-reference/next.config.js/introduction.md index b01cb75745366..474257638f4bc 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -44,7 +44,7 @@ module.exports = (phase, { defaultConfig }) => { } ``` -The commented lines are the place where you can put the configs allowed by `next.config.js`, which are defined [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L33). +The commented lines are the place where you can put the configs allowed by `next.config.js`, which are defined [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L68). However, none of the configs are required, and it's not necessary to understand what each config does. Instead, search for the features you need to enable or modify in this section and they will show you what to do. From 802af5ff5bc5fb76e498b29c129608ec222fa8e3 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 5 May 2021 23:02:07 -0500 Subject: [PATCH 09/19] Land - Font optimizations - Adobe Fonts / Typekit support (#24834) This updates this initial PR here https://github.com/vercel/next.js/pull/18146 to resolve merge conflicts and updates tests since we aren't able to update that PR itself. ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. Closes: https://github.com/vercel/next.js/pull/18146 --- docs/basic-features/font-optimization.md | 2 +- packages/next/next-server/lib/constants.ts | 5 +- packages/next/next-server/lib/head.tsx | 7 +- .../with-google/manifest-snapshot.json | 22 + .../fixtures/with-google/next.config.js | 1 + .../with-google}/pages/_document.js | 0 .../{ => fixtures/with-google}/pages/amp.js | 0 .../{ => fixtures/with-google}/pages/index.js | 0 .../{ => fixtures/with-google}/pages/nonce.js | 0 .../{ => fixtures/with-google}/pages/stars.js | 0 .../with-google}/pages/static-head.js | 0 .../with-google}/pages/with-font.js | 0 .../with-google}/pages/without-font.js | 0 .../{ => fixtures/with-google}/server.js | 0 .../with-typekit/manifest-snapshot.json | 14 + .../fixtures/with-typekit/next.config.js | 1 + .../fixtures/with-typekit/pages/_document.js | 27 ++ .../fixtures/with-typekit/pages/amp.js | 9 + .../fixtures/with-typekit/pages/index.js | 7 + .../fixtures/with-typekit/pages/nonce.js | 19 + .../fixtures/with-typekit/pages/stars.js | 26 ++ .../with-typekit/pages/static-head.js | 15 + .../fixtures/with-typekit/pages/with-font.js | 27 ++ .../with-typekit/pages/without-font.js | 26 ++ .../fixtures/with-typekit/server.js | 111 +++++ .../font-optimization/test/index.test.js | 423 ++++++++++-------- .../test/manifest-snapshot.json | 14 - 27 files changed, 545 insertions(+), 211 deletions(-) create mode 100644 test/integration/font-optimization/fixtures/with-google/manifest-snapshot.json create mode 100644 test/integration/font-optimization/fixtures/with-google/next.config.js rename test/integration/font-optimization/{ => fixtures/with-google}/pages/_document.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/amp.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/index.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/nonce.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/stars.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/static-head.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/with-font.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/pages/without-font.js (100%) rename test/integration/font-optimization/{ => fixtures/with-google}/server.js (100%) create mode 100644 test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json create mode 100644 test/integration/font-optimization/fixtures/with-typekit/next.config.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/_document.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/amp.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/index.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/nonce.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/stars.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/static-head.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/with-font.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/pages/without-font.js create mode 100644 test/integration/font-optimization/fixtures/with-typekit/server.js delete mode 100644 test/integration/font-optimization/test/manifest-snapshot.json diff --git a/docs/basic-features/font-optimization.md b/docs/basic-features/font-optimization.md index aebf952d77376..1c63fda0332ca 100644 --- a/docs/basic-features/font-optimization.md +++ b/docs/basic-features/font-optimization.md @@ -74,7 +74,7 @@ class MyDocument extends Document { export default MyDocument ``` -Automatic Webfont Optimization currently supports Google Fonts, 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. +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. ## Disabling Optimization diff --git a/packages/next/next-server/lib/constants.ts b/packages/next/next-server/lib/constants.ts index c07b817533ce2..eed0b3877ba92 100644 --- a/packages/next/next-server/lib/constants.ts +++ b/packages/next/next-server/lib/constants.ts @@ -37,5 +37,8 @@ export const TEMPORARY_REDIRECT_STATUS = 307 export const PERMANENT_REDIRECT_STATUS = 308 export const STATIC_PROPS_ID = '__N_SSG' export const SERVER_PROPS_ID = '__N_SSP' -export const OPTIMIZED_FONT_PROVIDERS = ['https://fonts.googleapis.com/css'] +export const OPTIMIZED_FONT_PROVIDERS = [ + 'https://fonts.googleapis.com/css', + 'https://use.typekit.net/', +] export const STATIC_STATUS_PAGES = ['/500'] diff --git a/packages/next/next-server/lib/head.tsx b/packages/next/next-server/lib/head.tsx index 2a6fa5a40e13b..f035ef5797485 100644 --- a/packages/next/next-server/lib/head.tsx +++ b/packages/next/next-server/lib/head.tsx @@ -147,9 +147,10 @@ function reduceComponents( c.type === 'link' && c.props['href'] && // TODO(prateekbh@): Replace this with const from `constants` when the tree shaking works. - ['https://fonts.googleapis.com/css'].some((url) => - c.props['href'].startsWith(url) - ) + [ + 'https://fonts.googleapis.com/css', + 'https://use.typekit.net/', + ].some((url) => c.props['href'].startsWith(url)) ) { const newProps = { ...(c.props || {}) } newProps['data-href'] = newProps['href'] diff --git a/test/integration/font-optimization/fixtures/with-google/manifest-snapshot.json b/test/integration/font-optimization/fixtures/with-google/manifest-snapshot.json new file mode 100644 index 0000000000000..766f70f7ae879 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-google/manifest-snapshot.json @@ -0,0 +1,22 @@ +[ + { + "url": "https://fonts.googleapis.com/css?family=Voces", + "content": "@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PGDmk.woff) format('woff')}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PIDm_6pClI_ik.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Voces';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/voces/v12/-F6_fjJyLyU8d7PGDm_6pClI.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}" + }, + { + "url": "https://fonts.googleapis.com/css2?family=Modak", + "content": "@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEsnME.woff) format('woff')}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMB-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0900-097F,U+1CD0-1CF6,U+1CF8-1CF9,U+200C-200D,U+20A8,U+20B9,U+25CC,U+A830-A839,U+A8E0-A8FB}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMO-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMA-hR77LKV.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}" + }, + { + "url": "https://fonts.googleapis.com/css2?family=Modak", + "content": "@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEsnME.woff) format('woff')}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMB-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0900-097F,U+1CD0-1CF6,U+1CF8-1CF9,U+200C-200D,U+20A8,U+20B9,U+25CC,U+A830-A839,U+A8E0-A8FB}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMO-hR77LKVTy8.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Modak';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/modak/v8/EJRYQgs1XtIEskMA-hR77LKV.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}" + }, + { + "url": "https://fonts.googleapis.com/css2?family=Roboto:wght@700", + "content": "@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlvAA.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}" + }, + { + "url": "https://fonts.googleapis.com/css2?family=Roboto:wght@400;700;900&display=swap", + "content": "@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Me5g.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlvAA.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtvAA.woff) format('woff')}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfCRc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfABc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfCBc4AMP6lbBP.woff2) format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfBxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfCxc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfChc4AMP6lbBP.woff2) format('woff2');unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:'Roboto';font-style:normal;font-weight:900;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmYUtfBBc4AMP6lQ.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}" + } +] diff --git a/test/integration/font-optimization/fixtures/with-google/next.config.js b/test/integration/font-optimization/fixtures/with-google/next.config.js new file mode 100644 index 0000000000000..4ba52ba2c8df6 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-google/next.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/integration/font-optimization/pages/_document.js b/test/integration/font-optimization/fixtures/with-google/pages/_document.js similarity index 100% rename from test/integration/font-optimization/pages/_document.js rename to test/integration/font-optimization/fixtures/with-google/pages/_document.js diff --git a/test/integration/font-optimization/pages/amp.js b/test/integration/font-optimization/fixtures/with-google/pages/amp.js similarity index 100% rename from test/integration/font-optimization/pages/amp.js rename to test/integration/font-optimization/fixtures/with-google/pages/amp.js diff --git a/test/integration/font-optimization/pages/index.js b/test/integration/font-optimization/fixtures/with-google/pages/index.js similarity index 100% rename from test/integration/font-optimization/pages/index.js rename to test/integration/font-optimization/fixtures/with-google/pages/index.js diff --git a/test/integration/font-optimization/pages/nonce.js b/test/integration/font-optimization/fixtures/with-google/pages/nonce.js similarity index 100% rename from test/integration/font-optimization/pages/nonce.js rename to test/integration/font-optimization/fixtures/with-google/pages/nonce.js diff --git a/test/integration/font-optimization/pages/stars.js b/test/integration/font-optimization/fixtures/with-google/pages/stars.js similarity index 100% rename from test/integration/font-optimization/pages/stars.js rename to test/integration/font-optimization/fixtures/with-google/pages/stars.js diff --git a/test/integration/font-optimization/pages/static-head.js b/test/integration/font-optimization/fixtures/with-google/pages/static-head.js similarity index 100% rename from test/integration/font-optimization/pages/static-head.js rename to test/integration/font-optimization/fixtures/with-google/pages/static-head.js diff --git a/test/integration/font-optimization/pages/with-font.js b/test/integration/font-optimization/fixtures/with-google/pages/with-font.js similarity index 100% rename from test/integration/font-optimization/pages/with-font.js rename to test/integration/font-optimization/fixtures/with-google/pages/with-font.js diff --git a/test/integration/font-optimization/pages/without-font.js b/test/integration/font-optimization/fixtures/with-google/pages/without-font.js similarity index 100% rename from test/integration/font-optimization/pages/without-font.js rename to test/integration/font-optimization/fixtures/with-google/pages/without-font.js diff --git a/test/integration/font-optimization/server.js b/test/integration/font-optimization/fixtures/with-google/server.js similarity index 100% rename from test/integration/font-optimization/server.js rename to test/integration/font-optimization/fixtures/with-google/server.js diff --git a/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json b/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json new file mode 100644 index 0000000000000..42681af1533b5 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json @@ -0,0 +1,14 @@ +[ + { + "url": "https://use.typekit.net/plm1izr.css", + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=plm1izr&ht=tk&f=32266&a=23152309&app=typekit&e=css\");@font-face{font-family:\"birra-2\";src:url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:700}.tk-birra-2{font-family:\"birra-2\",serif}@import url(\"https://p.typekit.net/p.css?s=1&k=plm1izr&ht=tk&f=32266&a=23152309&app=typekit&e=css\");@font-face{font-family:\"birra-2\";src:url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:700}.tk-birra-2{font-family:\"birra-2\",serif}" + }, + { + "url": "https://use.typekit.net/ucs7mcf.css", + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=ucs7mcf&ht=tk&f=43886&a=23152309&app=typekit&e=css\");@font-face{font-family:\"flegrei\";src:url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-flegrei{font-family:\"flegrei\",sans-serif}@import url(\"https://p.typekit.net/p.css?s=1&k=ucs7mcf&ht=tk&f=43886&a=23152309&app=typekit&e=css\");@font-face{font-family:\"flegrei\";src:url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-flegrei{font-family:\"flegrei\",sans-serif}" + }, + { + "url": "https://use.typekit.net/erd0sed.css", + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=erd0sed&ht=tk&f=43885&a=23152309&app=typekit&e=css\");@font-face{font-family:\"pantelleria\";src:url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-pantelleria{font-family:\"pantelleria\",sans-serif}@import url(\"https://p.typekit.net/p.css?s=1&k=erd0sed&ht=tk&f=43885&a=23152309&app=typekit&e=css\");@font-face{font-family:\"pantelleria\";src:url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-pantelleria{font-family:\"pantelleria\",sans-serif}" + } +] diff --git a/test/integration/font-optimization/fixtures/with-typekit/next.config.js b/test/integration/font-optimization/fixtures/with-typekit/next.config.js new file mode 100644 index 0000000000000..4ba52ba2c8df6 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/next.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js b/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js new file mode 100644 index 0000000000000..33cd61eee7ef8 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/_document.js @@ -0,0 +1,27 @@ +import * as React from 'react' +/// @ts-ignore +import Document, { Main, NextScript, Head } from 'next/document' + +export default class MyDocument extends Document { + constructor(props) { + super(props) + const { __NEXT_DATA__, ids } = props + if (ids) { + __NEXT_DATA__.ids = ids + } + } + + render() { + return ( + + + + + +
+ + + + ) + } +} diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/amp.js b/test/integration/font-optimization/fixtures/with-typekit/pages/amp.js new file mode 100644 index 0000000000000..53ac86b915b16 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/amp.js @@ -0,0 +1,9 @@ +import React from 'react' + +const Page = () => { + return
Hi!
+} + +export const config = { amp: true } + +export default Page diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/index.js b/test/integration/font-optimization/fixtures/with-typekit/pages/index.js new file mode 100644 index 0000000000000..39cf2d1f7b376 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/index.js @@ -0,0 +1,7 @@ +import React from 'react' + +const Page = () => { + return
Hi!
+} + +export default Page diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/nonce.js b/test/integration/font-optimization/fixtures/with-typekit/pages/nonce.js new file mode 100644 index 0000000000000..2c6b7e91af930 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/nonce.js @@ -0,0 +1,19 @@ +import React from 'react' +import Head from 'next/head' + +const Page = () => { + return ( + <> + + + +
Hi!
+ + ) +} + +export default Page diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/stars.js b/test/integration/font-optimization/fixtures/with-typekit/pages/stars.js new file mode 100644 index 0000000000000..c8ebb251db270 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/stars.js @@ -0,0 +1,26 @@ +import Head from 'next/head' + +function Home({ stars }) { + return ( +
+ + Create Next App + + + + +
+
Next stars: {stars}
+
+
+ ) +} + +Home.getInitialProps = async () => { + return { stars: Math.random() * 1000 } +} + +export default Home diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/static-head.js b/test/integration/font-optimization/fixtures/with-typekit/pages/static-head.js new file mode 100644 index 0000000000000..9b0ba8af28a32 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/static-head.js @@ -0,0 +1,15 @@ +import React from 'react' +import Head from 'next/head' + +const Page = () => { + return ( + <> + + + +
Hi!
+ + ) +} + +export default Page diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/with-font.js b/test/integration/font-optimization/fixtures/with-typekit/pages/with-font.js new file mode 100644 index 0000000000000..b1a1fa4011d49 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/with-font.js @@ -0,0 +1,27 @@ +import Head from 'next/head' +import Link from 'next/link' + +const WithFont = () => { + return ( + <> + + + + +
+ Page with custom fonts +
+
+ Without font +
+ + ) +} + +export const getServerSideProps = async () => { + return { + props: {}, + } +} + +export default WithFont diff --git a/test/integration/font-optimization/fixtures/with-typekit/pages/without-font.js b/test/integration/font-optimization/fixtures/with-typekit/pages/without-font.js new file mode 100644 index 0000000000000..2185cb252e07e --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/pages/without-font.js @@ -0,0 +1,26 @@ +import Head from 'next/head' +import Link from 'next/link' + +const WithoutFont = () => { + return ( + <> + + + +
+ Page without custom fonts +
+
+ With font +
+ + ) +} + +export const getServerSideProps = async () => { + return { + props: {}, + } +} + +export default WithoutFont diff --git a/test/integration/font-optimization/fixtures/with-typekit/server.js b/test/integration/font-optimization/fixtures/with-typekit/server.js new file mode 100644 index 0000000000000..6a98fa3d30806 --- /dev/null +++ b/test/integration/font-optimization/fixtures/with-typekit/server.js @@ -0,0 +1,111 @@ +const http = require('http') +const url = require('url') +const fs = require('fs') +const path = require('path') +const server = http.createServer(async (req, res) => { + let { pathname } = url.parse(req.url) + pathname = pathname.replace(/\/$/, '') + let isDataReq = false + if (pathname.startsWith('/_next/data')) { + isDataReq = true + pathname = pathname + .replace(`/_next/data/${process.env.BUILD_ID}/`, '/') + .replace(/\.json$/, '') + } + console.log('serving', pathname) + + if (pathname === '/favicon.ico') { + res.statusCode = 404 + return res.end() + } + + if (pathname.startsWith('/_next/static/')) { + res.write( + fs.readFileSync( + path.join( + __dirname, + './.next/static/', + decodeURI(pathname.slice('/_next/static/'.length)) + ), + 'utf8' + ) + ) + return res.end() + } else { + const ext = isDataReq ? 'json' : 'html' + if ( + fs.existsSync( + path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`) + ) + ) { + res.write( + fs.readFileSync( + path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`), + 'utf8' + ) + ) + return res.end() + } + + let re + try { + re = require(`./.next/serverless/pages${pathname}`) + } catch { + const d = decodeURI(pathname) + if ( + fs.existsSync( + path.join(__dirname, `./.next/serverless/pages${d}.${ext}`) + ) + ) { + res.write( + fs.readFileSync( + path.join(__dirname, `./.next/serverless/pages${d}.${ext}`), + 'utf8' + ) + ) + return res.end() + } + + const routesManifest = require('./.next/routes-manifest.json') + const { dynamicRoutes } = routesManifest + dynamicRoutes.some(({ page, regex }) => { + if (new RegExp(regex).test(pathname)) { + if ( + fs.existsSync( + path.join(__dirname, `./.next/serverless/pages${page}.${ext}`) + ) + ) { + res.write( + fs.readFileSync( + path.join(__dirname, `./.next/serverless/pages${page}.${ext}`), + 'utf8' + ) + ) + res.end() + return true + } + + re = require(`./.next/serverless/pages${page}`) + return true + } + return false + }) + } + if (!res.finished) { + try { + return await (typeof re.render === 'function' + ? re.render(req, res) + : re.default(req, res)) + } catch (e) { + console.log('FAIL_FUNCTION', e) + res.statusCode = 500 + res.write('FAIL_FUNCTION') + res.end() + } + } + } +}) + +server.listen(process.env.PORT, () => { + console.log('ready on', process.env.PORT) +}) diff --git a/test/integration/font-optimization/test/index.test.js b/test/integration/font-optimization/test/index.test.js index 317a3477d39a8..76b5d4b5d9c4a 100644 --- a/test/integration/font-optimization/test/index.test.js +++ b/test/integration/font-optimization/test/index.test.js @@ -1,6 +1,8 @@ /* eslint-env jest */ +import cheerio from 'cheerio' import { join } from 'path' +import fs from 'fs-extra' import { killApp, findPort, @@ -9,17 +11,10 @@ import { renderViaHTTP, initNextServerScript, } from 'next-test-utils' -import fs from 'fs-extra' -import cheerio from 'cheerio' jest.setTimeout(1000 * 60 * 2) -const appDir = join(__dirname, '../') -const nextConfig = join(appDir, 'next.config.js') -let builtServerPagesDir -let builtPage -let appPort -let app +const fixturesDir = join(__dirname, '..', 'fixtures') const fsExists = (file) => fs @@ -27,7 +22,7 @@ const fsExists = (file) => .then(() => true) .catch(() => false) -async function getBuildId() { +async function getBuildId(appDir) { return fs.readFile(join(appDir, '.next', 'BUILD_ID'), 'utf8') } @@ -36,196 +31,240 @@ const startServerlessEmulator = async (dir, port) => { const env = Object.assign( {}, { ...process.env }, - { PORT: port, BUILD_ID: await getBuildId() } + { PORT: port, BUILD_ID: await getBuildId(dir) } ) return initNextServerScript(scriptPath, /ready on/i, env) } -function runTests() { - it('should inline the google fonts for static pages', async () => { - const html = await renderViaHTTP(appPort, '/index') - expect(await fsExists(builtPage('font-manifest.json'))).toBe(true) - expect(html).toContain( - '' - ) - expect(html).toMatch( - /