Logan Chaffee
January 07, 2023
I am very happy that I chose Next.js to build my portfolio site. Next.js is a full-stack JavaScript framework for building multi-page React Apps and it has a lot of features that I wanted to take advantage of. Among these are:
Traditionally React is used to make single-page applications where all of the rendering and routing takes place on the client. This is the case for apps built with tools like Create React App and Vite. These are the kinds of apps that I usually built when creating a new React project. My app Brilliant Bookworm, for example, used Create React App. The server sends the bundled JavaScript to the client, and all of the HTML is rendered in the browser. I believe that there are many great use cases for single-page apps, but as an application grows and more routes are added, it becomes less optimal. This is when pre-rendering becomes incredibly useful.
With Next.js, all of the HTML for each page is pre-rendered on the server and then sent to the client. This means that the browser receives the full HTML that it needs to display, so there is no need to show a loading spinner or have a flicker between the time that the browser receives the response and the time that the JavaScript runs. That's a much better experience for the user.
Next.js lets you do this pre-rendering in a couple of ways. If you want to generate the HTML right after a user requests a page, you can use server-side rendering. Server-side rendering is great for fetching data specific to the request of the user. So if the user needed to see a page with data specific to them, this would be a good option. Because there is currently no content on my site that is specific to visitors of the site, this wasn't necessary for me.
I chose to go with Next's other option: static generation. With static generation, pages can be rendered to HTML at build time and then cached for better performance. Fetching these pages is much faster than server-side rendering so it should be used whenever possible. Let's take a look at how my home page fetches data.
export const getStaticProps = async () => {
const query = '*[_type == "homePage"][0]';
const homePageData = await sanityClient.fetch(query);
const query2 = '*[_type == "project"]';
const projectsData = await sanityClient.fetch(query2);
const query3 = '*[_type == "siteSettings"][0]';
const siteSettings = await sanityClient.fetch(query3);
return {
props: { homePageData, projectsData, siteSettings },
revalidate: 10,
};
};
To fetch data that will then be used on my home page I create an exported function named getStaticProps
. This function will run at build time and pass my fetched data as props
to my home page React component. I make three queries to the Sanity CMS API for the content on my homepage. (I will be writing another article about Sanity CMS soon)
I use this method to get data for all my site's pages. But what if I need to change the data in my CMS and update the content? That is where incremental static regeneration comes into play.
With Next's incremental static generation, I can update the content of statically generated pages after they've already been built, and Next will rebuild them and cache the new page. I set this up on all of my pages. If I add a new project to be shown on my homepage, or if I publish a new blog article, those pages can be regenerated with the new content.
To use static regeneration, all I have to do is add the revalidate
prop to getStaticProps
. This prop defines the number of seconds to wait after a user requests a page before re-validating the page. So in my case, if a user visits a page right after I update content, the new page will be available to all visitors 10 seconds later. With Next.js v12.2.0
it is possible to set up on-demand re-validation with API calls and webhooks. I may set that up in the future, but for now, re-validation triggered by a page request is sufficient.
Perhaps one of the best developer experiences of Next.js comes from file-based routing. To create a new page on my website, all I have to do is create a file in the /pages
directory. It's that simple. This is something that is not so easy with single-page apps, Usually, I would use something like React Router to handle route changes in my app, but this is much cleaner and simpler.
Even API routes are handled this way. To add an API endpoint, I just create a file in /pages/api
. This is where I set up an API endpoint for my contact form. Normally I would use Node and Express.js to set up API endpoints for something like this, but there was practically no learning curve here because many of the methods by Next.js are very Express.js-like. Here is what the endpoint for my contact route looks like. If you have built API endpoints with Node.js and Express, this should look very familiar:
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-west-2' });
export default async function (req, res) {
try {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'method not allowed' });
}
const { name, email, message } = req.body;
const params = {...};
const ses = new AWS.SES({...});
const data = await ses.sendEmail(params).promise();
res.status(200).json({ data: 'success' });
} catch (error) {
console.log(error);
res.status(500).json({ error: 'there was an error on the server'});
}
}
At the time of writing this, Next.js 13 is out, and there is a new way to handle routing with more features like layouts, and nested routes. But these features are currently in beta. I will be using these in the future when they hit a stable release, and I will likely update this article or write a new one about the process.
Overall I am pleased to be using Next.js and I will likely be using it for many other projects in the future. I think that the React community will be moving heavily towards multi-page apps again as well as server-side rendering, now that Server Components will be the default soon.
If you are used to building single-page React applications and have not tried Next.js I highly recommend it.