1

We’re encountering an issue with a component(which is added in sitecore cms and has html,css,scripts) in our Sitecore Headless (Next.js) implementation.

In Sitecore XP, we were able to use Rich Text fields to inject HTML, CSS, inline scripts, and external scripts — all of which executed seamlessly on the client side.

However, in the current headless setup, even when using <RichText /> to render content from Sitecore, the scripts (especially inline and external ones) are not executing at runtime.

We’ve attempted three different implementation strategies to fix this, but still face two major problems:

1.Scripts don’t consistently load — particularly on second visits

2.Initial script load takes 1–1.5 seconds, causing a noticeable performance hit

Has anyone faced a similar issue or found a reliable solution for dynamically injecting html,css,script content(inline and external scripts) and executing from Sitecore in a Next.js environment?

We have tried in 3 ways

#Code version-1

import { useEffect, useRef } from 'react';
import { ComponentProps } from 'lib/component-props';
import { Field, RichText } from '@sitecore-jss/sitecore-jss-nextjs';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
import { formatString } from 'scripts/format-string';
interface CodeObjectProps extends ComponentProps {
  fields: {
    HtmlContent: Field<string>;
    InPageNavigationTitle: Field<string>;
  };
}
const CodeObject = ({ fields }: CodeObjectProps): JSX.Element | null => {
  const { sitecoreContext } = useSitecoreContext();
  const isExperienceEditor = sitecoreContext?.pageEditing ?? false;
  const navigationTitle = fields?.InPageNavigationTitle?.value;
  const scriptsLoaded = useRef(false);
  useEffect(() => {
    if (!isExperienceEditor && !scriptsLoaded.current) {
      const htmlContent = fields?.HtmlContent?.value || '';
      const tempDiv = document.createElement('div');
      tempDiv.innerHTML = htmlContent;
      const scriptTags = tempDiv.getElementsByTagName('script');
      for (let i = 0; i < scriptTags.length; i++) {
        const scriptTag = scriptTags[i];
        const newScript = document.createElement('script');
        if (scriptTag.src) {
          newScript.src = scriptTag.src;
          newScript.defer = true;
          document.head.appendChild(newScript);
        } else {
          newScript.textContent = scriptTag.innerHTML;
          document.body.appendChild(newScript);
        }
      }
      scriptsLoaded.current = true;
    }
  }, [fields?.HtmlContent, isExperienceEditor]);
  return (
    <RichText
      field={fields?.HtmlContent}
      tag="section"
      className="code-object-sec"
      {...(navigationTitle ? { id: formatString(navigationTitle) } : {})}
      {...(navigationTitle ? { inPageNav_Name: navigationTitle } : {})}
    />
  );
};
export default CodeObject;

#code version 2:

import { ComponentProps } from 'lib/component-props';
import { Field } from '@sitecore-jss/sitecore-jss-nextjs';
import Script from 'next/script';
import { parse } from 'node-html-parser';
import { formatString } from 'scripts/format-string';
interface CodeObjectProps extends ComponentProps {
  fields: {
    HtmlContent: Field<string>;
    InPageNavigationTitle: Field<string>;
  };
}
const CodeObject = ({ fields }: CodeObjectProps): JSX.Element | null => {
  const htmlContent = fields?.HtmlContent?.value;
  const navigationTitle = fields?.InPageNavigationTitle?.value;
  if (!htmlContent) return null;
  const parsedHtml = parse(htmlContent);
  const scriptTags = parsedHtml.querySelectorAll('script');
  // Strip out script tags from HTML
  scriptTags.forEach((tag) => tag.remove());
  const nonScriptHtml = parsedHtml.toString();
  return (
    <>
      {/* Render non-script HTML */}
      <section
        className="code-object-sec"
        {...(navigationTitle ? { id: formatString(navigationTitle) } : {})}
        {...(navigationTitle ? { inPageNav_Name: navigationTitle } : {})}
        dangerouslySetInnerHTML={{ __html: nonScriptHtml }}
      />
      {/* Inject scripts (external & inline) */}
      {scriptTags.map((scriptTag, index) => {
        const src = scriptTag.getAttribute('src');
        const innerHtml = scriptTag.innerHTML?.trim();
        if (src) {
          return (
            <Script
              key={`external-${index}`}
              src={src}
              strategy="beforeInteractive" // Load early for dependencies
            />
          );
        }
        return (
          <Script
            key={`inline-${index}`}
            id={`inline-script-${index}`}
            strategy="afterInteractive"
            dangerouslySetInnerHTML={{ __html: innerHtml ?? '' }}
          />
        );
      })}
    </>
  );
};
export default CodeObject;

Any suggestions or best practices would be greatly appreciated!

1
  • Seems like a dedicated tool for this type of thing would work much better. Have you tried using Google Tag Manager or something similar? Injecting scripts like this is generally not a recommended approach. Commented Apr 9 at 18:10

1 Answer 1

1

In Sitecore Headless + Next.js apps — executing dynamic HTML + CSS + Script from CMS content, Next.js doesn’t automatically run injected scripts, you can use dangerouslySetInnerHTML

Check with this code snippet:

import { useEffect, useRef } from 'react';
import { Field } from '@sitecore-jss/sitecore-jss-nextjs';
import { parse } from 'node-html-parser';
import { ComponentProps } from 'lib/component-props';
import { formatString } from 'scripts/format-string';

interface CodeObjectProps extends ComponentProps {
  fields: {
    HtmlContent: Field<string>;
    InPageNavigationTitle: Field<string>;
  };
}

const CodeObject = ({ fields }: CodeObjectProps): JSX.Element | null => {
  const htmlContainerRef = useRef<HTMLDivElement>(null);
  const scriptsInjected = useRef(false);
  const htmlContent = fields?.HtmlContent?.value;
  const navigationTitle = fields?.InPageNavigationTitle?.value;

  useEffect(() => {
    if (!htmlContainerRef.current || scriptsInjected.current || !htmlContent) return;

    const parsedHtml = parse(htmlContent);
    const scriptTags = parsedHtml.querySelectorAll('script');

    scriptTags.forEach((scriptTag) => {
      const scriptEl = document.createElement('script');
      const src = scriptTag.getAttribute('src');
      if (src) {
        scriptEl.src = src;
        scriptEl.async = true;
      } else {
        scriptEl.text = scriptTag.innerHTML;
      }
      document.body.appendChild(scriptEl);
    });

    scriptsInjected.current = true;
  }, [htmlContent]);

  if (!htmlContent) return null;

  // Strip <script> tags from original HTML
  const parsedHtml = parse(htmlContent);
  parsedHtml.querySelectorAll('script').forEach((tag) => tag.remove());

  return (
    <section
      className="code-object-sec"
      ref={htmlContainerRef}
      dangerouslySetInnerHTML={{ __html: parsedHtml.toString() }}
      {...(navigationTitle ? { id: formatString(navigationTitle) } : {})}
      {...(navigationTitle ? { inPageNav_Name: navigationTitle } : {})}
    />
  );
};

export default CodeObject;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.