Upgrading Acquia CMS to Next.js 13

The Next.js 13 was released a few weeks ago. We updated the Next.js for Acquia CMS starter kit to the latest version and wanted to take the opportunity to share how the experience was.

If you prefer to just view the code changes needed, you can see that each section of this blog post roughly maps into its own commit on this pull request.

Updating versions

I started by manually updating all of the version constraints.

--- a/package.json
+++ b/package.json
@@ -15,11 +15,11 @@
     "classnames": "^2.3.2",
     "drupal-jsonapi-params": "^2.1.0",
     "html-react-parser": "^3.0.4",
-    "next": "^12.3.2",
+    "next": "^13.0.0",
     "next-acms": "^1.0.0",
     "next-drupal": "^1.5.0",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
     "sharp": "^0.31.2"
   },
   "devDependencies": {

After updating the version constraints, I was able to simply install dependencies again.

yarn install

I was getting some TypeScript type errors and realized that I needed to update the @types/react package to get the correct TypeScript types.

--- a/starters/basic-starter/package.json
+++ b/starters/basic-starter/package.json
@@ -25,7 +25,7 @@
   "devDependencies": {
     "@babel/core": "^7.20.2",
     "@types/node": "^18.11.9",
-    "@types/react": "^17.0.4",
+    "@types/react": "^18.0.25",
     "autoprefixer": "^10.4.13",
     "eslint": "^8.27.0",
     "eslint-config-next": "^12.3.2",

After installing dependencies again, the TypeScript error was fixed.

yarn install

Breaking changes

Links

After this, I ran into some issues with the Link component. The required changes are documented in the release notes. This was pretty straightforward to address - all that was needed was to remove the inner <a> from the Link component children.

--- a/components/formatted-text.tsx
+++ b/components/formatted-text.tsx
@@ -44,8 +44,8 @@ const options: HTMLReactParserOptions = {

         if (href && isRelative(href)) {
           return (
-            <Link href={href} passHref>
-              <a className={className}>{domToReact(domNode.children)}</a>
+            <Link href={href} className={className}>
+              {domToReact(domNode.children)}
             </Link>
           );
         }

Here's the full commit.

Images

After resolving the exceptions caused by the Link component, I noticed that I had some errors in the console because we were still using deprecated APIs from the Image component. This was also documented in the release notes.

The biggest change needed to address this was to update the MediaImage component to work with the new Image component. Next.js 13 ships with a simplified Image component that requires less client side code that should improve the client side performance. The new Image component also provides better developer experience by providing more HTML like interface, and by providing more validation to help address misconfigured uses of the Image component. 

The minimum requirement to get the MediaImage to work was to remove the objectFit and layout properties since those properties had been deprecated on the upstream component. 

--- a/components/media--image.tsx
+++ b/components/media--image.tsx
@@ -12,13 +12,16 @@ MediaImage.type = 'media--image';

 export function MediaImage({
   media,
-  layout = 'responsive',
-  objectFit,
+  imageStyle,
+  fill = false,
   width,
   height,
   priority,
+  quality,
   sizes,
-  imageStyle,
+  placeholder,
+  blurDataURL,
+  loading,
   ...props
 }: MediaImageProps) {
   const image = media?.image;
@@ -29,13 +32,12 @@ export function MediaImage({
   let sizeProps;
   let srcURL;

-  sizeProps =
-    layout === 'fill'
-      ? null
-      : {
-          width: width || image.resourceIdObjMeta.width,
-          height: height || image.resourceIdObjMeta.height,
-        };
+  sizeProps = fill
+    ? null
+    : {
+        width: width || image.resourceIdObjMeta.width,
+        height: height || image.resourceIdObjMeta.height,
+      };
   srcURL = absoluteURL(image.uri.url);

   // Use the image style to render an image if specified.
@@ -64,13 +66,16 @@ export function MediaImage({
     <div className="media__content image__wrapper" {...props}>
       <Image
         src={srcURL}
-        layout={layout}
-        objectFit={objectFit}
         alt={image.resourceIdObjMeta.alt || 'Image'}
         title={image.resourceIdObjMeta.title}
         priority={priority}
         sizes={sizes}
-        {...sizeProps}
+        fill={fill}
+        quality={quality}
+        blurDataURL={blurDataURL}
+        loading={loading}
+        placeholder={placeholder}
+        {...(!fill ? sizeProps : {})}
       />
     </div>
   );

Here's the full commit.

Title element

I also noticed a React warning in the console that indicated that there were multiple nodes inside the <title> element. This was caused by the fact that the title tag contained an expression within the children.

To address this issue, I converted the children of the <title> element into an expression that outputs a single text node. This is different from having an expression within text. In order for React to hydrate the expression, it adds additional comment nodes to the server rendered output to separate the expression from the text. Since title natively does not assume it could contain anything other than text nodes, this leads into the comment nodes being rendered as text in the browser title. This could have also led to a forced re-rendering on the client side. This was mentioned in the React 18.10 release notes.

--- a/components/layout.tsx
+++ b/components/layout.tsx
@@ -20,7 +20,7 @@ export function Layout({ title, menus, children }: LayoutProps) {
   return (
     <>
       <Head>
-        <title>{title} - Acquia CMS</title>
+        <title>{`${title} - Acquia CMS`}</title>
       </Head>
       <PreviewAlert />
       <div className="flex flex-col min-h-screen">

Thoughts

The official documentation and release notes made updating to Next.js 13 easy. It was apparent that the Next.js team has paid attention to the experience developers would have upgrading to the newly released major version.

Next.js 13 ships with some exciting new features, such as the new layouts system. Instead of replacing the old system with the new one, Next.js 13 is shipping with the old system on the side. This allows developers to gradually update to the new major version. We are looking forward to updating our starter kit to use the full power of the new features in future.