If React is a UI/state library, then Next.js shows us how to use the library the right way to build fast web applications and streamline the development process. This post introduces Next.js, highlighting some of its key features with examples that demonstrate how easy it is to use Next.js with React.
Simplified Routing
One of the fundamental aspects of web apps is routing. In many React apps I worked on before Next.js, developers had to freestyle their project's folder structure and naming conventions, which led to inconsistencies and readability issues. Every project had a slightly different file structure, making it hard to follow.
Next.js automatically handles routing for you. Simply create files under the /pages folder and export them as default.
pages/
|---> index.js
|---> about.js
|---> dashboard/
|---> index.js
|---> users.js
Then your site will be accessible at
/
/about
/dashboard
/dashboard/users
Dynamic Routes
Next.js supports dynamic page routing. For example, if you create a file structure like this:
pages/
|---> users/
|---> [userId].js
Your site will be accessible at pages/users/1, pages/users/2, etc. The matched parameters will be sent as query parameters (userId in this case) to the page.
import {useRouter} from 'next/router'
export default () => {
const { userId } = useRouter().query
return ...
}
Per-Page Layouts
Sometimes, multiple pages share a common layout. It's a good practice to separate the layout components to avoid repetition. For example, let's say we have two pages that share the same layout:
/page-a
Side Bar
Page A
/page-b
Side Bar
Page B
Create DashboardLayout
import SideBar from '../components/SideBar'
export default DashboardLayout({ content }){
return <div style={{display:"grid", gridTemplateColumns:"20% 1fr"}}>
<SideBar/>
{content}
</div>
}
Adding property getLayout
to your pages.
import DashboardLayout from '../layouts/dashboard-layout'
export default function PageA() {
return <div>
PageA
</div>
}
PageA.getLayout = (page) => {
return (
<DashboardLayout>
{page}
</DashboardLayout>
)
}
import DashboardLayout from '../layouts/dashboard-layout'
export default function PageB() {
return <div>
PageB
</div>
}
PageB.getLayout = (page) => {
return (
<DashboardLayout>
{page}
</DashboardLayout>
)
}
When using Next.js you’ll most likely need to override the global App component to get access to some features like persisting state, global css or layouts. This can be done by creating a file _app.js
directly under the pages/
folder. In your pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return <>
{getLayout(<Component {...pageProps} />)}
</>
}
This layout pattern enables state persistence because the React component tree is maintained between page transitions. For more read on official docs
getServerSideProps
If you export an async function called getServerSideProps from a page, Nextjs will pre-render this page on each request using the data returned by getServerSideProps.
export async function getServerSideProps(context) {
const { params, locale} = context;
const res = await fetchUserDataById(params.id) // fetch data from database
return {
props: {
userData: res?.data
locale
}, // will be passed to the page component as props
}
}
export default function User(props){
const { userData, locale } = props; // <-- props received from getServerSideProps
return <>
...
</>
}
The context
parameter is an object containing the following keys:
params
: If this page uses a dynamic route, params contains the route parameters. If the page name is[id].js
, then params will look like { id: ... }.req
: The HTTP IncomingMessage object, with an additional cookies prop, which is an object with string keys mapping to string values of cookies.res
: The HTTP response object.query
: An object representing the query string.preview
: preview is true if the page is in the Preview Mode and false otherwise.previewData
: The preview data set by setPreviewData.resolvedUrl
: A normalized version of the request URL that strips the _next/data prefix for client transitions and includes original query values.locale
contains the active locale (if enabled).locales
contains all supported locales (if enabled).defaultLocale
contains the configured default locale (if enabled).
getStaticProps
Nextjs will pre-render this page at build time using the data returned by getServerSideProps.
export async function getStaticProps() {
const { params, locale} = context;
// Call an external API endpoint to get data
const userData = await await fetch(`https://.../user/${params.id}`).then(res => res.json());
return {
props: {
userData,
locale
}, // will be passed to the page component as props
}
}
export default function User(props){
const { userData, locale } = props; // <-- props received from getServerSideProps
return <>
...
</>
}
The context parameter is an object containing the following keys:
params
contains the route parameters for pages using dynamic routes. For example, if the page name is[id].js
, then params will look like { id: ... }. You should use this together with getStaticPaths, which we’ll explain later.preview
is true if the page is in the Preview Mode and undefined otherwise.previewData
contains the preview data set bysetPreviewData
.locale
contains the active locale (if enabled).locales
contains all supported locales (if enabled).defaultLocale
contains the configured default locale (if enabled).
Wait, it is doing the same as getServerSideProps
? Yes, both functions passed some props to the page, but there are few differences to consider.
getServerSideProps
- This function get called every time you visit the site, the server will have to build the page every time each request.
- Can be slow
- Great for dynamic data that changes regularly
getStaticProps
- This function get called only once at build time. Props remain the same until the next build.
- Great for speed and SEO
- Great for static data
getStaticPaths
Assume you are building a blog site. You want to pre-render your articles as static pages for fast serving and better SEO.
export async function getStaticPaths() {
// Read the files inside the pages/posts dir
const articles = await fetch('https://.../articles').then(res => res.json());
// articles = [
// {
// id:"1",
// title:"hello-world"
// },
// {
// id:"2",
// title:"data-fetching-in-react"
// }
// ]
// Generate path for each file
const paths = articles.map((article) => {
return {
params: {
slug: article.id
},
};
});
return {
paths,
fallback: false,
};
}
The value for each params object must match the parameters used in the page name.
If the page name is articles/[slug].js
, then params should contain slug
.
This function run once at build time and can be used altogether with getStaticProps
but can not be used with getServerSideProps
. For more read on official docs
export default function Post({ post }) {
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const posts = await fetch('https://.../posts').then(res => 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 }
}
// 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 post = await fetch(`https://.../posts/${params.id}`).then(res => res.json())
// Pass post data to the page via props
return { props: { post } }
}
Conclusion
Next.js is an exceptional framework for React. If you're a React developer, you should definitely explore Next.js. With its powerful features and tools, Next.js can save you time and simplify your development process. Give it a try!