What Next.js is
Next.js is a React framework built around two priorities:
- Production readiness: it gives you the pieces you need for real-world apps without a lot of configuration
- A better developer experience (DX)
With Next.js, a React app can get features like SSR, SSG, TypeScript support, automatic bundling, and file-based routing out of the box.
What Next.js gives you
Let’s start with the features Next.js prepares for production React apps.
Page routing
After initializing a React app with Next.js, every component file inside the pages directory is automatically turned into a page, and routes are created for you. For example, if you add about.js under pages:
// pages/about.js
export default function About() {
return <div>About: </div>
}
pages
.
├── _app.js
├── about.js
├── api
│ └── hello.js
└── index.js
Once the app is running, you can visit http://localhost:3000/about to reach the about page. Next.js also supports dynamic routes. If you create pages/posts/[id].js, requests like /post/1 and /post/2 will be routed to that component automatically.
// pages/posts/[id].js
import { useRouter } from 'next/router'
export default function Post() {
const router = useRouter()
const { id } = router.query
return (
<div>
Post
<div>id: {id}</div>
</div>
)
}
At this point, visiting http://localhost:3000/posts/1 or http://localhost:3000/posts/2 will hit the same component, and the route parameter can be read from router.
Pre-rendering
Besides client-side rendering, Next.js supports two kinds of pre-rendering:
- static rendering at build time
- server-side rendering, or SSR
Both can be used in the same Next.js app. That means some pages can be static while others use SSR.
Static rendering (SSG)
Static rendering means the page is rendered into HTML files during the build step. Those files can then be served from a CDN. In Next.js, a statically rendered page may or may not use data. A page with no data, like the earlier about.js, already has its HTML generated after build:
<body>
<div id="__next"><div>About:</div></div>
<script id="__NEXT_DATA__" type="application/json">
{
"props": { "pageProps": {} },
"page": "/about",
"query": {},
"buildId": "Dc_JKg47EiBrN8BG0Me-S",
"nextExport": true,
"autoExport": true,
"isFallback": false,
"scriptLoader": []
}
</script>
</body>
Static rendering can also work with data. If you create ssg.js under pages, Next.js lets the component fetch external data during pre-rendering through getStaticProps. In this example, the current time is requested asynchronously and passed into the component as props:
export async function getStaticProps() {
const now = new Date().toLocaleTimeString()
const res = await fetch(`https://postman-echo.com/get?now=${now}`)
const obj = await res.json()
return {
props: {
now: obj.args.now,
},
}
}
export default function SSG({ now }) {
return <div>SSG: {now}</div>
}
After the build, you can see that the generated ssg.html already contains the time:
<body>
<div id="__next">
<div>
SSR:
<!-- -->6:21:06 PM
</div>
</div>
<script id="__NEXT_DATA__" type="application/json">
{
"props": { "pageProps": { "now": "6:21:06 PM" }, "__N_SSG": true },
"page": "/ssg",
"query": {},
"buildId": "PQ7ze9KG5oMk09L5idTqy",
"isFallback": false,
"gsp": true,
"scriptLoader": []
}
</script>
</body>
With dynamic routes, things get a little more involved, because Next.js needs to know the route parameters before rendering. For the posts/[id].js component above, if you want to fetch post detail data by postId, Next.js provides getStaticProps and getStaticPaths.
getStaticPaths: by returning apathsarray, Next.js can determine the possible values of the dynamic route at build time. In this case, each postidbecomes the value of the route parameterid, so Next.js can figure out every possible page forposts/[id].jsduring compilation.
export async function getStaticPaths() {
const res = await fetch('https://my-json-server.typicode.com/typicode/demo/posts')
const posts = await res.json()
const paths = posts.map((post) => ({ params: { id: `${post.id}` } }))
return {
paths,
fallback: false,
}
}
When rendering a specific post page, getStaticProps is still needed to fetch the data and pass it to the component as props.
export async function getStaticProps({ params }) {
const res = await fetch(`https://my-json-server.typicode.com/typicode/demo/posts/${params.id}`)
const post = await res.json()
return {
props: { post },
}
}
And here is the component itself:
export default function Post({ post }) {
return (
<div>
Post
<div>id: {post.id}</div>
<div>title: {post.title}</div>
</div>
)
}
Server-side rendering (SSR)
A page can also choose SSR for pre-rendering by implementing getServerSideProps in the page component:
// pages/status.js
export async function getServerSideProps() {
const now = new Date().toLocaleTimeString()
const res = await fetch(`https://postman-echo.com/get?now=${now}`)
const obj = await res.json()
return {
props: {
now: obj.args.now,
},
}
}
export default function Status({ now }) {
return <div>Status: {now}</div>
}
Compared with the SSG example above, the difference is easy to notice: the SSG page keeps showing the same time across refreshes, while the SSR page shows a new time every time you reload.
Hybrid rendering
Next.js also supports mixing SSG and SSR in the same app, so some pages can be static while others are rendered on the server.
API routes
Just like pages, Next.js also automatically supports server-side APIs. In a Next.js project, every file under pages/api is registered as an API endpoint automatically.
// pages/api/user.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
After the app starts, you can request this endpoint through /api/user.
In short
Next.js can be thought of as a more complete version of create-react-app. It not only helps you create a React app quickly, but also adds a lot of production-friendly features with little configuration, including routing and multiple pre-rendering strategies.