When I built this site in 2021, I had been aware of Next.js but never used it. I was excited to have a project that would allow me to learn the ins and outs of Next. At that time, I used version 12, and since then, I’ve been following the developments of Next on the Vercel YouTube channel, Hacker News, and r/nextjs. I was excited at the release of version 13 and all the enhancements that came with it. I knew I’d eventually find time to upgrade, but I didn’t prioritize it. I had some time off from work towards the end of the year and found a couple of days to go heads down in the updated docs and update this site. In addition, I also moved all my content from the /pages
directory into the /app
directory (which is still in beta).
I learned a few things while upgrading that weren’t clear in the official upgrade guide. I also took the opportunity to do a few cleanup things that I’ve wanted to do for a while. And, yes, it would have been cleaner/safer to keep those items separate from the upgrade, but this is my site and project, and I do what I want.
You can view the entire changeset on GitHub if you don’t want to read words and only look at the code.
I followed the official upgrade guide step by step. As this was my first time digging into the beta docs, I did a lot of bouncing around, reading, and re-reading sections and pages of the docs. For the most part, the upgrade guide is an excellent resource. I got through most sections without issue. There were areas that held me up and I will explain those problems and what I did to solve them. If you’re upgrading and have questions, please drop them in the Comments section below.
Images
The Next team offers two codemods to help you migrate to the new image component. However, I didn’t find either of them useful.
next-image-to-legacy-image
updates the import to use the legacy <Image />
component, which I didn’t want to do.
next-image-experimental
does its best to modify existing <Image />
components to match the new conventions. Since I have a component that wraps next/image
this codemod wouldn’t work for me.
So I found all the places where I imported my <Image />
component and manually updated them. Luckily for me, there were only four places. Updating the props used wasn’t intuitive, but between the beta docs and current docs for Image optimization and the next/image
docs, I got the information I needed to update the components accordingly.
Fonts
I was super happy to see the introduction of the @next/font
module, as it supports self-hosting of any font file, including Google Fonts. My site uses two Google Fonts: Roboto and Roboto Mono.
The upgrade guide helped me get most of the way to working except for 1) Font name conventions and 2) use with Bootstrap.
Font name conventions
The docs use the font Inter as an example. When importing the font from the @next/font/google
package, you destructure the font from the package using its name. So in the case of “Inter”.
However, they do not mention the convention when importing fonts with more than one word in their name, such as “Roboto Mono”. After trying a few things and searching around, I found this Next.js 13 + Google Fonts post on Medium, which confirmed what I had seen by testing: Fonts with two words use an underscore in place of the space.
I was able to get the answer I needed by trial and error and searching, but it would have been nice to see that as an example or mentioned in Next’s docs.
Bootstrap
Yes, I still use Bootstrap. I wrote a bit about my decision to use it over Tailwind on my Built With page. Because of how Bootstrap works, I couldn’t just add the font class name to the <html>
tag. Next does provide an example for using next/font
with Tailwind, which helped point me in the right direction.
To work with Bootstrap, first, include the variable
property to define a CSS variable for the font. The variable name (--font-roboto
) can be whatever you’d like.
Include the font’s variable
property as a class name to the <html>
tag.
Update your bootstrap.scss
file to use the CSS variable when setting the font family.
You can view my full implementation in the app/layout.js
file here.
Migrating from pages to app
I went back and forth about opting into the app directory, as the Next team is very clear that it is still in beta. They even go as far as to recommend not using it in production. I’d heed that warning if this were a client site or something for work. But, again, this is my site, and I do what I want.
Overall, migrating the five or so pages was pretty straightforward, and following the guide proved to be very useful. There were a few hiccups, though.
Routing
All the pages on my site are based on static files available during build time. For example, the /posts/[slug]
URL points to the app/posts/[slug]/page.js
file, and only the static files available in the _posts
directory should count for valid URLs. Everything else should 404.
Here’s the output from a recent build.
This means I don’t need true dynamic URLs where the lookup if a page exists happens server side. In my case, I was getting a runtime error where the URL /favicon.ico
was throwing a server error because it was attempting to be routed through the app/[page]/page.js
file.
There are two solutions to this problem.
For static URLs generated with the generateStaticParams
function, we can export a dynamicParams
const as false
. According to the docs for dynamicParams
, “Dynamic segments not included in generateStaticParams will return a 404” when the value is false
.
The other way is to use the notFound()
function provided by the next/naviagtion
module.
You can find more information on notFound()
in the docs.
Server Components + Client Components
With the concept of Server Components and Client Components, Next.js 13 defaults files within the app
director to server components. You can force any component to be a client component by using the "use client"
directive at the top of the component file.
Most of this site is purely static, and there is little interactivity that would require a ton of JavaScript at run time. Having so few client components allows me to fully take advantage of Server Components and Next’s static rendering. I’m only using the use client
directive in four places across the site.
Three of the four components that define the use client
directive truly need it. BackToTop
, Comments
, and NavToggle
all interact directly with the DOM in some way and cannot be rendered entirely on the server.
The one exception is the component I created that renders MDX (Markdown) content which uses the next-mdx-remote
npm package. The Next team is still working on proper support for MDX in the app
directory, so I’m happy to use this stopgap until they’ve sorted it out.
As a development tool, there is also the server-only
package. You can ensure that a client component doesn’t import the file by importing the package into a file. While in development mode, the package will throw a warning in the console, letting you know of your misdeeds.
Read more on keeping server-only code out of client components in the docs.
head.js
The last pain point I dealt with during this upgrade was the new head.js
paradigm. head.js
is a new “special file” Next introduced that allows different route segments to configure the <head>
tag of the corresponding page.
I use a combination of the next-seo
package and my own <Meta />
component to configure and output all the SEO related meta tags I’d like on any given page. Luckily for me, the next-seo
maintainers are already working on and have documentation for supporting the app
directory. Their work made my life easier in understanding the limitations of head.js
and getting things working the way I expected them to.
In making the upgrade, I had to create the new head.js
file for all my route segments. Doing so meant I had to fetch a page’s data twice, once in head.js
and again in the main content page.js
. The Next team says, “When rendering a route, Next.js will automatically dedupe requests for the same data across layout.js, page.js, and head.js.”. I have yet to see how they’re doing this and if it’s working, but it feels clunky to make the same request twice for a single page.
Lastly, I had to restructure how I set meta tags for each page. From 'next-seo
’s doc, “Next.js no longer de-duplicates tags in the head.” Removing the deduping of tags means instead of updating similar meta tags across different pages; new meta tags will be appended to the existing ones.
Now, the Next team has stated, in a few places, that they are still very much working on how head.js
works and the APIs they provide to support frequent use cases. I look forward to seeing what they come up with.
General Cleanup
As I mentioned earlier, I took the opportunity to complete the following cleanup tasks.
Replace defaultProps
with default function parameters
In React 18.3 defaultProps
will be deprecated, and I was getting console warnings about it, so I took the opportunity to convert the handful of components that used defaultProps
to use default parameters.
The migration was relatively easy and there weren’t any gotchas.
Absolute Imports
Since the /app
directory uses directories (and nested directories) to define routes, I found my import paths were becoming quite ugly.
For example, the posts page
file on my site lives in app/posts/[slug]/page.js
, which makes any import from the root directory look like this.
And for the posts listing page at app/posts/page
the import path is
Keeping track of how many levels deep a given file is and updating the import path is cumbersome and slows me down.
To fix this, I followed the Absolute Imports and Module path aliases guide to add a jsconfig.json
file to the root directory that contains the following.
Using the baseUrl
option allows me to ditch the many levels of ../
in my imports and always work from the root directory. So no matter where a file is in the directory tree, I can import a file like so.
Conclusion
Hopefully, others thinking about, or in the process, of upgrading to Next.js 13 find this writeup helpful. Overall my experience with Next continues to be an enjoyable one. I’m excited to see the progress and momentum it has gained recently.
Comments