- Initialize a NPM project. The following command will create a
package.json
file with sensible defaults.
npm init -y
- Install necessary dependencies.
npm install react react-dom next
- Add
node_modules
and.next
to.gitignore
file.
node_modules
.next
- Add NPM scripts
// package.json
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
- Create a new directory
pages
and create a new fileindex.js
inside the directory.
// pages/index.js
const IndexPage = () => <p>Index Page</p>;
export default IndexPage;
- Run the app in
development
mode.
npm run dev
- Build the app for
production
.
npm run build
- Start the app in
production
mode.
npm start
Next.js uses file system
based routing. Which means each page (route) corresponds to a .js
or .jsx
file within pages directory.
- For
/about
, create a new fileabout.js
inpages
directory.
// pages/about.js
const AboutPage = () => <p>About Page</p>;
export default AboutPage;
- For pages like
/courses/
and/courses/full-stack-development
, create a foldercourses
insidepages
directory and create two filesindex.js
andfull-stack-development
.
// pages/courses/index.js
const CoursesMainPage = () => (
<>
<h1>Courses</h1>
</>
);
export default CoursesMainPage;
// pages/courses/full-stack-development.js
const FSDCoursePage = () => (
<>
<h1>Full Stack Development Course</h1>
</>
);
export default FSDCoursePage;
- Next.js provides a
Link
component to allow client-side navigation between pages.
// pages/index.js
import Link from 'next/link';
const IndexPage = () => (
<>
<h1>Index Page</h1>
<Link href="/about">Goto About</Link>
</>
);
export default IndexPage;
- We can also navigate imperatively in Next.js using
useRouter
hook andwithRouter
HOC.
// pages/about.js
import { useRouter } from 'next/router'
const AboutPage = () => {
const router = useRouter();
return (
<>
<h1>About Page</h1>
<button onClick={() => router.push('/')}>Goto Home</button>
</>
)};
export default AboutPage;
// pages/index.js
import Link from 'next/link';
import { withRouter } from 'next/router';
const IndexPage = (props) => (
<>
<h1>Index Page</h1>
<Link href="/about">Goto About</Link>
<button onClick={() => props.router.push('/courses')}>Goto Courses</button>
</>
);
export default withRouter(IndexPage);
-
For pages like
/courses/:id
with:id
being a dynamic value, we can create a file like[id].js
whereid
will be available as aquery param
for the page. -
The query params from
props.router.query
are only available on the client-side. On server-side,props.router.query
will be an empty object.
// pages/courses/[id].js
import { withRouter } from "next/router";
const coursesMap = {
'full-stack-web-development': 'Full Stack Web Development',
'full-stack-android-development': 'Full Stack Android Development',
}
const FSDCoursePage = (props) => {
console.log(props.router.query);
if (typeof window === 'undefined') {
return <p>Loading...</p>
}
return (
<>
<h1>{coursesMap[props.router.query.id] || 'Unknown'} Course</h1>
</>
)
};
export default withRouter(FSDCoursePage);
-
Next.js supports two types of pre-rendering,
- Static Generation
- Server-side Rendering
-
There are 4 different functions for pre-rendering,
getStaticProps
(Static Generation)getStaticPaths
(Static Generation)getServerSideProps
(Server-side Rendering)getInitialProps
(Server-side Rendering)
-
We have to export an
async
function from a pages to use these data fetching methods.
- This should return an object with,
props
(required - serializable object)notFound
(optional - boolean)
// pages/courses/index.js
const CoursesMainPage = (props) => {
console.log(props);
return (
<>
<h1>Courses</h1>
<ol>
{props.courses.map(course => <li key={course}>{course}</li>)}
</ol>
</>
)
};
export const getStaticProps = async () => {
return {
props: {
courses: ['Full Stack Web Development', 'Full Stack Android Development'],
},
}
}
export default CoursesMainPage;
-
If a page with dynamic routes has
getStaticProps
then the page will needgetStaticPaths
to define the paths for pre-rendering. -
This should return an object with,
- paths (required - Array of Objects)
- fallback (required - boolean)
-
When fallback is set to
false
then any path that is defined ingetStaticPaths
will result in404 page
.
// pages/courses/[id].js
import { withRouter } from "next/router";
const coursesMap = {
"full-stack-web-development": "Full Stack Web Development",
"full-stack-android-development": "Full Stack Android Development",
};
const FSDCoursePage = (props) => {
console.log(props.router.query);
return (
<>
<h1>{coursesMap[props.router.query.id] || "Unknown"} Course</h1>
</>
);
};
export const getStaticProps = async (context) => {
return {
props: {
id: context.params.id,
},
};
};
export const getStaticPaths = async () => {
return {
paths: [
{
params: { id: "full-stack-web-development" },
},
{
params: { id: "full-stack-android-development" },
},
],
fallback: false,
};
};
export default withRouter(FSDCoursePage);
-
If a page exports
getServerSideProps
then the page will be pre-rendered on every request. -
This should return an object with,
- props (required - serializable object)
- notFound (optional - boolean)
// pages/ssr/getServerSideProps-example.js
const Page = (props) => (
<>
{props.data.map(ele => <p key={ele}>{ele}</p>)}
</>
);
export const getServerSideProps = async () => {
return {
props: {
data: ['a', 'b', 'c', 'd']
}
}
}
export default Page;
-
Pages with
getInitialProps
will also be pre-rendered on every request. -
This should return a serializable object.
// pages/ssr/getInitialProps-example.js
const Page = (props) => (
<>
{props.data.map(ele => <p key={ele}>{ele}</p>)}
</>
);
Page.getInitialProps = async () => {
return {
data: ['a', 'b', 'c', 'd']
}
}
export default Page;
- Custom App & Custom Document does not support data fetching methods like
getServerSideProps
,getStaticProps
andgetStaticPaths
.
-
Next.js uses
App
component to initialize all pages. You can override it by creating a new file_app.js
inpages
directory and can do the following,- Common layout for all pages
- Global CSS
- Meta tags in
head
. - Use Redux Provider
- Custom error handling with
componentDidCatch
-
Adding
getInitialProps
to_app.js
will disableAutomatic Static Optimiation
.
// pages/_app.js
const CustomApp = ({ Component, pageProps }) => (
<Component {...pageProps} />
);
export default CustomApp;
-
Next.js uses
Document
component to augment the application's<html>
and<body>
tags. We can override it by creating a new file_document.js
in pages directory. -
Document
is rendered only on server-side, soreact lifecycle methods
andevent handlers
will not work.
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class CustomDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps }
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default CustomDocument
- By default, Next.js comes with support for
css modules
andstyled-jsx
.
- Create a file with filename in
<something>.module.css
.ClassNames
in the file should be camelCased.
/* src/styles/index.module.css */
.heading {
text-align: center;
}
.linksContainer {
display: flex;
flex-direction: column;
align-items: center;
}
- Import the
css
file and which will expose the styles as an object withclassNames
as keys.
// pages/index.js
import Link from 'next/link';
import { withRouter } from 'next/router';
import styles from '../src/styles/index.module.css';
const IndexPage = (props) => (
<>
<h1 className={styles.heading}>Index Page</h1>
<div className={styles.linksContainer}>
<Link href="/about">Goto About</Link>
<button onClick={() => props.router.push('/courses')}>Goto Courses</button>
</div>
</>
);
export default withRouter(IndexPage);
-
Styles can be written within the react components and no restriction on camelCasing classNames as seen in css modules.
-
A simple component with scoped CSS is shown below,
// src/components/Button.js
const Button = (props) => (
<>
<style jsx>
{`
.button {
padding: 6px 8px;
background: black;
color: white;
border-radius: 4px;
border: none;
}
.button:hover {
transform: translateY(-1px);
cursor: pointer;
}
`}
</style>
<button
onClick={props.onClick}
className="button"
>
{props.children}
</button>
</>
);
export default Button;
- To add/override styles of children components, we can using global selector (
:global
).
// pages/about.js
import { useRouter } from 'next/router'
import Button from '../src/components/Button';
const AboutPage = () => {
const router = useRouter();
return (
<>
<style jsx>
{`
.about-page-container :global(.button) {
background: red;
color: white;
}
`}
</style>
<div className="about-page-container">
<h1>About Page</h1>
<Button onClick={() => router.push('/')}>Goto Home</Button>
</div>
</>
)};
export default AboutPage;
- To add global styles to your application which reflects across all the pages, we can use
<style jsx global>
in_app.js
.
// pages/_app.js
const CustomApp = ({ Component, pageProps }) => (
<>
<style jsx global>
{`
a {
color: maroon;
}
`}
</style>
<Component {...pageProps} />
</>
);
export default CustomApp;
-
Next.js supports static serving from
public
directory. This can be used to serve static content like images, fonts androbots.txt
. -
We can use the static assets by setting the path without the
public
prefix. For example, to use an image at/public/some-image.jpg
,
<img src="/some-image.jpg" />
- Next.js exposes a custom component for appending elements to
head
element. We can use this to add dynamic document titles and SEO related meta tags.
// pages/_app.js
import Head from 'next/head';
const CustomApp = ({ Component, pageProps }) => (
<>
<style jsx global>
{`
a {
color: maroon;
}
`}
</style>
<Head>
<title>Learn Next.js</title>
</Head>
<Component {...pageProps} />
</>
);
export default CustomApp;
- Next.js also exposes a custom image component for automatic image optimization. This allow for resizing, optimizing and serving images in modern formats (webp when the browser supports it). Images are optimized on request, so this will not effect your build time.
// pages/index.js
import Link from 'next/link';
import { withRouter } from 'next/router';
import Image from 'next/image';
import styles from '../src/styles/index.module.css';
const IndexPage = (props) => (
<>
<style jsx>
{`
.icecream-container {
width: 500px;
margin: 32px auto;
}
`}
</style>
<h1 className={styles.heading}>Index Page</h1>
<div className={styles.linksContainer}>
<Link href="/about">Goto About</Link>
<button onClick={() => props.router.push('/courses')}>Goto Courses</button>
</div>
<div className="icecream-container">
<Image src="/icecream-by-pexels.jpeg" width={500} height={750} />
</div>
</>
);
export default withRouter(IndexPage);
- Any file inside
/api
folder will be considered as an API route instead of a page.
// pages/api/health.js
const handler = (req, res) => {
res.status(200).json({ status: 'OK' })
};
export default handler;
-
This also follows the same file system based routing. So you can access this API route at
/api/health
. -
req
andres
are standard nodeIncomingMessage
andServerResponse
instance with some pre-built middlewares. So you can use most of the express/connect middlewares with API routes.
NOTE: We should not use these API routes in `getStaticProps`, instead you can directly import the logic used in the API route into `getStaticProps`. `getStaticProps`, `getStaticPaths` and `getServerSideProps` are not included in the client-side bundle.
Checkout https://next-code-elimination.vercel.app/