· tutorials · 8 min read
Next js Best Practices
So you are planning to build 🏗️ your next project using Next.js. That's great. Here are some of the best practices 🏀 to follow while building project with Next.js.
Nextjs is an opinionated React Framework by Vercel built on top 🗻 of the state of the art React library and file based routing for navigation. Next js has successfully introduced a lot of opinionated practices that ensures that the web application feels native and smooth to the end users as well as the search engines 🚒.
So, before you scaffold your Next js project, let’s learn a few best practices every developer can apply in their project.
- Server Side Rendering (SSR), Static Site Generation (SSG) and Client Side Rendering
Next js supports both Server side rendering of React templates as well as Static site generation at the same time. Server Side Rendering means that your React page will be server rendered into html at runtime for every request made by the user. This is very similar to what server side languages like PHP, Django and others did. This also means that now you will have access to the server previliges like fetching data from database, accessing a secret, performing compute intensive task and so on. Because the logic stays within the server only and is not accessible to the client, server side rendering has can be of great advantage. Also the user will not refresh the page because the generated component can be partially hydrated.
Server Side Rendering should be used if the UI data needs to be updated frequently and reflected in realtime. While being great for dynamic contents, server side rendered pages have significant latency and needs to run a serverless function to render pages i.e. you will need to host it in dedicated platform or server for deployment.
Static Site Generation means that your React page can be rendered into static contents at build time. This means you can easily use Next js to build out fully static html file while being able to use dynamic data just like with other JAM stack framework like Gatsby js, Hugo, Jekyll and others.
It differs from server side rendering in that even if the dynamic data changes, the updated content will be reflected only on the next build of the site. Generating your static website as static sites has its advantages and some limitations as well. First of all, static sites are very easy to deploy and distribute over the CDN network like Cloudflare, Netlify, Nginx, Apache or even S3.
Client Side Rendering is the exact opposite of server side rendering in that the whole UI is rendered at client machine. This is what the Single Page react app does. While it is essential for client side interactivity with Javascript, it does not go well with search engines as the crawler bot will read an empty html file instead of the web content we intend to show. This results in very poor Lighthouse score. Also the data loading states must be handled by the client while means a blank screen sometimes.
Despite this, client side rendering is popular because it enables users to have an single app like experience with no page breaks. Also it is better for pages inside authentication layer because these pages shoul not be accessible to search engines any ways.
Luckily Next.js allows us to use the combination of all three within a single project. So, choose the correct tool for your needs accordingly.
- How Next js works (https://nextjs.org/learn/foundations/how-nextjs-works)
A good understanding of what happens inside tha Next.js compile time can help you become a better Next.js developer.
- App router vs Page router
With the introduction of Next.js 13, app router was released for production with experimental mode for server actions. Uptill Next.js version 12, Page router was the default.
With the introduction of React Server Component in React 18, it became a server side library complimenting SSR. So, Next.js 13 also jumped the wagon for its app router adding the first class support to react server components. With this every component is a server component by default and will be rendered in the server, allowing users to directly await for data without useEffects or access server only data. Only the rendered HTML will be sent to the client removing the need to load client Javascript.
App router also comes with new set of Routing Conventions for Routing Files with much easier support for Layout, Loading UI, Error UI, API Endpoint, Fallback Page. Improving on the pages router, it has added convention for Route Groups, Parallel and Intercepted Routes, Metadata files and SEO.
Although app router has pretty less resources and ecosystem compared to pages router, Vercel and community is pushing onto app router to be the future. So, new developers might as well begin with app router altogether.
Find more here
- Lazy Loading and Streaming with React Suspense
By creating a special file loading.js
, you can create meaningful Loading UI with React Suspense. With this convention, you can show an instant loading state from the server while the content of a route segment loads.
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingSkeleton />;
}
In addition to loading.js
, you can also manually create Suspense Boundaries for your own UI components.
Streaming allows you to break down the page’s HTML into smaller chunks and progressively send those chunks from the server to the client.
For Example:
import { PostFeed, Weather, Comment } from './Components';
export default async function Posts() {
const postData = await fetchPostData()
const weatherData = await fetchWeatherData()
const commentData = await fetchCommentData()
const
return (
<PostSections>
<PostFeed data={postData}/>
<Weather data={weatherData}/>
<Comment data={commentData}/>
</PostSections>
);
}
Here, the posts page has two different component which needs to fetch data. Since we are fetching data at Page level, the user will get the page only after all the data has been fetched. This doesn’t ensure proper developer experience. Instead moving the data fetching within the components and wrapping the components will ensure that the user gets the first response immediately instead of default loading UI and other UIs incrementally.
import { Suspense } from 'react';
import { PostFeed, Weather, Comment } from './Components';
export default function Posts() {
return (
<PostSections>
<Suspense fallback={<p>Loading feed...</p>}>
<PostFeed />
</Suspense>
<Suspense fallback={<p>Loading weather...</p>}>
<Weather />
</Suspense>
<Suspense fallback={<p>Loading comments...</p>}>
<Comment />
</Suspense>
</PostSections>
);
}
Now users can see the informative screen immediately on page load.
Learn More here
- Next.js Component Hierarchy
Each file in Next.js app diectory exports a React Component. These components are handlled by Next.js as follows.
- Parallel Routes
Parallel routes can be creating folder with the convention @slot_name
. Slots do not create new routes. The components defined inside the slots can be accessed as props in the same level layouts.
To better understand slots, you can consider page.tsx as @children
slot which you access inside the layout as props.children
.
Parallel Routing allows you to simultaneously or conditionally render one or more pages in the same layout. For highly dynamic sections of an app, such as dashboards and feeds on social sites, Parallel Routing can be used to implement complex routing patterns. Example a dashboarb page where you need to conditionally render components based on user’s role.
- React Query for Data Fetching on the Client instead of useEffect
React Query is a powerful data fetching library by TranStack. It can handle data fetching from REST endpoints as well as GraphQL. It can refresh data automatically, handle caching, background updates, cache invalidation and infinite loading. Its caching mechanism can also be used for handlling of global states.
- Immer.js for working with immutable data
React state and props are immutable. But handlling immutable data can become quite tedious especially if the data is deeply nested. Immer is a tiny library that allows you to work with immutable state in a more convenient way.
Example:
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
Without Immer, we’ll have to carefully shallow copy every level of the state structure that is affected by our change:
const nextState = baseState.slice() // shallow clone the array
nextState[1] = {
...nextState[1],
done: true
}
nextState.push({title: "Tweet about it"})
With Immer, this process is more straightforward.
import {produce} from "immer"
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
})
Well, that’s a lot I still have few other recommendation as follows.
- Shadcn UI
Shadcn UI has definitely got a lot of recognition in recent times, but it is a very good alternative to JavaScript based styling library like MUI.
- Create T3 App for scaffolding Next.js project
Create T3 App provides a cli tool for scaffolding Next.js project with configurable options for TypeScript, tRPC, Prisma, Tailwind CSS, and NextAuth. In addition, it presets linters and recommended practices for Next.js.
- React Hook Form for reactive forms
If you are using functional components, which you should be and come up with the need to manage forms, Try React Hook Form. If you’ve been using onChange handllers or even Formik till now, you’ll find much better Developer experience with React Hook Form and much better performace in application.
- Zod for type validation
If you come up with the use case that requires defining type schema and validations, check out Zod. At least it has much better documentation that alternatives like yup.
At last I thank you very much for staying with me till the end. Please do share it if you found this useful. Peace ✌️.