Handling Script Tags on Next.js Route Changes

Read time: 2 minutes
Mitchell Christ
Mitchell Christ
traffic officer wearing a yellow jacket with the text "<script>" aggressively pointing in different directions and breakdancing in the middle of a busy intersection in Shibuya Tokyo, 4k Hasselblad

When building websites with Next.js and Sanity, you might encounter a common challenge: managing custom HTML that includes <script> tags. By default, these scripts only execute on the initial page load, but what if you need them to run again after route changes? Let's explore how this is effectively handled in SanityPress with the help of the Custom HTML module.

The Challenge

In Next.js applications, client-side navigation doesn't trigger a full page reload. While this provides a smooth user experience, it can cause issues with custom scripts that need to re-execute after route changes. This is particularly important when working with embedded content or third-party widgets.

The Solution

We've implemented a custom component in SanityPress—the Custom HTML module— that handles this scenario elegantly. Here's how it works:

  1. We use a combination of useRef and useState to track the component's render state.
  2. The component maintains a firstRender flag to handle initial mounting.
  3. On subsequent renders, we:
    1. Create a new DOM fragment from the HTML string
    2. Append it to our container element

Here's the implementation:

'use client'

import { useEffect, useRef, useState } from 'react'

export default function CustomHTML({ code }: { code?: string }) {
  const ref = useRef<HTMLElement>(null)
  const [firstRender, setFirstRender] = useState(true)

  if (!code) return null

  // if no <script> tag, render as is
  
  if (!code.includes('<script'))
    return (
      <section dangerouslySetInnerHTML={{ __html: code }} />
    )

  // if includes <script> tag, ensure script is re-run on each render

  useEffect(() => {
    if (firstRender) {
      setFirstRender(false)
    } else {
      const parsed = document
        .createRange()
        .createContextualFragment(code)

      ref.current?.appendChild(parsed)
    }
  }, [ref.current, code])

  return (
    <section ref={ref} />
  )
}

Best Practices

While this solution works well, consider these best practices:

  1. Only use custom scripts when absolutely necessary.
  2. Prefer Next.js's built-in next/script component when possible.
  3. Be cautious with third-party scripts and their impact on performance and security.
  4. Always sanitize HTML content before rendering.

Example Use Case in SanityPress

Imagine you have a content block in your CMS where users can embed custom HTML. You don’t want scripts within this block to execute multiple times on each page transition. By implementing the Custom HTML module, you ensure that scripts run cleanly and predictably, improving performance and avoiding errors.

Conclusion

Managing script execution in Next.js doesn't have to be complicated. With this solution implemented in SanityPress, you can confidently handle custom HTML content that includes scripts, ensuring they work correctly throughout your application's lifecycle.

heavily modded white JDM Nissan R33 Skyline with black heart patterns and underbody lights, with the logo "SanityPress", in the streets of Daikoku parking lot at night, Nikon film