Overview
Setup
As of march 2021 you need node version 10.13 or later to run NextJS. Create a folder an initialize your project with
npx create-next-app my-app-name cd my-app-name yarn dev - Starts development server on localhost:3000 yarn build - builds the application for production usage yarn start - starts a Next.js production server
What we will get is
- Automatic compilation and bundling (with webpack and babel)
- React Fast Refresh
- Static generation and server-side rendering of
./pages/
(see Pages and Routing below) - Static file serving.
./public/
is mapped to/
(see Assets below)
Pages and Routing
In Next.js, a page is a React Component exported from a file (.js
, .jsx
, .ts
, or .tsx
) in the pages
directory. The component can have any name, but you must export it as a default
export.
pages/index.js
is associated with the/
route.pages/posts/first-post.js
is associated with the/posts/first-post
route.
To match a dynamic segment you can use the bracket-syntax. This allows you to match named parameters:
pages/blog/[slug].js
→/blog/:slug
(/blog/hello-world
)pages/[username]/settings.js
→/:username/settings
(/foo/settings
)pages/post/[...all].js
→/post/*
(/post/2020/id/title
)
See ‘Statically Generated Pages with Dynamic Routes’ below to learn more.
Client-Side Navigation with <Link>
You use <a> tag and wrap them in a Link Component
import Link from 'next/link' export default function FirstPost() { return ( <> <h1>First Post</h1> <h2> <Link href="/"> <a>Back to home</a> </Link> </h2> </> ) }
Of course, you can also interpolate your links:
import Link from 'next/link' function Posts({ posts }) { return ( <ul> {posts.map((post) => ( <li key={post.id}> <Link href={`/blog/${encodeURIComponent(post.slug)}`}> <a>{post.title}</a> </Link> </li> ))} </ul> ) } export default Posts
Or alternatively use a URL object:
<Link href={{ pathname: '/blog/[slug]', query: { slug: post.slug }, }} >
In a production build of Next.js, whenever Link
components appear in the browser’s viewport, Next.js automatically prefetches the code for the linked page in the background. By the time you click the link, the code for the destination page will already be loaded in the background, and the page transition will be near-instant.
If you need to link to an external page outside the Next.js app, just use an <a>
tag without Link
. If you need to add attributes like, for example, className
, add it to the a
tag, not to the Link
tag.
Linking imperatively
<Link>
should be able to cover most of your routing needs, but you can also do client-side navigation imperatively:
import { useRouter } from 'next/router' function ReadMore() { const router = useRouter() return ( <span onClick={() => router.push('/about')}>Click here to read more</span> ) } export default ReadMore
Change URL without fetching data again
Also called Shallow Routing. getServerSideProps
, getStaticProps
, and getInitialProps
will not be called, but you will still receive the updated pathname
and the query
via the router
object when you set the shallow
option to true
:
import { useEffect } from 'react' import { useRouter } from 'next/router' // Current URL is '/' function Page() { const router = useRouter() useEffect(() => { // Always do navigations after the first render router.push('/?counter=10', undefined, { shallow: true }) }, []) useEffect(() => { // The counter changed! }, [router.query.counter]) } export default Page
Shallow routing only works for same-page URL changes.
Assets (such as images)
Files inside public
can be referenced from the root of the application. That is useful to serve static assets like robots.txt
and images. For example, if you add an image to public/me.png
, the following code will access the image:
import Image from 'next/image' function Avatar() { return <Image src="/me.png" alt="me" width="64" height="64" /> } export default Avatar
Note: Be sure to not have a static file with the same name as a file in the pages/
directory, as this will result in an error.
Next.js provides an Image
component out of the box to handle the following for you:
- Ensuring your image is responsive on different screen sizes
- Optimizing your images without the need of a third-party tool or library
- Only loading images when they enter the viewport (Lazy loading)
Image Optimization
The Automatic Image Optimization (since version 10) allows for resizing, optimizing, and serving images in modern formats like WebP when the browser supports it. This avoids shipping large images to devices with a smaller viewport. It also allows Next.js to automatically adopt future image formats and serve them to browsers that support those formats.
Automatic Image Optimization works with any image source, even if the image is hosted by an external data source, like a CMS. Next.js optimizes images on-demand, as users request them, not at build time.
import Image from 'next/image' const YourComponent = () => ( <Image src="/images/profile.jpg" // Route of the image file height={144} // Desired size with correct aspect ratio width={144} // Desired size with correct aspect ratio alt="Your Name" /> )
Images are optimized dynamically upon request and stored in the <distDir>/cache/images
directory. That means it does not work when using next export
, which exports static files. The expiration (or rather Max Age) is defined by the upstream server’s Cache-Control
header. You can optionally configure Image Optimization for more advanced use cases via next.config.js
.
Image layout
The layout behavior of the image changes as the viewport changes size. Defaults to intrinsic
. When fixed
, the image dimensions will not change as the viewport changes (no responsiveness) similar to the native img
element. When intrinsic
, the image will scale the dimensions down for smaller viewports but maintain the original dimensions for larger viewports. When responsive
, the image will scale the dimensions down for smaller viewports and scale up for larger viewports. When fill
, the image will stretch both width and height to the dimensions of the parent element, usually paired with object-fit. Demos can be found here.
Image domains
To enable Image Optimization for images hosted on an external website, use an absolute url for the Image src
and specify which domains
are allowed to be optimized. This is needed to ensure that external urls can’t be abused. When loader
is set to an external image service, this option is ignored.
module.exports = { images: { domains: ['example.com'], }, }
Metadata in <Head>
You change metadata by using <Head>
Component instead of <head>
element.
import Head from 'next/head' export default function FirstPost() { return ( <> <Head> <title>First Post</title> </Head> <h1>First Post</h1> </> ) }
CSS Styling
Next.js allows several possibilities for styling:
- Built-in support for CSS and Sass which allows you to import
.css
and.scss
files (still needsnpm install sass
) - Built-in support for CSS modules via
import ./mycomponent.module.css
which will automatically generate unique class names. Next.js’s code splitting feature works on CSS Modules as well. It ensures the minimal amount of CSS is loaded for each page. - CSS-in-JS libraries such as styled-jsx (built-in support), styled-components or emotion
- Using popular CSS libraries like Tailwind CSS
To load global CSS files, create a file called pages/_app.js
and import styles/global.css
for example:
// pages/_app.js import '../styles/global.css' // also direct imports from node_modules are allowed import 'bootstrap/dist/css/bootstrap.css' export default function App({ Component, pageProps }) { return <Component {...pageProps} /> }
This App
component is the top-level component which will be common across all the different pages. You can use this App
component to keep state when navigating between pages, for example. You cannot import global CSS anywhere else.
Customizing PostCSS Config
Out of the box, with no configuration, Next.js compiles CSS using PostCSS. To customize PostCSS config, you can create a top-level file called postcss.config.js
. This is useful if you’re using libraries like Tailwind CSS.
npm install tailwindcss postcss-preset-env postcss-flexbugs-fixes
// postcss.config.js module.exports = { plugins: [ 'tailwindcss', 'postcss-flexbugs-fixes', [ 'postcss-preset-env', { autoprefixer: { flexbox: 'no-2009' }, stage: 3, features: { 'custom-properties': false } } ] ] }
// tailwind.config.js module.exports = { purge: [ // Use *.tsx if using TypeScript './pages/**/*.js', './components/**/*.js' ] // ... }
Rendering techniques
By default, Next.js pre-renders every page using Static Generation without fetching data. 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.)
Next.js has two forms of pre-rendering:
- Static Site Generation (SSG) using getStaticProps() is the pre-rendering method that generates the HTML at build time. The pre-rendered HTML is then reused on each request.
- Server-side Rendering (SSR) using getServerSideProps() is the pre-rendering method that generates the HTML on each request.
You can create a “hybrid” Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
When to use which kind of rendering?

In development mode (when you run npm run dev
or yarn dev
), every page is pre-rendered on each request — even for pages that use Static Generation.
It is recommended to use 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 should use getStaticProps
if:
- The data required to render the page is available at build time ahead of a user’s request.
- The data comes from a headless CMS.
- The data can be publicly cached (not user-specific).
- The page must be pre-rendered (for SEO) and be very fast —
getStaticProps
generates HTML and JSON files, both of which can be cached by a CDN for performance.
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. You won’t be able to use data with static generation that’s only available during request time, such as query parameters or HTTP headers. In that case, you can use Server-side Rendering. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate frequently updated data.
Static Site Generation (SSG)
If you export an async getStaticProps function, it will get called at build time and fetch external data before it is send as props to the page. For example your blog page might need to fetch the list of blog posts from a CMS.
export default function Home(props) { ... } export async function getStaticProps(context) { // context = { // params - route parameters for pages using dynamic routes // preview - is true if the page is in the preview mode // previewData - contains the preview data set by setPreviewData // locale - contains the active locale (if enabled) // locales - contains all supported locales (if enabled) // defaultLocale - contains the configured default locale (if enabled) // } // You should not use fetch() to call an API route in getStaticProps. // Instead, directly import the logic used inside your API route // Get external data from the file system, API, DB, etc. const data = ... // The value of the `props` key will be passed to the `Home` component return { props: ... // A required object with the props that will be received by the page component revalidate: ... // An optional amount in seconds after which a page re-generation can occur (see Incremental Static Regeneration) notFound: ... // An optional boolean value to allow the page to return a 404 status and page redirect: { destination: '/', permanent: false, }, // An optional redirect value to allow redirecting to internal and external resources } }
- In development mode,
getStaticProps
runs on each request instead. - Next.js polyfills
fetch()
on both the client and server. You don’t need to import it. getStaticProps
only runs on the server-side, that means you can write code such as direct database queries without them being sent to browsers.getStaticProps
can only be exported from a page. You can’t export it from non-page files.
Server Side Rendering (SSR)
To use Server-side Rendering, you need to export getServerSideProps
instead of getStaticProps
from your page.
export async function getServerSideProps(context) { return { props: { // props for your component } } }
Client-side Rendering
You first statically generate (pre-render) parts of the page that do not require external data. Then, when the page loads, you fetch external data from the client using JavaScript and populate the remaining parts. This approach works well for user dashboard pages, for example. Because a dashboard is a private, user-specific page, SEO is not relevant, and the page doesn’t need to be pre-rendered. The data is frequently updated, which requires request-time data fetching.
The team behind Next.js has created a React hook for data fetching called SWR. It is highly recommended if you’re fetching data on the client side. It handles caching, revalidation, focus tracking, refetching on interval, and more.
import useSWR from 'swr' function Profile() { const { data, error } = useSWR('/api/user', fetch) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return <div>hello {data.name}!</div> }
Statically Generated Pages with Dynamic Routes (getStaticPaths)
If you want to statically generate a page at a path called /posts/<id>
where <id>
can be dynamic, then you first have to create a page /pages/posts/[id].js
(yes, explicitly use brackets in the file):
import Layout from '../../components/layout' export default function Post() { return <Layout> <Head> <title>{postData.title}</title> </Head> </Layout> } export async function getStaticPaths() { // Return a list of possible value for id const paths = getAllPostIds(); // paths MUST be an array of objects, each // containing a 'params' key and an object with 'id' because // we are using [id] in the file name. // Example { params: { id: 4711 } } return { paths, fallback: false // any paths not returned by getStaticPaths will result in a 404 page } } export async function getStaticProps({ params }) { // Fetch necessary data for the blog post using params.id const postData = getPostData(params.id) return { props: { postData } } }
You could access the router directly:
// pages/post/[pid].js will match for example: // /post/abc -> { "pid": "abc" } // /post/1 -> { "pid": 1 } // /post/abc?foo=bar -> { "foo": "bar", "pid": "abc" } // /post/abc?pid=123 -> { "pid": "abc" } route param pid=abc has priority over query param pid=123 // page pages/post/[pid]/[comment].js -> { "pid": "abc", "comment": "a-comment" } import { useRouter } from 'next/router' const Post = () => { const router = useRouter() const { pid } = router.query return <p>Post: {pid}</p> } export default Post
Catch-All Routes […slug]
Dynamic routes can be extended to catch all paths by adding three dots (...
) inside the brackets: pages/post/[...slug].js
matches /post/a
, but also /post/a/b
, /post/a/b/c
and so on. The resulting query object would be:
// the file pages/post/[...slug].js // if called as /post/a would result as query: { "slug": ["a"] } // if called as /post/a/b would result as query: { "slug": ["a", "b"] }
Optional Catch-All Routes [[…slug]]
Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]
).
For example, pages/post/[[...slug]].js
will match /post
, /post/a
, /post/a/b
, and so on.
The main difference between catch all and optional catch all routes is that with optional, the route without the parameter is also matched. The query
objects are as follows:
{ } // GET `/post` (empty object) { "slug": ["a"] } // `GET /post/a` (single-element array) { "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)
Note: Predefined routes take precedence over dynamic routes, and dynamic routes over catch all routes.
Fallback
If fallback is false
, then any paths not returned by getStaticPaths
will result in a 404 page. If fallback
is true
, then the behavior of getStaticProps
changes:
- The paths returned from
getStaticPaths
will be rendered to HTML at build time bygetStaticProps
. - The paths that have not been generated at build time will not result in a 404 page. Instead, Next.js will serve a “fallback” version of the page on the first request to such a path (see “Fallback pages” below for details).
- In the background, Next.js will statically generate the requested path HTML and JSON. This includes running
getStaticProps
. - When that’s done, the browser receives the JSON for the generated path. This will be used to automatically render the page with the required props. From the user’s perspective, the page will be swapped from the fallback page to the full page.
- At the same time, Next.js adds this path to the list of pre-rendered pages. Subsequent requests to the same path will serve the generated page, just like other pages pre-rendered at build time.
// pages/posts/[id].js import { useRouter } from 'next/router' function Post({ post }) { const router = useRouter() // If the page is not yet generated, this will be displayed // initially until getStaticProps() finishes running if (router.isFallback) { return <div>Loading...</div> } // Render post... } // This function gets called at build time export async function getStaticPaths() { return { // Only `/posts/1` and `/posts/2` are generated at build time paths: [{ params: { id: '1' } }, { params: { id: '2' } }], // Enable statically generating additional pages // For example: `/posts/3` fallback: true, } } // 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 }, // Re-generate the post at most once per second // if a request comes in revalidate: 1, } } export default Post
fallback: true
is useful if your app has a very large number of static pages that depend on data (think: a very large e-commerce site). You want to pre-render all product pages, but then your builds would take forever.
Instead, you may statically generate a small subset of pages and use fallback: true
for the rest. When someone requests a page that’s not generated yet, the user will see the page with a loading indicator. Shortly after, getStaticProps
finishes and the page will be rendered with the requested data. From now on, everyone who requests the same page will get the statically pre-rendered page.
This ensures that users always have a fast experience while preserving fast builds and the benefits of Static Generation.
fallback: true
will not update generated pages, for that take a look at Incremental Static Regeneration.
Incremental Static Regeneration
Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in. This ensures traffic is served uninterruptedly, always from static storage, and the newly built page is pushed only after it’s done generating.
// This function gets called at build time on server-side. // It may be called again, on a serverless function, if // revalidation is enabled and a new request comes in export async function getStaticProps() { const res = await fetch('https://.../posts') const posts = await res.json() return { props: { posts, }, // Next.js will attempt to re-generate the page: // - When a request comes in // - At most once every second revalidate: 1, // In seconds } } export default Blog
Reading files within getStaticProps
Since Next.js compiles your code into a separate directory you can’t use __dirname
. Instead you can use process.cwd()
:
import { promises as fs } from 'fs' import path from 'path' // posts will be populated at build time by getStaticProps() function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li> <h3>{post.filename}</h3> <p>{post.content}</p> </li> ))} </ul> ) } // This function gets called at build time on server-side. // It won't be called on client-side, so you can even do // direct database queries. See the "Technical details" section. export async function getStaticProps() { const postsDirectory = path.join(process.cwd(), 'posts') const filenames = await fs.readdir(postsDirectory) const posts = filenames.map(async (filename) => { const filePath = path.join(postsDirectory, filename) const fileContents = await fs.readFile(filePath, 'utf8') // Generally you would parse/transform the contents // For example you can transform markdown to HTML here return { filename, content: fileContents, } }) // By returning { props: { posts } }, the Blog component // will receive `posts` as a prop at build time return { props: { posts: await Promise.all(posts), }, } } export default Blog
API Routes
API Routes let you create an API endpoint inside a Next.js app. An endpoint that shall be available at localhost:3000/api/hello
must have a file pages/api/hello.js
with the following content:
// req = HTTP incoming message, res = HTTP server response export default function handler(req, res) { res.status(200).json({ text: 'Hello' }); // res.json(json) - Sends a JSON response. // res.send(body) - Sends the HTTP response // res.redirect([status,] path) - Redirects to a specified path or URL }
To handle different HTTP methods:
export default function handler(req, res) { if (req.method === 'POST') { // Process a POST request } else { // Handle any other HTTP method } }
Dynamic API routes
API routes support dynamic routes, and follow the same file naming rules used for pages
. For example, the API route pages/api/post/[pid].js
has the following code:
export default function handler(req, res) { const { pid } = req.query res.end(`Post: ${pid}`) }
Catch-all API routes
pages/api/post/[...slug].js
matches /api/post/a
, but also /api/post/a/b
, /api/post/a/b/c
and so on, but it does not match /api/post
. For that, you would either need pages/api/post/[[...slug]].js
(“optional catch all”), /api/posts.js
or /api/posts/index.js
.
Do’s and dont’s
You should not fetch an API Route from getStaticProps
or getStaticPaths
. Instead, write your server-side code directly in getStaticProps
or getStaticPaths
(or call a helper function).
A good use case for API Routes is handling form input. For example, you can create a form on your page and have it send a POST
request to your API Route. You can then write code to directly save it to your database. The API Route code will not be part of your client bundle, so you can safely write server-side code. API Routes can be dynamic, just like regular pages.
API Middleware
Use them to read and/or further modify incoming requests, such as req.cookies, req.query or req.body.
Every API route can export a config
object to change the default configs:
export const config = { api: { bodyParser: { sizeLimit: '1mb', }, }, }
bodyParser
Enables body parsing, you can disable it if you want to consume it as a Stream
:
export const config = { api: { bodyParser: false, }, }
Enabling CORS
npm i cors # or yarn add cors
import Cors from 'cors' // Initializing the cors middleware const cors = Cors({ methods: ['GET', 'HEAD'], }) // Helper method to wait for a middleware to execute before continuing // And to throw an error when an error happens in a middleware function runMiddleware(req, res, fn) { return new Promise((resolve, reject) => { fn(req, res, (result) => { if (result instanceof Error) { return reject(result) } return resolve(result) }) }) } async function handler(req, res) { // Run the middleware await runMiddleware(req, res, cors) // Rest of the API logic res.json({ message: 'Hello Everyone!' }) } export default handler
TypeScript and API Routes
You can read how to extend req and res objects with TypeScript on the offical doc page.
Deploying your app
next build
builds the production application in the .next
folder. After building, next start
starts a Node.js server that supports hybrid pages, serving both statically generated and server-side rendered pages.
According to the makers of Next.js, the easiest way to deploy Next.js to production is to use the Vercel platform developed by the creators of Next.js. Vercel is an all-in-one platform with Global CDN supporting static & JAMstack deployment and Serverless Functions.
Once you’re signed up, import your application
from your Code Repo on Vercel. When it’s done, you’ll get deployment URLs. Click on one of the URLs and you should see the Next.js starter page live.
When you deploy your Next.js app to Vercel, the following happens by default:
- Pages that use Static Generation and assets (JS, CSS, images, fonts, etc) will automatically be served from the Vercel Edge Network, which is blazingly fast.
- Pages that use Server-Side Rendering and API routes will automatically become isolated Serverless Functions. This allows page rendering and API requests to scale infinitely.
Vercel has many more features, such as Custom Domains, Environment Variables and Automatic HTTPS that auto-renew themselves.
Develop, Preview, Ship
We’ve just gone through the workflow we call DPS: Develop, Preview, and Ship.
- Develop: We’ve written code in Next.js and used the Next.js development server running to take advantage of its hot reloading feature.
- Preview: We’ve pushed changes to a branch on GitHub, and Vercel created a preview deployment that’s available via a URL. We can share this preview URL with others for feedback. In addition to doing code reviews, you can do deployment previews.
- Ship: We’ve merged the pull request to
main
to ship to production.
We strongly recommend using this workflow when developing Next.js apps — it will help you iterate on your app faster.
Exporting and deploying application as static files
You can export your app by generating static files with next build && next export
to an out
directory. Use -o
to specify a custom out directory. The upload those static files on a webserver.
To only export specific pages:
// next.config.js module.exports = { exportPathMap: async function ( defaultPathMap, { dev, dir, outDir, distDir, buildId } ) { return { '/': { page: '/' }, '/about': { page: '/about' }, // /pages/about.js '/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } }, // /pages/post.js '/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } }, '/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }, } }, }
Keep in mind that next/image does not work with static file exporting. You will get an error like this:
Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
Explanation can be read here.
Using NextJS with TypeScript
Create an empty tsconfig.json
file in the root of your project and add dependencies:
# If you’re using npm npm install --save-dev typescript @types/react @types/node # If you’re using Yarn yarn add --dev typescript @types/react @types/node
Now run yarn dev
to start the server and Next.js will:
- Populate the
tsconfig.json
file for you. You may customize this file. - Create the
next-env.d.ts
file, which ensures Next.js types are picked up by the TypeScript compiler. You should not touch this file.
Here is how you use the types:
import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next' export const getStaticProps: GetStaticProps = async context => { // ... } export const getStaticPaths: GetStaticPaths = async () => { // ... } export const getServerSideProps: GetServerSideProps = async context => { // ... }
This is how you use API Routes with TypeScript:
import { NextApiRequest, NextApiResponse } from 'next' export default (req: NextApiRequest, res: NextApiResponse) => { // ... }
You can convert pages/_app.js
into pages/_app.tsx
and use the built-in type AppProps
, like so:
import { AppProps } from 'next/app' function App({ Component, pageProps }: AppProps) { return <Component {...pageProps} /> } export default App
Environment Variables
Next.js has built-in support for loading environment variables from .env.local
into process.env
.
// .env.local DB_HOST=localhost DB_USER=myuser DB_PASS=mypassword // you can use variables HOSTNAME=localhost PORT=8080 HOST=http://$HOSTNAME:$PORT
// pages/index.js export async function getStaticProps() { const db = await myDB.connect({ host: process.env.DB_HOST, username: process.env.DB_USER, password: process.env.DB_PASS, }) // You cannot use destructuring, it will fail: // const { NEXT_PUBLIC_PUBLISHABLE_KEY } = process.env. // ... }
By default all environment variables loaded through .env.local
are only available in the Node.js environment, meaning they won’t be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_
// pages/index.js import setupAnalyticsService from '../lib/my-analytics-service' // NEXT_PUBLIC_ANALYTICS_ID can be used here as it's prefixed by NEXT_PUBLIC_ setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID) function HomePage() { return <h1>Hello World</h1> } export default HomePage
Note: .env
, .env.development
, and .env.production
files should be included in your repository as they define defaults. .env*.local
should be added to .gitignore
, as those files are intended to be ignored. .env.local
is where secrets can be stored.
Customizing App Component
Next.js uses the App
component to initialize pages. You can override it creating a ./pages/_app.js
file if, for example. you want to do some of the following things:
- Persisting layout between page changes
- Keeping state when navigating pages
- Custom error handling using
componentDidCatch
- Inject additional data into pages
- Add global CSS
function MyApp({ Component, pageProps }) { return <Component {...pageProps} /> } export default MyApp
The Component
prop is the active page
, so whenever you navigate between routes, Component
will change to the new page
.
Customizing Document
You can create a ./pages/_document.js
to augment your application’s <html>
and <body>
tags.
import Document, { Html, Head, Main, NextScript } from 'next/document' class MyDocument extends Document { render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument
<Html>
, <Head />
, <Main />
and <NextScript />
are required for the page to be properly rendered. The <Head />
component used here is not the same one from next/head
. To set individual <title>
tags, you should use next/head
in your pages or components.
Document
is only rendered in the server, event handlers like onClick
won’t work.
Custom Error pages
Next.js provides a static 404 page by default without having to add any additional files. To create a custom 404 page you can create a pages/404.js file. This file is statically generated at build time.
// pages/404.js export default function Custom404() { return <h1>404 - Page Not Found</h1> }
Same goes for 500 error page
// pages/500.js export default function Custom500() { return <h1>500 - Server-side error occurred</h1> }
500 errors are handled both client-side and server-side by the Error
component. If you wish to override it, define the file pages/_error.js
and add the following code:
function Error({ statusCode }) { return ( <p> {statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'} </p> ) } Error.getInitialProps = ({ res, err }) => { const statusCode = res ? res.statusCode : err ? err.statusCode : 404 return { statusCode } } export default Error
Next CLI
// Start the application in development mode (hot-code reloading, error reporting, etc) next dev next dev mydir -p 8080 // build application in current directory's .next folder next build next build mydir // start in production mode (must run 'next build' before) next start next start mydir -p 8080 // Export the application for production deployment next export
next/router
Introducing the useRouter
hook:
import { useRouter } from 'next/router' function ActiveLink({ children, href }) { const router = useRouter() const style = { marginRight: 10, color: router.asPath === href ? 'red' : 'black', } const handleClick = (e) => { e.preventDefault() router.push(href) } return ( <a href={href} onClick={handleClick} style={style}> {children} </a> ) } export default ActiveLink
The router
object returned by useRouter
:
// Current route. That is the path of the page in /pages pathname: String // The query string parsed to an object. It will be an empty object during prerendering if the page doesn't have data fetching requirements. Defaults to {} query: Object // The path (including the query) shown in the browser without the configured basePath or locale. asPath: String // Whether the current page is in fallback mode. isFallback: boolean // The active basePath (if enabled). basePath: String // The active locale (if enabled) locale: String // All supported locales (if enabled). locales: String[] // The current default locale (if enabled) defaultLocale: String // Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server. isReady: boolean // Whether the application is currently in preview mode. isPreview: boolean
Router methods
Push
import { useEffect } from 'react' import { useRouter } from 'next/router' // Here you would fetch and return the user const useUser = () => ({ user: null, loading: false }) export default function Page() { const { user, loading } = useUser() const router = useRouter() useEffect(() => { if (!(user || loading)) { router.push('/login') } }, [user, loading]) return <p>Redirecting...</p> }
Replace
import { useRouter } from 'next/router' export default function Page() { const router = useRouter() return <button onClick={() => router.replace('/home')}>Click me</button> }
beforePopState
import { useEffect } from 'react' import { useRouter } from 'next/router' export default function Page() { const router = useRouter() useEffect(() => { router.beforePopState(({ url, as, options }) => { // I only want to allow these two routes! if (as !== '/' && as !== '/other') { // Have SSR render bad routes as a 404. window.location.href = as return false } return true }) }, []) return <p>Welcome to the page</p> }
There is also router.back(), router.reload().
You can listen to routing events:
routeChangeStart(url, { shallow }) - Fires when a route starts to change routeChangeComplete(url, { shallow }) - Fires when a route changed completely routeChangeError(err, url, { shallow }) - Fires when there's an error when changing routes, or a route load is cancelled - err.cancelled - Indicates if the navigation was cancelled beforeHistoryChange(url, { shallow }) - Fires just before changing the browser's history hashChangeStart(url, { shallow }) - Fires when the hash will change but not the page hashChangeComplete(url, { shallow }) - Fires when the hash has changed but not the page
Configuring Testing with Jest in NextJS for TypeScript
npm install typescript @types/react @types/node -D npm i jest @testing-library/react @types/jest babel-jest @testing-library/jest-dom @testing-library/user-event @testing-library/dom -D
Add to package.json
:
"test": "jest --watch"
Add to .eslintrc.json
:
{ "env": { "jest": true } }
Add to .babelrc
:
{ "presets": ["next/babel"] }
Create jest.config.js
// jest.config.js module.exports = { setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'], };
Create jest.setup.ts
// jest.setup.ts import '@testing-library/jest-dom';