diff --git a/contributing.md b/contributing.md
index 6bfc13c554a6f..eb5c27aded797 100644
--- a/contributing.md
+++ b/contributing.md
@@ -3,7 +3,7 @@
Our Commitment to Open Source can be found [here](https://vercel.com/oss).
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
-2. Create a new branch `git checkout -b MY_BRANCH_NAME`
+2. Create a new branch: `git checkout -b MY_BRANCH_NAME`
3. Install yarn: `npm install -g yarn`
4. Install the dependencies: `yarn`
5. Run `yarn dev` to build and watch for code changes
diff --git a/docs/migrating/from-create-react-app.md b/docs/migrating/from-create-react-app.md
index 83a0eb481d7a1..09d5bbb1f1b37 100644
--- a/docs/migrating/from-create-react-app.md
+++ b/docs/migrating/from-create-react-app.md
@@ -199,12 +199,12 @@ export default function SEO({ description, title, siteTitle }) {
## Single-Page App (SPA)
-If you want to move your existing Create React App to Next.js and keep a Single-Page App, you can move your old application entry point to an [Optional Catch-All Route](/docs/routing/dynamic-routes.md#optional-catch-all-routes) named `pages/[[…app]].js`.
+If you want to move your existing Create React App to Next.js and keep a Single-Page App, you can move your old application's entry point to an [Optional Catch-All Route](/docs/routing/dynamic-routes.md#optional-catch-all-routes) named `pages/[[…app]].js`.
```jsx
// pages/[[...app]].js
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import CreateReactAppEntryPoint from '../components/app'
function App() {
diff --git a/examples/blog/.gitignore b/examples/blog/.gitignore
new file mode 100644
index 0000000000000..18088234abe02
--- /dev/null
+++ b/examples/blog/.gitignore
@@ -0,0 +1,36 @@
+# 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
+
+public/feed.xml
\ No newline at end of file
diff --git a/examples/blog/README.md b/examples/blog/README.md
new file mode 100644
index 0000000000000..fb4ec38394c84
--- /dev/null
+++ b/examples/blog/README.md
@@ -0,0 +1,36 @@
+# Portfolio Starter Kit
+
+This portfolio is built with **Next.js** and a library called [Nextra](https://nextra.vercel.app/). It allows you to write Markdown and focus on the _content_ of your portfolio. This starter includes:
+
+- Automatically configured to handle Markdown/MDX
+- Generates an RSS feed based on your posts
+- A beautiful theme included out of the box
+- Easily categorize posts with tags
+- Fast, optimized web font loading
+
+https://demo.vercel.blog
+
+## Configuration
+
+1. Update your name in `theme.config.js` or change the footer.
+1. Update your name and site URL for the RSS feed in `scripts/gen-rss.js`.
+1. Update the meta tags in `pages/_document.js`.
+1. Update the posts inside `pages/posts/*.md` with your own content.
+
+## Deploy your own
+
+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/blog&project-name=portfolio&repository-name=portfolio)
+
+## 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 blog my-blog
+# or
+yarn create next-app --example blog my-blog
+```
+
+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/blog/next.config.js b/examples/blog/next.config.js
new file mode 100644
index 0000000000000..499fb45b4ab1e
--- /dev/null
+++ b/examples/blog/next.config.js
@@ -0,0 +1,2 @@
+const withNextra = require('nextra')('nextra-theme-blog', './theme.config.js')
+module.exports = withNextra()
diff --git a/examples/blog/package.json b/examples/blog/package.json
new file mode 100644
index 0000000000000..947846a512b98
--- /dev/null
+++ b/examples/blog/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "portfolio",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "scripts": {
+ "dev": "next",
+ "build": "node ./scripts/gen-rss.js && next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "gray-matter": "^4.0.2",
+ "next": "latest",
+ "nextra": "^0.4.3",
+ "nextra-theme-blog": "^0.1.4",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
+ "rss": "^1.2.2"
+ },
+ "prettier": {
+ "arrowParens": "always",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "none",
+ "semi": false
+ }
+}
diff --git a/examples/blog/pages/_app.js b/examples/blog/pages/_app.js
new file mode 100644
index 0000000000000..803a85e066426
--- /dev/null
+++ b/examples/blog/pages/_app.js
@@ -0,0 +1,27 @@
+import 'nextra-theme-blog/style.css'
+import Head from 'next/head'
+
+import '../styles/main.css'
+
+export default function Nextra({ Component, pageProps }) {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/examples/blog/pages/_document.js b/examples/blog/pages/_document.js
new file mode 100644
index 0000000000000..bd31c09abe7ed
--- /dev/null
+++ b/examples/blog/pages/_document.js
@@ -0,0 +1,41 @@
+import Document, { Html, Head, Main, NextScript } from 'next/document'
+
+class MyDocument extends Document {
+ static async getInitialProps(ctx) {
+ const initialProps = await Document.getInitialProps(ctx)
+ return { ...initialProps }
+ }
+
+ render() {
+ const meta = {
+ title: 'Next.js Blog Starter Kit',
+ description: 'Clone and deploy your own Next.js portfolio in minutes.',
+ image:
+ 'https://assets.vercel.com/image/upload/q_auto/front/vercel/dps.png'
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default MyDocument
diff --git a/examples/blog/pages/index.mdx b/examples/blog/pages/index.mdx
new file mode 100644
index 0000000000000..da48626824c8a
--- /dev/null
+++ b/examples/blog/pages/index.mdx
@@ -0,0 +1,46 @@
+---
+type: page
+title: About
+date: 2021-03-19
+---
+
+# Your Name
+
+Hey, I'm a Senior Software Engineer at Company. I enjoy working with Next.js and crafting beautiful front-end experiences.
+
+This portfolio is built with **Next.js** and a library called [Nextra](https://nextra.vercel.app/). It allows you to write Markdown and focus on the _content_ of your portfolio.
+
+[**Deploy your own**](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/blog&project-name=portfolio&repository-name=portfolio) in a few minutes.
+
+---
+
+
+ Twitter [@yourname](https://twitter.com/yourname)
+
+ GitHub [@yourname](https://github.com/yourname)
+
+ Instagram [@yourname](https://instagram.com/yourname)
+
+ Email your@name.com
+
+
+
+
+### Experience
+
+- Senior Software Engineer at Company 2021–
+- Software Engineer at Company, 2017–2021
+- Bachelor of Computer Science at Your University, 2013–2017
+
+### Projects
+
+- [Next.js](https://nextjs.org)
+- [Nextra](https://nextra.vercel.app/)
+- [Vercel](http://vercel.com)
+
+## Skills
+
+- Next.js
+- TypeScript
+- Vercel
+- CSS
diff --git a/examples/blog/pages/photos.mdx b/examples/blog/pages/photos.mdx
new file mode 100644
index 0000000000000..32bc27f7899f2
--- /dev/null
+++ b/examples/blog/pages/photos.mdx
@@ -0,0 +1,31 @@
+---
+type: page
+title: Photos
+date: 2021-03-18
+---
+
+# Photos
+
+Here's some of my photography.
+
+import Image from 'next/image'
+
+
+[Unsplash ↗ ](https://unsplash.com/photos/WeYamle9fDM)
+
+
+[Unsplash ↗ ](https://unsplash.com/photos/ndN00KmbJ1c)
diff --git a/examples/blog/pages/posts/index.md b/examples/blog/pages/posts/index.md
new file mode 100644
index 0000000000000..e985ac7548874
--- /dev/null
+++ b/examples/blog/pages/posts/index.md
@@ -0,0 +1,7 @@
+---
+type: posts
+title: Posts
+date: 2021-03-18
+---
+
+# Posts
diff --git a/examples/blog/pages/posts/markdown.md b/examples/blog/pages/posts/markdown.md
new file mode 100644
index 0000000000000..397e49809d89f
--- /dev/null
+++ b/examples/blog/pages/posts/markdown.md
@@ -0,0 +1,99 @@
+---
+title: Markdown Examples
+date: 2021/3/19
+description: View examples of all possible Markdown options.
+tag: web development
+author: You
+---
+
+# Markdown Examples
+
+## h2 Heading
+
+### h3 Heading
+
+#### h4 Heading
+
+##### h5 Heading
+
+###### h6 Heading
+
+## Emphasis
+
+**This is bold text**
+
+_This is italic text_
+
+~~Strikethrough~~
+
+## Blockquotes
+
+> Develop. Preview. Ship. – Vercel
+
+## Lists
+
+Unordered
+
+- Lorem ipsum dolor sit amet
+- Consectetur adipiscing elit
+- Integer molestie lorem at massa
+
+Ordered
+
+1. Lorem ipsum dolor sit amet
+2. Consectetur adipiscing elit
+3. Integer molestie lorem at massa
+
+## Code
+
+Inline `code`
+
+```
+export default function Nextra({ Component, pageProps }) {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+```
+
+## Tables
+
+| **Option** | **Description** |
+| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
+| First | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
+| Second | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
+| Third | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
+
+## Links
+
+- [Next.js](https://nextjs.org)
+- [Nextra](https://nextra.vercel.app/)
+- [Vercel](http://vercel.com)
+
+### Footnotes
+
+- Footnote [^1].
+- Footnote [^2].
+
+[^1]: Footnote **can have markup**
+
+ and multiple paragraphs.
+
+[^2]: Footnote text.
diff --git a/examples/blog/pages/posts/pages.md b/examples/blog/pages/posts/pages.md
new file mode 100644
index 0000000000000..d97c0afefc3d8
--- /dev/null
+++ b/examples/blog/pages/posts/pages.md
@@ -0,0 +1,238 @@
+---
+title: Next.js Pages
+date: 2021/3/18
+description: Learn more about Next.js pages.
+tag: web development
+author: You
+---
+
+# Next.js Pages
+
+In Next.js, a **page** is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.jsx`, `.ts`, or `.tsx` file in the `pages` directory. Each page is associated with a route based on its file name.
+
+**Example**: If you create `pages/about.js` that exports a React component like below, it will be accessible at `/about`.
+
+```
+function About() {
+ return About
+}
+
+export default About
+```
+
+### Pages with Dynamic Routes
+
+Next.js supports pages with dynamic routes. For example, if you create a file called `pages/posts/[id].js`, then it will be accessible at `posts/1`, `posts/2`, etc.
+
+> To learn more about dynamic routing, check the [Dynamic Routing documentation](/docs/routing/dynamic-routes.md).
+
+## Pre-rendering
+
+By default, Next.js **pre-renders** every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and SEO.
+
+Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. (This process is called _hydration_.)
+
+### Two forms of Pre-rendering
+
+Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
+
+- [**Static Generation (Recommended)**](#static-generation-recommended): The HTML is generated at **build time** and will be reused on each request.
+- [**Server-side Rendering**](#server-side-rendering): The HTML is generated on **each request**.
+
+Importantly, Next.js lets you **choose** which pre-rendering form you'd like to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
+
+We **recommend** using **Static Generation** over Server-side Rendering for performance reasons. Statically generated pages can be cached by CDN with no extra configuration to boost performance. However, in some cases, Server-side Rendering might be the only option.
+
+You can also use **Client-side Rendering** along with Static Generation or Server-side Rendering. That means some parts of a page can be rendered entirely by client side JavaScript. To learn more, take a look at the [Data Fetching](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side) documentation.
+
+## Static Generation (Recommended)
+
+If a page uses **Static Generation**, the page HTML is generated at **build time**. That means in production, the page HTML is generated when you run `next build` . This HTML will then be reused on each request. It can be cached by a CDN.
+
+In Next.js, you can statically generate pages **with or without data**. Let's take a look at each case.
+
+### Static Generation without data
+
+By default, Next.js pre-renders pages using Static Generation without fetching data. Here's an example:
+
+```
+function About() {
+ return About
+}
+
+export default About
+```
+
+Note that this page does not need to fetch any external data to be pre-rendered. In cases like this, Next.js generates a single HTML file per page during build time.
+
+### Static Generation with data
+
+Some pages require fetching external data for pre-rendering. There are two scenarios, and one or both might apply. In each case, you can use a special function Next.js provides:
+
+1. Your page **content** depends on external data: Use `getStaticProps`.
+2. Your page **paths** depend on external data: Use `getStaticPaths` (usually in addition to `getStaticProps`).
+
+#### Scenario 1: Your page **content** depends on external data
+
+**Example**: Your blog page might need to fetch the list of blog posts from a CMS (content management system).
+
+```
+// TODO: Need to fetch `posts` (by calling some API endpoint)
+// before this page can be pre-rendered.
+function Blog({ posts }) {
+ return (
+
+ {posts.map((post) => (
+ {post.title}
+ ))}
+
+ )
+}
+
+export default Blog
+```
+
+To fetch this data on pre-render, Next.js allows you to `export` an `async` function called `getStaticProps` from the same file. This function gets called at build time and lets you pass fetched data to the page's `props` on pre-render.
+
+```
+function Blog({ posts }) {
+ // Render posts...
+}
+
+// This function gets called at build time
+export async function getStaticProps() {
+ // Call an external API endpoint to get posts
+ const res = await fetch('https://.../posts')
+ const posts = await res.json()
+
+ // By returning { props: { posts } }, the Blog component
+ // will receive `posts` as a prop at build time
+ return {
+ props: {
+ posts
+ }
+ }
+}
+
+export default Blog
+```
+
+To learn more about how `getStaticProps` works, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation).
+
+#### Scenario 2: Your page paths depend on external data
+
+Next.js allows you to create pages with **dynamic routes**. For example, you can create a file called `pages/posts/[id].js` to show a single blog post based on `id`. This will allow you to show a blog post with `id: 1` when you access `posts/1`.
+
+> To learn more about dynamic routing, check the [Dynamic Routing documentation](/docs/routing/dynamic-routes.md).
+
+However, which `id` you want to pre-render at build time might depend on external data.
+
+**Example**: suppose that you've only added one blog post (with `id: 1`) to the database. In this case, you'd only want to pre-render `posts/1` at build time.
+
+Later, you might add the second post with `id: 2`. Then you'd want to pre-render `posts/2` as well.
+
+So your page **paths** that are pre-rendered depend on external data**.** To handle this, Next.js lets you `export` an `async` function called `getStaticPaths` from a dynamic page (`pages/posts/[id].js` in this case). This function gets called at build time and lets you specify which paths you want to pre-render.
+
+```
+// This function gets called at build time
+export async function getStaticPaths() {
+ // Call an external API endpoint to get posts
+ const res = await fetch('https://.../posts')
+ const posts = await res.json()
+
+ // Get the paths we want to pre-render based on posts
+ const paths = posts.map((post) => ({
+ params: { id: post.id }
+ }))
+
+ // We'll pre-render only these paths at build time.
+ // { fallback: false } means other routes should 404.
+ return { paths, fallback: false }
+}
+```
+
+Also in `pages/posts/[id].js`, you need to export `getStaticProps` so that you can fetch the data about the post with this `id` and use it to pre-render the page:
+
+```
+function Post({ post }) {
+ // Render post...
+}
+
+export async function getStaticPaths() {
+ // ...
+}
+
+// This also gets called at build time
+export async function getStaticProps({ params }) {
+ // params contains the post `id`.
+ // If the route is like /posts/1, then params.id is 1
+ const res = await fetch(`https://.../posts/${params.id}`)
+ const post = await res.json()
+
+ // Pass post data to the page via props
+ return { props: { post } }
+}
+
+export default Post
+```
+
+To learn more about how `getStaticPaths` works, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation).
+
+### When should I use Static Generation?
+
+We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
+
+You can use Static Generation for many types of pages, including:
+
+- Marketing pages
+- Blog posts
+- E-commerce product listings
+- Help and documentation
+
+You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
+
+On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
+
+In cases like this, you can do one of the following:
+
+- Use Static Generation with **Client-side Rendering:** You can skip pre-rendering some parts of a page and then use client-side JavaScript to populate them. To learn more about this approach, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side).
+- Use **Server-Side Rendering:** Next.js pre-renders a page on each request. It will be slower because the page cannot be cached by a CDN, but the pre-rendered page will always be up-to-date. We'll talk about this approach below.
+
+## Server-side Rendering
+
+> Also referred to as "SSR" or "Dynamic Rendering".
+
+If a page uses **Server-side Rendering**, the page HTML is generated on **each request**.
+
+To use Server-side Rendering for a page, you need to `export` an `async` function called `getServerSideProps`. This function will be called by the server on every request.
+
+For example, suppose that your page needs to pre-render frequently updated data (fetched from an external API). You can write `getServerSideProps` which fetches this data and passes it to `Page` like below:
+
+```
+function Page({ data }) {
+ // Render data...
+}
+
+// This gets called on every request
+export async function getServerSideProps() {
+ // Fetch data from external API
+ const res = await fetch(`https://.../data`)
+ const data = await res.json()
+
+ // Pass data to the page via props
+ return { props: { data } }
+}
+
+export default Page
+```
+
+As you can see, `getServerSideProps` is similar to `getStaticProps`, but the difference is that `getServerSideProps` is run on every request instead of on build time.
+
+To learn more about how `getServerSideProps` works, check out our [Data Fetching documentation](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)
+
+## Summary
+
+We've discussed two forms of pre-rendering for Next.js.
+
+- **Static Generation (Recommended):** The HTML is generated at **build time** and will be reused on each request. To make a page use Static Generation, either export the page component, or export `getStaticProps` (and `getStaticPaths` if necessary). It's great for pages that can be pre-rendered ahead of a user's request. You can also use it with Client-side Rendering to bring in additional data.
+- **Server-side Rendering:** The HTML is generated on **each request**. To make a page use Server-side Rendering, export `getServerSideProps`. Because Server-side Rendering results in slower performance than Static Generation, use this only if absolutely necessary.
diff --git a/examples/blog/pages/tags/[tag].mdx b/examples/blog/pages/tags/[tag].mdx
new file mode 100644
index 0000000000000..970952ce0cdac
--- /dev/null
+++ b/examples/blog/pages/tags/[tag].mdx
@@ -0,0 +1,13 @@
+---
+type: tag
+title: Tagged Posts
+---
+
+import { useRouter } from 'next/router'
+
+export const TagName = () => {
+ const { tag } = useRouter().query
+ return tag || null
+}
+
+# Posts Tagged with “ ”
diff --git a/examples/blog/public/favicon.ico b/examples/blog/public/favicon.ico
new file mode 100644
index 0000000000000..4965832f2c9b0
Binary files /dev/null and b/examples/blog/public/favicon.ico differ
diff --git a/examples/blog/public/fonts/Inter-italic.latin.var.woff2 b/examples/blog/public/fonts/Inter-italic.latin.var.woff2
new file mode 100644
index 0000000000000..5066cfdf36722
Binary files /dev/null and b/examples/blog/public/fonts/Inter-italic.latin.var.woff2 differ
diff --git a/examples/blog/public/fonts/Inter-roman.latin.var.woff2 b/examples/blog/public/fonts/Inter-roman.latin.var.woff2
new file mode 100644
index 0000000000000..9503ba17ff104
Binary files /dev/null and b/examples/blog/public/fonts/Inter-roman.latin.var.woff2 differ
diff --git a/examples/blog/public/images/photo.jpg b/examples/blog/public/images/photo.jpg
new file mode 100644
index 0000000000000..449c181d86714
Binary files /dev/null and b/examples/blog/public/images/photo.jpg differ
diff --git a/examples/blog/public/images/photo2.jpg b/examples/blog/public/images/photo2.jpg
new file mode 100644
index 0000000000000..2b028af36a8a2
Binary files /dev/null and b/examples/blog/public/images/photo2.jpg differ
diff --git a/examples/blog/scripts/gen-rss.js b/examples/blog/scripts/gen-rss.js
new file mode 100644
index 0000000000000..1dba6d6495e50
--- /dev/null
+++ b/examples/blog/scripts/gen-rss.js
@@ -0,0 +1,38 @@
+const { promises: fs } = require('fs')
+const path = require('path')
+const RSS = require('rss')
+const matter = require('gray-matter')
+
+async function generate() {
+ const feed = new RSS({
+ title: 'Your Name',
+ site_url: 'https://yoursite.com',
+ feed_url: 'https://yoursite.com/feed.xml'
+ })
+
+ const posts = await fs.readdir(path.join(__dirname, '..', 'pages', 'posts'))
+
+ await Promise.all(
+ posts.map(async (name) => {
+ if (name.startsWith('index.')) return
+
+ const content = await fs.readFile(
+ path.join(__dirname, '..', 'pages', 'posts', name)
+ )
+ const frontmatter = matter(content)
+
+ feed.item({
+ title: frontmatter.data.title,
+ url: '/posts/' + name.replace(/\.mdx?/, ''),
+ date: frontmatter.data.date,
+ description: frontmatter.data.description,
+ categories: frontmatter.data.tag.split(', '),
+ author: frontmatter.data.author
+ })
+ })
+ )
+
+ await fs.writeFile('./public/feed.xml', feed.xml({ indent: true }))
+}
+
+generate()
diff --git a/examples/blog/styles/main.css b/examples/blog/styles/main.css
new file mode 100644
index 0000000000000..d5016fa9a6462
--- /dev/null
+++ b/examples/blog/styles/main.css
@@ -0,0 +1,57 @@
+@font-face {
+ font-family: 'Inter var';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: block;
+ src: url(/fonts/Inter-roman.latin.var.woff2) format('woff2');
+}
+@font-face {
+ font-family: 'Inter var';
+ font-style: italic;
+ font-weight: 100 900;
+ font-display: block;
+ src: url(/fonts/Inter-italic.latin.var.woff2) format('woff2');
+ font-named-instance: 'Italic';
+}
+
+body {
+ font-family: 'Inter var', system-ui, -apple-system, BlinkMacSystemFont,
+ 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ -webkit-font-smoothing: subpixel-antialiased;
+ font-feature-settings: 'case' 1, 'cpsp' 1, 'dlig' 1, 'cv01' 1, 'cv02',
+ 'cv03' 1, 'cv04' 1;
+ font-variation-settings: 'wght' 450;
+ font-variant: common-ligatures contextual;
+ letter-spacing: -0.02em;
+}
+b,
+strong,
+h3,
+h4,
+h5,
+h6 {
+ font-variation-settings: 'wght' 650;
+}
+h1 {
+ font-variation-settings: 'wght' 850;
+}
+h2 {
+ font-variation-settings: 'wght' 750;
+}
+
+@media screen and (min-device-pixel-ratio: 1.5),
+ screen and (min-resolution: 1.5dppx) {
+ body {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+}
+
+details summary {
+ cursor: pointer;
+}
+
+img.next-image {
+ margin: 0;
+}
diff --git a/examples/blog/theme.config.js b/examples/blog/theme.config.js
new file mode 100644
index 0000000000000..fb56fe005f6df
--- /dev/null
+++ b/examples/blog/theme.config.js
@@ -0,0 +1,21 @@
+const YEAR = new Date().getFullYear()
+
+export default {
+ footer: (
+
+ {YEAR} © Your Name.
+ RSS
+
+
+ )
+}
diff --git a/examples/with-redux-toolkit-typescript/.gitignore b/examples/with-redux-toolkit-typescript/.gitignore
new file mode 100644
index 0000000000000..1437c53f70bc2
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/.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/with-redux-toolkit-typescript/README.md b/examples/with-redux-toolkit-typescript/README.md
new file mode 100644
index 0000000000000..20599f991daf6
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/README.md
@@ -0,0 +1,23 @@
+# Redux Toolkit TypeScript Example
+
+This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org).
+
+The **Redux Toolkit** is intended to be the standard way to write Redux logic (create actions and reducers, setup the store with some default middlewares like redux devtools extension). This example demonstrates each of these features with Next.js
+
+## Deploy your own
+
+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/with-redux-toolkit-typescript&project-name=with-redux-toolkit&repository-name=with-redux-toolkit)
+
+## 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-redux-toolkit-typescript with-redux-toolkit-app
+# or
+yarn create next-app --example with-redux-toolkit-typescript with-redux-toolkit-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-redux-toolkit-typescript/components/add-note.tsx b/examples/with-redux-toolkit-typescript/components/add-note.tsx
new file mode 100644
index 0000000000000..c06d35bd43b3d
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/components/add-note.tsx
@@ -0,0 +1,41 @@
+import { FC } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+
+import { addNote, selectNotes } from '../lib/slices/notesSlice'
+import useForm from '../lib/useForm'
+import { Note } from '../types/Note'
+import { isErrorResponse } from '../types/ErrorResponse'
+
+const AddNoteForm: FC = () => {
+ const dispatch = useDispatch()
+ const { error } = useSelector(selectNotes)
+ const handleSubmit = useForm({
+ title: '',
+ content: '',
+ })
+
+ return (
+
+ )
+}
+
+export default AddNoteForm
diff --git a/examples/with-redux-toolkit-typescript/components/clock.tsx b/examples/with-redux-toolkit-typescript/components/clock.tsx
new file mode 100644
index 0000000000000..f2cfa93b6111c
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/components/clock.tsx
@@ -0,0 +1,34 @@
+import { FC } from 'react'
+import { useSelector } from 'react-redux'
+
+import { selectClock } from '../lib/slices/clockSlice'
+
+const formatTime = (time: number) => {
+ // cut off except hh:mm:ss
+ return new Date(time).toJSON().slice(11, 19)
+}
+
+const Clock: FC = () => {
+ const { lastUpdate, light } = useSelector(selectClock)
+
+ return (
+
+ {formatTime(lastUpdate)}
+
+
+ )
+}
+
+export default Clock
diff --git a/examples/with-redux-toolkit-typescript/components/counter.tsx b/examples/with-redux-toolkit-typescript/components/counter.tsx
new file mode 100644
index 0000000000000..0161ecb139c60
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/components/counter.tsx
@@ -0,0 +1,156 @@
+import { ChangeEvent, FC } from 'react'
+import { useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+
+import {
+ decrement,
+ increment,
+ incrementAsync,
+ incrementByAmount,
+ reset,
+ selectCount,
+} from '../lib/slices/counterSlice'
+
+const Counter: FC = () => {
+ const dispatch = useDispatch()
+ const count = useSelector(selectCount)
+ const [incrementAmount, setIncrementAmount] = useState('2')
+
+ function dispatchIncrement() {
+ dispatch(increment())
+ }
+ function dispatchDecrement() {
+ dispatch(decrement())
+ }
+ function dispatchReset() {
+ dispatch(reset())
+ }
+ function changeIncrementAmount(event: ChangeEvent) {
+ setIncrementAmount(event.target.value)
+ }
+ function dispatchIncrementByAmount() {
+ dispatch(incrementByAmount(Number(incrementAmount) || 0))
+ }
+ function dispatchIncrementAsync() {
+ dispatch(incrementAsync(Number(incrementAmount) || 0))
+ }
+
+ return (
+ <>
+
+
+ +
+
+ {count}
+
+ -
+
+
+
+
+
+ Add Amount
+
+
+ Add Async
+
+
+
+
+ Reset
+
+
+
+ >
+ )
+}
+
+export default Counter
diff --git a/examples/with-redux-toolkit-typescript/components/edit-note.tsx b/examples/with-redux-toolkit-typescript/components/edit-note.tsx
new file mode 100644
index 0000000000000..63e5ef920f6e3
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/components/edit-note.tsx
@@ -0,0 +1,60 @@
+import { FC } from 'react'
+import { useLayoutEffect, useRef } from 'react'
+import { useDispatch } from 'react-redux'
+
+import { editNote } from '../lib/slices/notesSlice'
+import useForm from '../lib/useForm'
+import { PersistedNote } from '../types/Note'
+
+type Props = {
+ note: PersistedNote
+}
+
+const EditNoteForm: FC = ({ note }) => {
+ const dialogRef = useRef(null)
+ const dispatch = useDispatch()
+ const handleSubmit = useForm(note)
+
+ useLayoutEffect(() => {
+ const isOpen = Object.keys(note).length > 0
+ if (isOpen) {
+ dialogRef.current?.setAttribute('open', 'true')
+ }
+ }, [note])
+
+ return (
+
+ {
+ await dispatch(editNote(data))
+ dialogRef.current?.removeAttribute('open')
+ })}
+ >
+ Edit Note
+
+ Title:
+
+
+
+
+ Content:
+
+
+
+ Edit
+
+
+
+ )
+}
+
+export default EditNoteForm
diff --git a/examples/with-redux-toolkit-typescript/lib/slices/clockSlice.ts b/examples/with-redux-toolkit-typescript/lib/slices/clockSlice.ts
new file mode 100644
index 0000000000000..9ed19cadf938c
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/lib/slices/clockSlice.ts
@@ -0,0 +1,29 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+import { CoreState } from '../../src/store'
+
+type ClockState = {
+ lastUpdate: number
+ light: boolean
+}
+
+const initialState: ClockState = {
+ lastUpdate: 0,
+ light: true,
+}
+
+const clockSlice = createSlice({
+ name: 'clock',
+ initialState,
+ reducers: {
+ tick: (state, action: PayloadAction) => {
+ state.lastUpdate = action.payload.lastUpdate
+ state.light = !!action.payload.light
+ },
+ },
+})
+
+export const selectClock = (state: CoreState) => state.clock
+
+export const { tick } = clockSlice.actions
+
+export default clockSlice.reducer
diff --git a/examples/with-redux-toolkit-typescript/lib/slices/counterSlice.ts b/examples/with-redux-toolkit-typescript/lib/slices/counterSlice.ts
new file mode 100644
index 0000000000000..8efda8feaf286
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/lib/slices/counterSlice.ts
@@ -0,0 +1,60 @@
+import { createSlice, Dispatch } from '@reduxjs/toolkit'
+import { CoreState } from '../../src/store'
+
+type CounterState = {
+ value: number
+}
+
+const initialState: CounterState = {
+ value: 0,
+}
+
+const counterSlice = createSlice({
+ name: 'counter',
+ initialState,
+ reducers: {
+ // Redux Toolkit allows us to write "mutating" logic in reducers. It
+ // doesn't actually mutate the state because it uses the Immer library,
+ // which detects changes to a "draft state" and produces a brand new
+ // immutable state based off those changes
+ increment: (state) => {
+ state.value += 1
+ },
+ decrement: (state) => {
+ state.value -= 1
+ },
+ reset: (state) => {
+ state.value = 0
+ },
+ incrementByAmount: (state, action) => {
+ state.value += action.payload
+ },
+ },
+})
+
+/**
+ * Extract count from root state
+ *
+ * @param {Object} state The root state
+ * @returns {number} The current count
+ */
+export const selectCount = (state: CoreState) => state.counter.value
+
+export const {
+ increment,
+ decrement,
+ reset,
+ incrementByAmount,
+} = counterSlice.actions
+
+// The function below is called a thunk and allows us to perform async logic. It
+// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
+// will call the thunk with the `dispatch` function as the first argument. Async
+// code can then be executed and other actions can be dispatched
+export const incrementAsync = (amount: number) => (dispatch: Dispatch) => {
+ setTimeout(() => {
+ dispatch(incrementByAmount(amount))
+ }, 1000)
+}
+
+export default counterSlice.reducer
diff --git a/examples/with-redux-toolkit-typescript/lib/slices/notesSlice.ts b/examples/with-redux-toolkit-typescript/lib/slices/notesSlice.ts
new file mode 100644
index 0000000000000..6dc170973ec4e
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/lib/slices/notesSlice.ts
@@ -0,0 +1,193 @@
+import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit'
+import { Note, PersistedNote } from '../../types/Note'
+import { CoreState } from '../../src/store'
+import { ErrorResponse } from '../../types/ErrorResponse'
+
+type ErrorResult = {
+ error: ErrorResponse | string
+}
+
+type ThunkConfig = { rejectValue: ErrorResult }
+
+export const addNote = createAsyncThunk(
+ 'notes/addNote',
+ async (newNote: Note, thunkAPI) => {
+ try {
+ const response = await fetch('/api/notes', {
+ method: 'POST',
+ body: JSON.stringify(newNote),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+
+ if (!response.ok) {
+ const error = await response.json()
+
+ return thunkAPI.rejectWithValue({ error: error.errors })
+ }
+
+ return response.json()
+ } catch (error) {
+ return thunkAPI.rejectWithValue({ error: error.message })
+ }
+ }
+)
+
+export const loadNotes = createAsyncThunk(
+ 'notes/loadNotes',
+ async (_, thunkAPI) => {
+ try {
+ const response = await fetch('/api/notes')
+
+ return response.json()
+ } catch (error) {
+ return thunkAPI.rejectWithValue({ error: error.message })
+ }
+ }
+)
+
+export const editNote = createAsyncThunk<
+ PersistedNote,
+ PersistedNote,
+ ThunkConfig
+>('notes/editNote', async (updates, thunkAPI) => {
+ const { id, title, content } = updates
+
+ try {
+ const response = await fetch(`/api/notes?noteId=${id}`, {
+ method: 'PUT',
+ body: JSON.stringify({ title, content }),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+
+ return response.json()
+ } catch (error) {
+ return thunkAPI.rejectWithValue({ error: error.message })
+ }
+})
+
+export const deleteNote = createAsyncThunk(
+ 'notes/deleteNote',
+ async (id, thunkAPI) => {
+ try {
+ await fetch(`/api/notes?noteId=${id}`, { method: 'DELETE' })
+ return id
+ } catch (error) {
+ return thunkAPI.rejectWithValue({ error: error.message })
+ }
+ }
+)
+
+enum LoadingState {
+ IDLE = 'idle',
+ LOADING = 'loading',
+ LOADED = 'loaded',
+ ERROR = 'error',
+}
+
+type NoteState = {
+ notes: PersistedNote[]
+ loading: LoadingState
+ error?: ErrorResponse | string
+ tempNote?: PersistedNote
+ backupNote?: PersistedNote
+ backupPosition?: number
+}
+
+const initialState: NoteState = {
+ notes: [],
+ loading: LoadingState.IDLE,
+ error: undefined,
+ tempNote: undefined,
+ backupNote: undefined,
+ backupPosition: undefined,
+}
+
+const notesSlice = createSlice({
+ name: 'notes',
+ initialState,
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(addNote.pending, (state) => {
+ delete state.error
+ })
+
+ builder.addCase(addNote.fulfilled, (state, action) => {
+ state.notes.push(action.payload)
+ })
+
+ builder.addCase(addNote.rejected, (state, action) => {
+ state.error = action.payload?.error
+ })
+
+ builder.addCase(loadNotes.pending, (state) => {
+ state.notes = []
+ state.loading = LoadingState.LOADING
+ })
+
+ builder.addCase(loadNotes.fulfilled, (state, action) => {
+ state.notes = action.payload
+ state.loading = LoadingState.LOADED
+ })
+
+ builder.addCase(loadNotes.rejected, (state, action) => {
+ state.loading = LoadingState.ERROR
+ state.error = action.payload?.error
+ })
+
+ builder.addCase(editNote.pending, (state, action) => {
+ const note = state.notes.find((note) => note.id === action.meta.arg.id)
+ if (!note) return
+ state.tempNote = Object.assign({}, note)
+ note.title = action.meta.arg.title || note.title
+ note.content = action.meta.arg.content || note.content
+ })
+
+ builder.addCase(editNote.fulfilled, (state, action) => {
+ const note = state.notes.find((note) => note.id === action.payload.id)
+ delete state.tempNote
+ Object.assign(note, action.payload)
+ })
+
+ builder.addCase(editNote.rejected, (state, action) => {
+ const note = state.notes.find((note) => note.id === action.meta.arg.id)
+ state.error = action.payload?.error || action.error.message
+ Object.assign(note, state.tempNote)
+ delete state.tempNote
+ })
+
+ builder.addCase(deleteNote.pending, (state, action) => {
+ const position = state.notes.findIndex(
+ (note) => note.id === action.meta.arg
+ )
+ state.backupNote = Object.assign({}, state.notes[position])
+ state.backupPosition = position
+ state.notes.splice(position, 1)
+ })
+
+ builder.addCase(deleteNote.fulfilled, (state) => {
+ delete state.backupNote
+ delete state.backupPosition
+ })
+
+ builder.addCase(deleteNote.rejected, (state) => {
+ if (!state.backupPosition || !state.backupNote) return
+ state.notes.splice(state.backupPosition, 0, state.backupNote)
+ delete state.backupPosition
+ delete state.backupNote
+ })
+ },
+})
+
+export const selectNotes = createSelector(
+ (state: CoreState) => ({
+ notes: state.notes.notes,
+ error: state.notes.error,
+ }),
+ (state) => state
+)
+
+export default notesSlice.reducer
diff --git a/examples/with-redux-toolkit-typescript/lib/useForm.ts b/examples/with-redux-toolkit-typescript/lib/useForm.ts
new file mode 100644
index 0000000000000..1ab9a71daa75a
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/lib/useForm.ts
@@ -0,0 +1,24 @@
+import { ChangeEvent } from 'react'
+
+const useForm = (defaultValues: TContent) => (
+ handler: (content: TContent) => void
+) => async (event: ChangeEvent) => {
+ event.preventDefault()
+ event.persist()
+
+ const form = event.target
+ const elements = Array.from(form.elements) as HTMLInputElement[]
+ const data = elements
+ .filter((element) => element.hasAttribute('name'))
+ .reduce(
+ (object, element) => ({
+ ...object,
+ [`${element.getAttribute('name')}`]: element.value,
+ }),
+ defaultValues
+ )
+ await handler(data)
+ form.reset()
+}
+
+export default useForm
diff --git a/examples/with-redux-toolkit-typescript/lib/useInterval.ts b/examples/with-redux-toolkit-typescript/lib/useInterval.ts
new file mode 100644
index 0000000000000..b732276c97e65
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/lib/useInterval.ts
@@ -0,0 +1,19 @@
+import { useEffect, useRef } from 'react'
+
+// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
+const useInterval = (callback: Function, delay: number) => {
+ const savedCallback = useRef()
+ useEffect(() => {
+ savedCallback.current = callback
+ }, [callback])
+ useEffect(() => {
+ const handler = (...args: any) => savedCallback.current?.(...args)
+
+ if (delay !== null) {
+ const id = setInterval(handler, delay)
+ return () => clearInterval(id)
+ }
+ }, [delay])
+}
+
+export default useInterval
diff --git a/examples/with-stitches/next-env.d.ts b/examples/with-redux-toolkit-typescript/next-env.d.ts
similarity index 100%
rename from examples/with-stitches/next-env.d.ts
rename to examples/with-redux-toolkit-typescript/next-env.d.ts
diff --git a/examples/with-redux-toolkit-typescript/package.json b/examples/with-redux-toolkit-typescript/package.json
new file mode 100644
index 0000000000000..3f208e49f53d0
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "with-redux-toolkit-typescript",
+ "version": "1.0.0",
+ "scripts": {
+ "dev": "next",
+ "build": "next build",
+ "start": "next start",
+ "type-check": "tsc"
+ },
+ "dependencies": {
+ "@nano-sql/core": "^2.3.7",
+ "@reduxjs/toolkit": "^1.3.6",
+ "next": "latest",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-redux": "^7.2.0"
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "@types/node": "14.14.35",
+ "@types/react": "17.0.3",
+ "@types/react-dom": "17.0.2",
+ "@types/react-redux": "7.1.16",
+ "prettier": "2.2.1",
+ "typescript": "4.2.3"
+ }
+}
diff --git a/examples/with-redux-toolkit-typescript/pages/_app.tsx b/examples/with-redux-toolkit-typescript/pages/_app.tsx
new file mode 100644
index 0000000000000..7c020d686163d
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/pages/_app.tsx
@@ -0,0 +1,21 @@
+import React, { ComponentType } from 'react'
+import { Provider } from 'react-redux'
+import { AppInitialProps } from 'next/app'
+
+import store from '../src/store'
+
+const MyApp = ({
+ Component,
+ pageProps,
+}: {
+ Component: ComponentType
+ pageProps: AppInitialProps
+}) => {
+ return (
+
+
+
+ )
+}
+
+export default MyApp
diff --git a/examples/with-redux-toolkit-typescript/pages/api/notes.js b/examples/with-redux-toolkit-typescript/pages/api/notes.js
new file mode 100644
index 0000000000000..640885f99e35d
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/pages/api/notes.js
@@ -0,0 +1,112 @@
+import { nSQL } from '@nano-sql/core'
+
+const connectMiddleware = (handler) => async (req, res) => {
+ const dbName = 'with-redux-toolkit'
+
+ if (!nSQL().listDatabases().includes(dbName)) {
+ await nSQL().createDatabase({
+ id: dbName,
+ mode: 'PERM',
+ tables: [
+ {
+ name: 'notes',
+ model: {
+ 'id:uuid': { pk: true },
+ 'title:string': { notNull: true },
+ 'content:string': { notNull: true },
+ 'createdAt:date': { default: () => new Date() },
+ },
+ },
+ ],
+ version: 1,
+ })
+ }
+ nSQL().useDatabase(dbName)
+
+ return handler(req, res)
+}
+const saveNote = async (req, res) => {
+ const { title, content } = req.body
+ const errors = {}
+
+ if (!title) errors['title'] = 'Title is required'
+
+ if (!content) errors['content'] = 'Content is required'
+
+ if (Object.keys(errors).length > 0)
+ return res.status(422).json({
+ statusCode: 422,
+ message: 'Unprocessable Entity',
+ errors,
+ })
+
+ const [note] = await nSQL('notes').query('upsert', { title, content }).exec()
+
+ res.status(201).json(note)
+}
+const listNotes = async (_, res) => {
+ const notes = await nSQL('notes').query('select').exec()
+
+ res.json(notes)
+}
+const updateNote = async (req, res) => {
+ const { noteId } = req.query
+ const [note] = await nSQL()
+ .query('select')
+ .where(['id', '=', noteId])
+ .limit(1)
+ .exec()
+
+ if (!note)
+ return res.status(404).json({
+ statusCode: 404,
+ message: 'Not Found',
+ })
+
+ const { title = note.title, content = note.content } = req.body
+ const [noteUpdated] = await nSQL('notes')
+ .query('upsert', { title, content })
+ .where(['id', '=', noteId])
+ .limit(1)
+ .exec()
+
+ res.json(noteUpdated)
+}
+const removeNote = async (req, res) => {
+ const { noteId } = req.query
+ const [note] = await nSQL()
+ .query('select')
+ .where(['id', '=', noteId])
+ .limit(1)
+ .exec()
+
+ if (!note)
+ return res.status(404).json({
+ statusCode: 404,
+ message: 'Not Found',
+ })
+
+ await nSQL('notes').query('delete').where(['id', '=', noteId]).limit(1).exec()
+
+ res.status(204).send(null)
+}
+
+const handler = (req, res) => {
+ switch (req.method) {
+ case 'POST':
+ return saveNote(req, res)
+ case 'GET':
+ return listNotes(req, res)
+ case 'PUT':
+ return updateNote(req, res)
+ case 'DELETE':
+ return removeNote(req, res)
+ default:
+ return res.status(404).json({
+ statusCode: 404,
+ message: 'Not Found',
+ })
+ }
+}
+
+export default connectMiddleware(handler)
diff --git a/examples/with-redux-toolkit-typescript/pages/index.tsx b/examples/with-redux-toolkit-typescript/pages/index.tsx
new file mode 100644
index 0000000000000..da290a63db2c4
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/pages/index.tsx
@@ -0,0 +1,23 @@
+import { useDispatch } from 'react-redux'
+
+import Clock from '../components/clock'
+import Counter from '../components/counter'
+import { tick } from '../lib/slices/clockSlice'
+import useInterval from '../lib/useInterval'
+
+const IndexPage = () => {
+ const dispatch = useDispatch()
+ // Tick the time every second
+ useInterval(() => {
+ dispatch(tick({ light: true, lastUpdate: Date.now() }))
+ }, 1000)
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default IndexPage
diff --git a/examples/with-redux-toolkit-typescript/pages/notes.tsx b/examples/with-redux-toolkit-typescript/pages/notes.tsx
new file mode 100644
index 0000000000000..d108abb6623b6
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/pages/notes.tsx
@@ -0,0 +1,58 @@
+import Dynamic from 'next/dynamic'
+import Head from 'next/head'
+import { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+
+import AddNoteForm from '../components/add-note'
+import { deleteNote, loadNotes, selectNotes } from '../lib/slices/notesSlice'
+import { PersistedNote } from '../types/Note'
+
+const EditNoteForm = Dynamic(import('../components/edit-note'), { ssr: false })
+const Notes = () => {
+ const [selectedNote, setSelectedNote] = useState()
+ const dispatch = useDispatch()
+ const { notes } = useSelector(selectNotes)
+
+ useEffect(() => {
+ async function dispatchLoadNotes() {
+ await dispatch(loadNotes())
+ }
+ dispatchLoadNotes()
+ }, [dispatch])
+
+ const renderNote = (note: PersistedNote) => (
+
+ {note.title}
+
+ {note.content}
+
+ dispatch(deleteNote(note.id))}
+ >
+ 🗑️
+
+ setSelectedNote(note)}
+ aria-label={`Edit note with title: ${note.title}`}
+ >
+ ✏️
+
+
+ )
+
+ return (
+ <>
+
+ Next.js with Redux Toolkit | Notes App
+
+
+
+ All Notes
+
+ {selectedNote && }
+ >
+ )
+}
+
+export default Notes
diff --git a/examples/with-redux-toolkit-typescript/src/store.ts b/examples/with-redux-toolkit-typescript/src/store.ts
new file mode 100644
index 0000000000000..ba01ac80109b5
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/src/store.ts
@@ -0,0 +1,18 @@
+import { combineReducers, configureStore } from '@reduxjs/toolkit'
+
+import clockReducer from '../lib/slices/clockSlice'
+import counterReducer from '../lib/slices/counterSlice'
+import notesReducer from '../lib/slices/notesSlice'
+
+const rootReducer = combineReducers({
+ counter: counterReducer,
+ clock: clockReducer,
+ notes: notesReducer,
+})
+
+export type CoreState = ReturnType
+
+export default configureStore({
+ reducer: rootReducer,
+ devTools: true,
+})
diff --git a/examples/with-redux-toolkit-typescript/tsconfig.json b/examples/with-redux-toolkit-typescript/tsconfig.json
new file mode 100644
index 0000000000000..68f3100ddd957
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "rootDirs": ["src/", "types/", "pages/", "lib/", "components/"],
+ "allowJs": true,
+ "alwaysStrict": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "lib": ["dom", "es2017"],
+ "module": "esnext",
+ "moduleResolution": "node",
+ "noEmit": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "esnext"
+ },
+ "exclude": ["node_modules"],
+ "include": ["**/*.ts", "**/*.tsx"]
+}
diff --git a/examples/with-redux-toolkit-typescript/types/ErrorResponse.ts b/examples/with-redux-toolkit-typescript/types/ErrorResponse.ts
new file mode 100644
index 0000000000000..2074b13df957e
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/types/ErrorResponse.ts
@@ -0,0 +1,10 @@
+export type ErrorResponse = {
+ title: string
+ content: string
+}
+
+export function isErrorResponse(
+ error: ErrorResponse | string | undefined
+): error is ErrorResponse {
+ return (error as ErrorResponse)?.title !== undefined
+}
diff --git a/examples/with-redux-toolkit-typescript/types/Note.ts b/examples/with-redux-toolkit-typescript/types/Note.ts
new file mode 100644
index 0000000000000..b7fc4a3ffc3bf
--- /dev/null
+++ b/examples/with-redux-toolkit-typescript/types/Note.ts
@@ -0,0 +1,8 @@
+export type Note = {
+ title: string
+ content: string
+}
+
+export type PersistedNote = Note & {
+ id: string
+}
diff --git a/examples/with-stitches/components/StitchesLogo.tsx b/examples/with-stitches/components/StitchesLogo.jsx
similarity index 100%
rename from examples/with-stitches/components/StitchesLogo.tsx
rename to examples/with-stitches/components/StitchesLogo.jsx
diff --git a/examples/with-stitches/package.json b/examples/with-stitches/package.json
index 819de9b6d10f7..efe65b88ece5b 100644
--- a/examples/with-stitches/package.json
+++ b/examples/with-stitches/package.json
@@ -7,14 +7,10 @@
"start": "next start"
},
"dependencies": {
- "@stitches/react": "0.0.1",
+ "@stitches/react": "0.1.0",
"next": "latest",
- "react": "^16.13.1",
- "react-dom": "^16.13.1"
- },
- "devDependencies": {
- "@types/react": "^16.9.43",
- "typescript": "^3.9.7"
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1"
},
"license": "MIT"
}
diff --git a/examples/with-stitches/pages/_document.jsx b/examples/with-stitches/pages/_document.jsx
new file mode 100644
index 0000000000000..4e2be3e7bdb26
--- /dev/null
+++ b/examples/with-stitches/pages/_document.jsx
@@ -0,0 +1,22 @@
+import React from 'react'
+import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
+import { getCssString } from '../stitches.config'
+
+export default class Document extends NextDocument {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/examples/with-stitches/pages/_document.tsx b/examples/with-stitches/pages/_document.tsx
deleted file mode 100644
index 499e3ec607e63..0000000000000
--- a/examples/with-stitches/pages/_document.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react'
-import NextDocument, { DocumentContext } from 'next/document'
-import { css } from '../stitches.config'
-
-export default class Document extends NextDocument {
- static async getInitialProps(ctx: DocumentContext) {
- const originalRenderPage = ctx.renderPage
-
- try {
- let extractedStyles
- ctx.renderPage = () => {
- const { styles, result } = css.getStyles(originalRenderPage)
- extractedStyles = styles
- return result
- }
-
- const initialProps = await NextDocument.getInitialProps(ctx)
-
- return {
- ...initialProps,
- styles: (
- <>
- {initialProps.styles}
-
- {extractedStyles.map((content, index) => (
-
- ))}
- >
- ),
- }
- } finally {
- }
- }
-}
diff --git a/examples/with-stitches/pages/index.tsx b/examples/with-stitches/pages/index.jsx
similarity index 94%
rename from examples/with-stitches/pages/index.tsx
rename to examples/with-stitches/pages/index.jsx
index b3e465fc8bb65..17d0d1bee720d 100644
--- a/examples/with-stitches/pages/index.tsx
+++ b/examples/with-stitches/pages/index.jsx
@@ -40,7 +40,7 @@ export default function Home() {
Use Stitches with Next.js
-
+
Hello, from Stitches.
diff --git a/examples/with-stitches/stitches.config.js b/examples/with-stitches/stitches.config.js
new file mode 100644
index 0000000000000..e0c95e32707c1
--- /dev/null
+++ b/examples/with-stitches/stitches.config.js
@@ -0,0 +1,73 @@
+import { createCss } from '@stitches/react'
+
+export const { css, styled, global, getCssString } = createCss({
+ theme: {
+ colors: {
+ $hiContrast: 'hsl(206,10%,5%)',
+ $loContrast: 'white',
+
+ $gray100: 'hsl(206,22%,99%)',
+ $gray200: 'hsl(206,12%,97%)',
+ $gray300: 'hsl(206,11%,92%)',
+ $gray400: 'hsl(206,10%,84%)',
+ $gray500: 'hsl(206,10%,76%)',
+ $gray600: 'hsl(206,10%,44%)',
+
+ $purple100: 'hsl(252,100%,99%)',
+ $purple200: 'hsl(252,100%,98%)',
+ $purple300: 'hsl(252,100%,94%)',
+ $purple400: 'hsl(252,75%,84%)',
+ $purple500: 'hsl(252,78%,60%)',
+ $purple600: 'hsl(252,80%,53%)',
+ },
+ space: {
+ $1: '5px',
+ $2: '10px',
+ $3: '15px',
+ $4: '20px',
+ $5: '25px',
+ $6: '35px',
+ },
+ sizes: {
+ $1: '5px',
+ $2: '10px',
+ $3: '15px',
+ $4: '20px',
+ $5: '25px',
+ $6: '35px',
+ },
+ fontSizes: {
+ $1: '12px',
+ $2: '13px',
+ $3: '15px',
+ $4: '17px',
+ $5: '19px',
+ $6: '21px',
+ },
+ fonts: {
+ $system: 'system-ui',
+ },
+ },
+ utils: {
+ marginX: (config) => (value) => ({
+ marginLeft: value,
+ marginRight: value,
+ }),
+ marginY: (config) => (value) => ({
+ marginTop: value,
+ marginBottom: value,
+ }),
+ paddingX: (config) => (value) => ({
+ paddingLeft: value,
+ paddingRight: value,
+ }),
+ paddingY: (config) => (value) => ({
+ paddingTop: value,
+ paddingBottom: value,
+ }),
+ },
+ media: {
+ bp1: '@media (min-width: 520px)',
+ bp2: '@media (min-width: 900px)',
+ },
+})
diff --git a/examples/with-stitches/stitches.config.ts b/examples/with-stitches/stitches.config.ts
deleted file mode 100644
index b09b336090233..0000000000000
--- a/examples/with-stitches/stitches.config.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { createStyled } from '@stitches/react'
-
-const theme = {
- colors: {
- $hiContrast: 'hsl(206,10%,5%)',
- $loContrast: 'white',
-
- $gray100: 'hsl(206,22%,99%)',
- $gray200: 'hsl(206,12%,97%)',
- $gray300: 'hsl(206,11%,92%)',
- $gray400: 'hsl(206,10%,84%)',
- $gray500: 'hsl(206,10%,76%)',
- $gray600: 'hsl(206,10%,44%)',
-
- $purple100: 'hsl(252,100%,99%)',
- $purple200: 'hsl(252,100%,98%)',
- $purple300: 'hsl(252,100%,94%)',
- $purple400: 'hsl(252,75%,84%)',
- $purple500: 'hsl(252,78%,60%)',
- $purple600: 'hsl(252,80%,53%)',
- },
- space: {
- $1: '5px',
- $2: '10px',
- $3: '15px',
- $4: '20px',
- $5: '25px',
- $6: '35px',
- },
- sizes: {
- $1: '5px',
- $2: '10px',
- $3: '15px',
- $4: '20px',
- $5: '25px',
- $6: '35px',
- },
- fontSizes: {
- $1: '12px',
- $2: '13px',
- $3: '15px',
- $4: '17px',
- $5: '19px',
- $6: '21px',
- },
- fonts: {
- $system: 'system-ui',
- },
-}
-
-export const { styled, css } = createStyled({
- tokens: theme,
- utils: {
- marginX: (config) => (
- value: keyof typeof theme['space'] | (string & {})
- ) => ({
- marginLeft: value,
- marginRight: value,
- }),
- marginY: (config) => (
- value: keyof typeof theme['space'] | (string & {})
- ) => ({
- marginTop: value,
- marginBottom: value,
- }),
- paddingX: (config) => (
- value: keyof typeof theme['space'] | (string & {})
- ) => ({
- paddingLeft: value,
- paddingRight: value,
- }),
- paddingY: (config) => (
- value: keyof typeof theme['space'] | (string & {})
- ) => ({
- paddingTop: value,
- paddingBottom: value,
- }),
- },
- breakpoints: {
- bp1: (rule) => `@media (min-width: 520px) { ${rule} }`,
- bp2: (rule) => `@media (min-width: 900px) { ${rule} }`,
- },
-})
diff --git a/examples/with-stitches/tsconfig.json b/examples/with-stitches/tsconfig.json
deleted file mode 100644
index 93a83a407c40c..0000000000000
--- a/examples/with-stitches/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": false,
- "forceConsistentCasingInFileNames": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve"
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
-}
diff --git a/lerna.json b/lerna.json
index f9fea0ceacafa..ce3ea901af2a8 100644
--- a/lerna.json
+++ b/lerna.json
@@ -17,5 +17,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "10.0.10-canary.6"
+ "version": "10.0.10-canary.7"
}
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index 2f48016416f4d..da670658b3e08 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index a697ffdb2609a..f41bc08170b6f 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "ESLint plugin for NextJS.",
"main": "lib/index.js",
"license": "MIT",
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 7078ba06b0bde..10a9be3bb9595 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 972a7a1d94c84..4ea67ac22aac6 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index 41cff83a81533..45b453406368c 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index 8edaef89d1ae9..f29132d6fc394 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-google-analytics/package.json b/packages/next-plugin-google-analytics/package.json
index ce85fb1258bed..7310480ee6d08 100644
--- a/packages/next-plugin-google-analytics/package.json
+++ b/packages/next-plugin-google-analytics/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-google-analytics",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-google-analytics"
diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json
index d1faa5f9fd73b..796eae2815306 100644
--- a/packages/next-plugin-sentry/package.json
+++ b/packages/next-plugin-sentry/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-sentry",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-sentry"
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index c34ddb2796910..644b49d1a1b17 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index 301629a0889d0..0b0de943b0a68 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index d459465dfbec1..f1971d2daef44 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next/bundles/yarn.lock b/packages/next/bundles/yarn.lock
index 337ae612e31ce..ee9335a466f0f 100644
--- a/packages/next/bundles/yarn.lock
+++ b/packages/next/bundles/yarn.lock
@@ -223,9 +223,9 @@ commander@^2.20.0:
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
electron-to-chromium@^1.3.634:
- version "1.3.690"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.690.tgz#54df63ec42fba6b8e9e05fe4be52caeeedb6e634"
- integrity sha512-zPbaSv1c8LUKqQ+scNxJKv01RYFkVVF1xli+b+3Ty8ONujHjAMg+t/COmdZqrtnS1gT+g4hbSodHillymt1Lww==
+ version "1.3.693"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.693.tgz#5089c506a925c31f93fcb173a003a22e341115dd"
+ integrity sha512-vUdsE8yyeu30RecppQtI+XTz2++LWLVEIYmzeCaCRLSdtKZ2eXqdJcrs85KwLiPOPVc6PELgWyXBsfqIvzGZag==
enhanced-resolve@^5.7.0:
version "5.7.0"
@@ -439,9 +439,9 @@ terser-webpack-plugin@^5.1.1:
terser "^5.5.1"
terser@^5.5.1:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2"
- integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c"
+ integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"
@@ -476,9 +476,9 @@ watchpack@^2.0.0:
source-map "^0.6.1"
"webpack5@npm:webpack@5":
- version "5.26.2"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.26.2.tgz#b61c018c9abdb7c67b08e2783ba46fe8723f59e8"
- integrity sha512-h07tAPeQceEO3Idrevqv4ECcpMH8Zp0aUUJ+IytujWTVf6TF5PI3rKVw0Z+7rNjU4qJuEx18BykFxgRvR9VgEQ==
+ version "5.27.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.27.1.tgz#6808fb6e45e35290cdb8ae43c7a10884839a3079"
+ integrity sha512-rxIDsPZ3Apl3JcqiemiLmWH+hAq04YeOXqvCxNZOnTp8ZgM9NEPtbu4CaMfMEf9KShnx/Ym8uLGmM6P4XnwCoA==
dependencies:
"@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.46"
diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx
index 108d83258590e..7ea3f794bcfaa 100644
--- a/packages/next/client/image.tsx
+++ b/packages/next/client/image.tsx
@@ -295,8 +295,6 @@ export default function Image({
let sizerStyle: JSX.IntrinsicElements['div']['style'] | undefined
let sizerSvg: string | undefined
let imgStyle: ImgElementStyle | undefined = {
- visibility: isVisible ? 'inherit' : 'hidden',
-
position: 'absolute',
top: 0,
left: 0,
diff --git a/packages/next/compiled/webpack/bundle5.js b/packages/next/compiled/webpack/bundle5.js
index bd3aaafedb520..47952a75754bd 100644
--- a/packages/next/compiled/webpack/bundle5.js
+++ b/packages/next/compiled/webpack/bundle5.js
@@ -46,7 +46,7 @@ module.exports = JSON.parse("{\"definitions\":{\"Rule\":{\"description\":\"Filte
/***/ (function(module) {
"use strict";
-module.exports = JSON.parse("{\"name\":\"terser\",\"description\":\"JavaScript parser, mangler/compressor and beautifier toolkit for ES6+\",\"homepage\":\"https://terser.org\",\"author\":\"Mihai Bazon (http://lisperator.net/)\",\"license\":\"BSD-2-Clause\",\"version\":\"5.6.0\",\"engines\":{\"node\":\">=10\"},\"maintainers\":[\"Fábio Santos \"],\"repository\":\"https://github.com/terser/terser\",\"main\":\"dist/bundle.min.js\",\"type\":\"module\",\"module\":\"./main.js\",\"exports\":{\".\":[{\"import\":\"./main.js\",\"require\":\"./dist/bundle.min.js\"},\"./dist/bundle.min.js\"],\"./package\":\"./package.json\",\"./package.json\":\"./package.json\"},\"types\":\"tools/terser.d.ts\",\"bin\":{\"terser\":\"bin/terser\"},\"files\":[\"bin\",\"dist\",\"lib\",\"tools\",\"LICENSE\",\"README.md\",\"CHANGELOG.md\",\"PATRONS.md\",\"main.js\"],\"dependencies\":{\"commander\":\"^2.20.0\",\"source-map\":\"~0.7.2\",\"source-map-support\":\"~0.5.19\"},\"devDependencies\":{\"@ls-lint/ls-lint\":\"^1.9.2\",\"acorn\":\"^8.0.5\",\"astring\":\"^1.6.2\",\"eslint\":\"^7.19.0\",\"eslump\":\"^2.0.0\",\"esm\":\"^3.2.25\",\"mocha\":\"^8.2.1\",\"pre-commit\":\"^1.2.2\",\"rimraf\":\"^3.0.2\",\"rollup\":\"2.38.4\",\"semver\":\"^7.3.4\"},\"scripts\":{\"test\":\"node test/compress.js && mocha test/mocha\",\"test:compress\":\"node test/compress.js\",\"test:mocha\":\"mocha test/mocha\",\"lint\":\"eslint lib\",\"lint-fix\":\"eslint --fix lib\",\"ls-lint\":\"ls-lint\",\"build\":\"rimraf dist/bundle* && rollup --config --silent\",\"prepare\":\"npm run build\",\"postversion\":\"echo 'Remember to update the changelog!'\"},\"keywords\":[\"uglify\",\"terser\",\"uglify-es\",\"uglify-js\",\"minify\",\"minifier\",\"javascript\",\"ecmascript\",\"es5\",\"es6\",\"es7\",\"es8\",\"es2015\",\"es2016\",\"es2017\",\"async\",\"await\"],\"eslintConfig\":{\"parserOptions\":{\"sourceType\":\"module\",\"ecmaVersion\":\"2020\"},\"env\":{\"node\":true,\"browser\":true,\"es2020\":true},\"globals\":{\"describe\":false,\"it\":false,\"require\":false,\"global\":false,\"process\":false},\"rules\":{\"brace-style\":[\"error\",\"1tbs\",{\"allowSingleLine\":true}],\"quotes\":[\"error\",\"double\",\"avoid-escape\"],\"no-debugger\":\"error\",\"no-undef\":\"error\",\"no-unused-vars\":[\"error\",{\"varsIgnorePattern\":\"^_$\"}],\"no-tabs\":\"error\",\"semi\":[\"error\",\"always\"],\"no-extra-semi\":\"error\",\"no-irregular-whitespace\":\"error\",\"space-before-blocks\":[\"error\",\"always\"]}},\"pre-commit\":[\"build\",\"lint-fix\",\"ls-lint\",\"test\"]}");
+module.exports = JSON.parse("{\"name\":\"terser\",\"description\":\"JavaScript parser, mangler/compressor and beautifier toolkit for ES6+\",\"homepage\":\"https://terser.org\",\"author\":\"Mihai Bazon (http://lisperator.net/)\",\"license\":\"BSD-2-Clause\",\"version\":\"5.6.1\",\"engines\":{\"node\":\">=10\"},\"maintainers\":[\"Fábio Santos \"],\"repository\":\"https://github.com/terser/terser\",\"main\":\"dist/bundle.min.js\",\"type\":\"module\",\"module\":\"./main.js\",\"exports\":{\".\":[{\"import\":\"./main.js\",\"require\":\"./dist/bundle.min.js\"},\"./dist/bundle.min.js\"],\"./package\":\"./package.json\",\"./package.json\":\"./package.json\"},\"types\":\"tools/terser.d.ts\",\"bin\":{\"terser\":\"bin/terser\"},\"files\":[\"bin\",\"dist\",\"lib\",\"tools\",\"LICENSE\",\"README.md\",\"CHANGELOG.md\",\"PATRONS.md\",\"main.js\"],\"dependencies\":{\"commander\":\"^2.20.0\",\"source-map\":\"~0.7.2\",\"source-map-support\":\"~0.5.19\"},\"devDependencies\":{\"@ls-lint/ls-lint\":\"^1.9.2\",\"acorn\":\"^8.0.5\",\"astring\":\"^1.6.2\",\"eslint\":\"^7.19.0\",\"eslump\":\"^2.0.0\",\"esm\":\"^3.2.25\",\"mocha\":\"^8.2.1\",\"pre-commit\":\"^1.2.2\",\"rimraf\":\"^3.0.2\",\"rollup\":\"2.38.4\",\"semver\":\"^7.3.4\"},\"scripts\":{\"test\":\"node test/compress.js && mocha test/mocha\",\"test:compress\":\"node test/compress.js\",\"test:mocha\":\"mocha test/mocha\",\"lint\":\"eslint lib\",\"lint-fix\":\"eslint --fix lib\",\"ls-lint\":\"ls-lint\",\"build\":\"rimraf dist/bundle* && rollup --config --silent\",\"prepare\":\"npm run build\",\"postversion\":\"echo 'Remember to update the changelog!'\"},\"keywords\":[\"uglify\",\"terser\",\"uglify-es\",\"uglify-js\",\"minify\",\"minifier\",\"javascript\",\"ecmascript\",\"es5\",\"es6\",\"es7\",\"es8\",\"es2015\",\"es2016\",\"es2017\",\"async\",\"await\"],\"eslintConfig\":{\"parserOptions\":{\"sourceType\":\"module\",\"ecmaVersion\":\"2020\"},\"env\":{\"node\":true,\"browser\":true,\"es2020\":true},\"globals\":{\"describe\":false,\"it\":false,\"require\":false,\"global\":false,\"process\":false},\"rules\":{\"brace-style\":[\"error\",\"1tbs\",{\"allowSingleLine\":true}],\"quotes\":[\"error\",\"double\",\"avoid-escape\"],\"no-debugger\":\"error\",\"no-undef\":\"error\",\"no-unused-vars\":[\"error\",{\"varsIgnorePattern\":\"^_$\"}],\"no-tabs\":\"error\",\"semi\":[\"error\",\"always\"],\"no-extra-semi\":\"error\",\"no-irregular-whitespace\":\"error\",\"space-before-blocks\":[\"error\",\"always\"]}},\"pre-commit\":[\"build\",\"lint-fix\",\"ls-lint\",\"test\"]}");
/***/ }),
@@ -54,7 +54,7 @@ module.exports = JSON.parse("{\"name\":\"terser\",\"description\":\"JavaScript p
/***/ (function(module) {
"use strict";
-module.exports = {"i8":"5.26.2"};
+module.exports = {"i8":"5.27.1"};
/***/ }),
@@ -58682,7 +58682,18 @@ module.exports = class MultiCompiler {
* @returns {SetupResult[]} result of setup
*/
_runGraph(setup, run, callback) {
- /** @typedef {{ compiler: Compiler, result: Stats, state: "blocked" | "queued" | "running" | "done", children: Node[], parents: Node[] }} Node */
+ /** @typedef {{ compiler: Compiler, result: Stats, state: "pending" | "blocked" | "queued" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */
+
+ // State transitions for nodes:
+ // -> blocked (initial)
+ // blocked -> queued [add to queue] (when all parents done)
+ // queued -> running [running++] (when processing the queue)
+ // running -> done [running--] (when compilation is done)
+ // done -> pending (when invalidated from file change)
+ // pending -> blocked (when invalidated from aggregated changes)
+ // done -> blocked (when invalidated, from parent invalidation)
+ // running -> running-outdated (when invalidated, either from change or parent invalidation)
+ // running-outdated -> blocked [running--] (when compilation is done)
/** @type {Node[]} */
const nodes = this.compilers.map(compiler => ({
@@ -58740,13 +58751,12 @@ module.exports = class MultiCompiler {
running--;
if (node.state === "running") {
node.state = "done";
- }
- for (const child of node.children) {
- if (child.state !== "blocked") continue;
- if (child.parents.every(p => p.state === "done")) {
- child.state = "queued";
- queue.enqueue(child);
+ for (const child of node.children) {
+ checkUnblocked(child);
}
+ } else if (node.state === "running-outdated") {
+ node.state = "blocked";
+ checkUnblocked(node);
}
process.nextTick(processQueue);
};
@@ -58754,12 +58764,28 @@ module.exports = class MultiCompiler {
* @param {Node} node node
* @returns {void}
*/
- const nodeInvalid = node => {
- if (node.state === "done" || node.state === "running") {
+ const nodeInvalidFromParent = node => {
+ if (node.state === "done") {
node.state = "blocked";
+ } else if (node.state === "running") {
+ node.state = "running-outdated";
}
for (const child of node.children) {
- nodeInvalid(child);
+ nodeInvalidFromParent(child);
+ }
+ };
+ /**
+ * @param {Node} node node
+ * @returns {void}
+ */
+ const nodeInvalid = node => {
+ if (node.state === "done") {
+ node.state = "pending";
+ } else if (node.state === "running") {
+ node.state = "running-outdated";
+ }
+ for (const child of node.children) {
+ nodeInvalidFromParent(child);
}
};
/**
@@ -58768,15 +58794,26 @@ module.exports = class MultiCompiler {
*/
const nodeChange = node => {
nodeInvalid(node);
+ if (node.state === "pending") {
+ node.state = "blocked";
+ }
+ checkUnblocked(node);
+ processQueue();
+ };
+ /**
+ * @param {Node} node node
+ * @returns {void}
+ */
+ const checkUnblocked = node => {
if (
node.state === "blocked" &&
node.parents.every(p => p.state === "done")
) {
node.state = "queued";
queue.enqueue(node);
- processQueue();
}
};
+
const setupResults = [];
nodes.forEach((node, i) => {
setupResults.push(
@@ -58784,7 +58821,7 @@ module.exports = class MultiCompiler {
node.compiler,
i,
nodeDone.bind(null, node),
- () => node.state === "blocked" || node.state === "queued",
+ () => node.state !== "done" && node.state !== "running",
() => nodeChange(node),
() => nodeInvalid(node)
)
@@ -58798,7 +58835,11 @@ module.exports = class MultiCompiler {
node.state = "running";
run(node.compiler, nodeDone.bind(null, node));
}
- if (!errored && running === 0) {
+ if (
+ !errored &&
+ running === 0 &&
+ nodes.every(node => node.state === "done")
+ ) {
const stats = [];
for (const node of nodes) {
const result = node.result;
@@ -59414,7 +59455,7 @@ const {
} = __webpack_require__(21699);
const createHash = __webpack_require__(34627);
const { join } = __webpack_require__(71593);
-const { contextify } = __webpack_require__(47779);
+const { contextify, absolutify } = __webpack_require__(47779);
const makeSerializable = __webpack_require__(55575);
const memoize = __webpack_require__(18003);
@@ -59818,6 +59859,30 @@ class NormalModule extends Module {
}
};
};
+ const getAbsolutify = memoize(() =>
+ absolutify.bindCache(compilation.compiler.root)
+ );
+ const getAbsolutifyInContext = memoize(() =>
+ absolutify.bindContextCache(this.context, compilation.compiler.root)
+ );
+ const getContextify = memoize(() =>
+ contextify.bindCache(compilation.compiler.root)
+ );
+ const getContextifyInContext = memoize(() =>
+ contextify.bindContextCache(this.context, compilation.compiler.root)
+ );
+ const utils = {
+ absolutify: (context, request) => {
+ return context === this.context
+ ? getAbsolutifyInContext()(request)
+ : getAbsolutify()(context, request);
+ },
+ contextify: (context, request) => {
+ return context === this.context
+ ? getContextifyInContext()(request)
+ : getContextify()(context, request);
+ }
+ };
const loaderContext = {
version: 2,
getOptions: schema => {
@@ -59930,6 +59995,7 @@ class NormalModule extends Module {
}
this.buildInfo.buildDependencies.add(dep);
},
+ utils,
rootContext: options.context,
webpack: true,
sourceMap: !!this.useSourceMap,
@@ -61227,7 +61293,7 @@ class NormalModuleFactory extends ModuleFactory {
}
// resource without scheme and without path
- else if (/^($|\?|#)/.test(unresolvedResource)) {
+ else if (/^($|\?)/.test(unresolvedResource)) {
resourceData = {
resource: unresolvedResource,
data: {},
@@ -62978,6 +63044,7 @@ class RecordIdsPlugin {
}
if (records.chunks.bySource) {
for (const chunk of chunks) {
+ if (chunk.id !== null) continue;
const sources = getChunkSources(chunk);
for (const source of sources) {
const id = records.chunks.bySource[source];
@@ -92719,7 +92786,10 @@ const { RuntimeGlobals } = __webpack_require__(16520);
const HotUpdateChunk = __webpack_require__(90972);
const Template = __webpack_require__(90751);
const { getCompilationHooks } = __webpack_require__(80867);
-const { generateEntryStartup } = __webpack_require__(9005);
+const {
+ generateEntryStartup,
+ updateHashForEntryStartup
+} = __webpack_require__(9005);
/** @typedef {import("../Compiler")} Compiler */
@@ -92849,6 +92919,10 @@ class ArrayPushCallbackChunkFormatPlugin {
hash.update(`${runtimeTemplate.outputOptions.chunkLoadingGlobal}`);
hash.update(`${runtimeTemplate.outputOptions.hotUpdateGlobal}`);
hash.update(`${runtimeTemplate.outputOptions.globalObject}`);
+ const entries = Array.from(
+ chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
+ );
+ updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
}
);
}
@@ -93364,7 +93438,10 @@ const {
getChunkFilenameTemplate,
getCompilationHooks
} = __webpack_require__(80867);
-const { generateEntryStartup } = __webpack_require__(9005);
+const {
+ generateEntryStartup,
+ updateHashForEntryStartup
+} = __webpack_require__(9005);
/** @typedef {import("../Compiler")} Compiler */
@@ -93508,6 +93585,10 @@ class CommonJsChunkFormatPlugin {
if (chunk.hasRuntime()) return;
hash.update("CommonJsChunkFormatPlugin");
hash.update("1");
+ const entries = Array.from(
+ chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)
+ );
+ updateHashForEntryStartup(hash, chunkGraph, entries, chunk);
}
);
}
@@ -98945,6 +99026,7 @@ const Template = __webpack_require__(90751);
const { isSubset } = __webpack_require__(86088);
const { chunkHasJs } = __webpack_require__(80867);
+/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
@@ -99005,21 +99087,24 @@ exports.generateEntryStartup = (
return `__webpack_exec__(${JSON.stringify(id)})`;
};
const outputCombination = (chunks, moduleIds, final) => {
- const old = final ? "undefined" : "0";
- const prefix = final ? EXPORT_PREFIX : "";
if (chunks.size === 0) {
- runtime.push(`${prefix}(${moduleIds.map(runModule).join(", ")});`);
+ runtime.push(
+ `${final ? EXPORT_PREFIX : ""}(${moduleIds.map(runModule).join(", ")});`
+ );
} else {
const fn = runtimeTemplate.returningFunction(
moduleIds.map(runModule).join(", ")
);
runtime.push(
- `${prefix}${
+ `${final && !passive ? EXPORT_PREFIX : ""}${
passive
? RuntimeGlobals.onChunksLoaded
: RuntimeGlobals.startupEntrypoint
- }(${old}, ${JSON.stringify(Array.from(chunks, c => c.id))}, ${fn});`
+ }(0, ${JSON.stringify(Array.from(chunks, c => c.id))}, ${fn});`
);
+ if (final && passive) {
+ runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`);
+ }
}
};
@@ -99053,6 +99138,23 @@ exports.generateEntryStartup = (
return Template.asString(runtime);
};
+/**
+ * @param {Hash} hash the hash to update
+ * @param {ChunkGraph} chunkGraph chunkGraph
+ * @param {import("../ChunkGraph").EntryModuleWithChunkGroup[]} entries entries
+ * @param {Chunk} chunk chunk
+ * @returns {void}
+ */
+exports.updateHashForEntryStartup = (hash, chunkGraph, entries, chunk) => {
+ for (const [module, entrypoint] of entries) {
+ const runtimeChunk = entrypoint.getRuntimeChunk();
+ const moduleId = chunkGraph.getModuleId(module);
+ hash.update(`${moduleId}`);
+ for (const c of getAllChunks(entrypoint, chunk, runtimeChunk))
+ hash.update(`${c.id}`);
+ }
+};
+
/**
* @param {Chunk} chunk the chunk
* @param {ChunkGraph} chunkGraph the chunk graph
diff --git a/packages/next/package.json b/packages/next/package.json
index a8dbc89d5e487..d18dd00d6a679 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -62,10 +62,10 @@
"dependencies": {
"@babel/runtime": "7.12.5",
"@hapi/accept": "5.0.1",
- "@next/env": "10.0.10-canary.6",
- "@next/polyfill-module": "10.0.10-canary.6",
- "@next/react-dev-overlay": "10.0.10-canary.6",
- "@next/react-refresh-utils": "10.0.10-canary.6",
+ "@next/env": "10.0.10-canary.7",
+ "@next/polyfill-module": "10.0.10-canary.7",
+ "@next/react-dev-overlay": "10.0.10-canary.7",
+ "@next/react-refresh-utils": "10.0.10-canary.7",
"@opentelemetry/api": "0.14.0",
"ast-types": "0.13.2",
"browserslist": "4.16.1",
@@ -133,7 +133,7 @@
"@babel/preset-react": "7.12.10",
"@babel/preset-typescript": "7.12.7",
"@babel/types": "7.12.12",
- "@next/polyfill-nomodule": "10.0.10-canary.6",
+ "@next/polyfill-nomodule": "10.0.10-canary.7",
"@taskr/clear": "1.1.0",
"@taskr/esnext": "1.1.0",
"@taskr/watch": "1.1.0",
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index cf624183496f1..33da6c8c7cbcf 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index a67bbae34d090..60356ea22ae95 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "10.0.10-canary.6",
+ "version": "10.0.10-canary.7",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/test/integration/image-component/base-path/test/index.test.js b/test/integration/image-component/base-path/test/index.test.js
index e5c98edf13f20..bda5a9e8f27e5 100644
--- a/test/integration/image-component/base-path/test/index.test.js
+++ b/test/integration/image-component/base-path/test/index.test.js
@@ -51,23 +51,6 @@ async function getComputed(browser, id, prop) {
return null
}
-async function getComputedStyle(browser, id, prop) {
- const val = await browser.eval(
- `window.getComputedStyle(document.getElementById('${id}')).${prop}`
- )
- if (typeof val === 'number') {
- return val
- }
- if (typeof val === 'string') {
- const v = parseInt(val, 10)
- if (isNaN(v)) {
- return val
- }
- return v
- }
- return null
-}
-
async function getSrc(browser, id) {
const src = await browser.elementById(id).getAttribute('src')
if (src) {
@@ -442,44 +425,6 @@ function runTests(mode) {
})
}
- it('should correctly inherit the visibilty of the parent component', async () => {
- let browser
- try {
- browser = await webdriver(appPort, '/docs/hidden-parent')
-
- const id = 'hidden-image'
-
- // Wait for image to load:
- await check(async () => {
- const result = await browser.eval(
- `document.getElementById(${JSON.stringify(id)}).naturalWidth`
- )
-
- if (result < 1) {
- throw new Error('Image not ready')
- }
-
- return 'result-correct'
- }, /result-correct/)
-
- await waitFor(1000)
-
- const desiredVisibilty = await getComputed(
- browser,
- id,
- 'style.visibility'
- )
- expect(desiredVisibilty).toBe('inherit')
-
- const actualVisibility = await getComputedStyle(browser, id, 'visibility')
- expect(actualVisibility).toBe('hidden')
- } finally {
- if (browser) {
- await browser.close()
- }
- }
- })
-
it('should correctly ignore prose styles', async () => {
let browser
try {
diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js
index 4d8f90329ba7e..fa96399819eaa 100644
--- a/test/integration/image-component/default/test/index.test.js
+++ b/test/integration/image-component/default/test/index.test.js
@@ -53,23 +53,6 @@ async function getComputed(browser, id, prop) {
return null
}
-async function getComputedStyle(browser, id, prop) {
- const val = await browser.eval(
- `window.getComputedStyle(document.getElementById('${id}')).${prop}`
- )
- if (typeof val === 'number') {
- return val
- }
- if (typeof val === 'string') {
- const v = parseInt(val, 10)
- if (isNaN(v)) {
- return val
- }
- return v
- }
- return null
-}
-
async function getSrc(browser, id) {
const src = await browser.elementById(id).getAttribute('src')
if (src) {
@@ -505,44 +488,6 @@ function runTests(mode) {
})
}
- it('should correctly inherit the visibilty of the parent component', async () => {
- let browser
- try {
- browser = await webdriver(appPort, '/hidden-parent')
-
- const id = 'hidden-image'
-
- // Wait for image to load:
- await check(async () => {
- const result = await browser.eval(
- `document.getElementById(${JSON.stringify(id)}).naturalWidth`
- )
-
- if (result < 1) {
- throw new Error('Image not ready')
- }
-
- return 'result-correct'
- }, /result-correct/)
-
- await waitFor(1000)
-
- const desiredVisibilty = await getComputed(
- browser,
- id,
- 'style.visibility'
- )
- expect(desiredVisibilty).toBe('inherit')
-
- const actualVisibility = await getComputedStyle(browser, id, 'visibility')
- expect(actualVisibility).toBe('hidden')
- } finally {
- if (browser) {
- await browser.close()
- }
- }
- })
-
it('should correctly ignore prose styles', async () => {
let browser
try {