Next.js reload Script tag

3.2k Views Asked by At

I am trying to embed 3rd party commenting script to my next.js eg. (disqus, remark42, hyvor) but unfortunately it only loads on the first load and I have to reload the page again for the embedded 3rd party script to show up, but it is anti pattern to react/next.js because next.js doesn't reload when you navigate to another page via the Link component, So I'm looking for a solution to only reload the Script component itself so that my commenting widget appears on every article pages of the website.

code:

export const getStaticPaths: GetStaticPaths = async () => {
  const data = await getArticles();

  const paths = data.map((article) => ({
    params: {
      slug: article?.slug,
    },
  }));

  return {
    paths,
    fallback: true,
  };
};

export const getStaticProps = async ({
  params,
}: GetStaticPropsContext<{ slug: string }>) => {
  const article = await getArticleByProp("slug", params!.slug);

  return {
    props: {
      article: article[0],
    },
    notFound: article.length === 0,
    revalidate: 60,
  };
};

const ArticlePage = ({
  article,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
  const router = useRouter();
  const articleDate = useFormattedDate(
    article?.createdAt ? new Date(article.createdAt) : new Date(),
    "distance"
  );

  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <Container>
      <SEOHeader
        title={article?.title}
        author={article?.author}
        description={article?.excerpt}
        ogImage={article?.featuredImage}
        canonical={article?.slug}
      />
      <Wrapper>
        <ArticleWrapper>
          <ArticleHeader>
            <small className="category">{article?.category}</small>
            <h1 className="title">{article?.title}</h1>
            <p className="contributor">
              <span>
                By <strong> {article?.author}</strong> <br />
              </span>
            </p>
            <div className="date">
              <Clock size={18} />{" "}
              <span>{article?.createdAt ? articleDate : "N/A"}</span>
            </div>
            <button className="share">
              <Share size={24} />
            </button>
          </ArticleHeader>
          <ArticleBody>
            {!!article?.featuredImage && (
              <Featured>
                <Image
                  src={article.featuredImage}
                  layout="responsive"
                  width={1920}
                  height={1080}
                  alt="Featured article image"
                />
              </Featured>
            )}
            <ArticleExcerpt>{article?.excerpt}</ArticleExcerpt>
            <ArticleMdx>{article?.body}</ArticleMdx>
          </ArticleBody>
          <div id="remark42">{""}</div>

          {/* <Script id="remark42-script" strategy="afterInteractive">
            {`
              var remark_config = {
                host: "http://localhost:5010",
                site_id: "mysite",
                show_email_subscription: false,
                url: "${process.env.BASE_URL + router.asPath}"
              }
              
              !(function (e, n) {
                for (var o = 0; o < e.length; o++) {
                  var r = n.createElement("script"),
                    c = ".js",
                    d = n.head || n.body;
                  "noModule" in r ? ((r.type = "module"), (c = ".mjs")) : (r.async = !0),
                    (r.defer = !0),
                    (r.src = remark_config.host + "/web/" + e[o] + c),
                    d.appendChild(r);
                }
              })(remark_config.components || ["embed"], document);
            `}
          </Script> */}

          {/* <div id="hyvor-talk-view"></div>
          <Script id="hyvor-script">
            {`
                  var HYVOR_TALK_WEBSITE = 7527;
                  var HYVOR_TALK_CONFIG = {
                      url: "${process.env.BASE_URL + router.asPath}",
                      id: "${article?.id}",
                  };
            `}
          </Script>
          <Script
            async
            src="//talk.hyvor.com/web-api/embed.js"
            strategy="lazyOnload"
            onLoad={() =>
              console.log(
                `script loaded correctly, window.FB has been populated`
              )
            }
          /> */}

          {/* <Discussion id={article?.id} title={article?.title} /> */}

          <Script id="remark42-test">{`
          const remark_config = {
            host: 'https://demo.remark42.com',
            site_id: 'remark',
          };
          window.remark_config = remark_config;
          !function(e,n){for(var o=0;o<e.length;o++){var r=n.createElement("script"),c=".js",d=n.head||n.body;"noModule"in r?(r.type="module",c=".mjs"):r.async=!0,r.defer=!0,r.src=remark_config.host+"/web/"+e[o]+c,d.appendChild(r)}}(remark_config.components||["embed"],document);
          `}</Script>
        </ArticleWrapper>
        <Recommended>
          <h2>Recommended</h2>
          <ArticleCard card={article} variant="slim" />
          <ArticleCard card={article} variant="slim" />
          <ArticleCard card={article} variant="slim" />
          <ArticleCard card={article} variant="slim" />
        </Recommended>
      </Wrapper>
    </Container>
  );
};

export default ArticlePage;
2

There are 2 best solutions below

0
peperoli On

Maybe this helps with similar issues:

next/script offers the newer onReady prop wich is very useful for dealing with third-party maps, widgets and more.

You can execute code after the script's load event when it first loads and then after every subsequent component re-mount using the onReady property.

Code example from the docs:

      <Script
        id="google-maps"
        src="https://maps.googleapis.com/maps/api/js"
        onReady={() => {
          new google.maps.Map(mapRef.current, {
            center: { lat: -34.397, lng: 150.644 },
            zoom: 8,
          })
        }}
      />
0
Aman Kumar Gupta On

Script component from 'next/script' works in the same way as you mentioned in the question i.e it will only load the script as soon as you land on the page or reload it, after which the script gets cached and won't be reloaded while navigating between the pages.

So in order to achieve the requirement to reload the script while navigating back and forth we need to make some custom adjustments using Vanilla JS and contextual ref.

Please follow the below code for reference and make the adjustments in your code accordingly as per the requirements -

export default function LoyaltyRewards(): JSX.Element | null {
    const socialAnnexRef = useRef<HTMLDivElement>(null)

    const script = `
        // Whatever script you want to inject
        <script id="social-annex">
            var siteID = '${siteId}';
            var sa_emailid = '${sAEmailId}';
            var token = '${token}';
            var sa_uni = sa_uni || []; sa_uni.push(['sa_pg', '5']);
            (function () {
                function sa_async_load () {
                    var sa = document.createElement('script');
                    sa.type = 'text/javascript';
                    sa.async = true; sa.src="https://cdn.socialannexuat.com/partner/${siteId}/universal.js";
                    var sax = document.getElementsByTagName('script')[0]; sax.parentNode.insertBefore(sa, sax);
                }
                sa_async_load();
            })();
        </script>
    `

    useEffect(() => {
        /* Clear out the loaded script's on component's un-mount */
        return () => {
            document.getElementById('social-annex')?.remove()
            document.getElementById('sa_load_loader')?.remove()
            document.getElementById('socialannex-s15dasboard')?.remove()
            document.getElementById('social-annex-universal')?.remove()
        }

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    useEffect(() => {
        if (siteId) {
            // creates a document range (grouping of nodes in the document). In this case, we instantiate it as empty, on purpose
            const range = document.createRange()
            // creates a mini-document (lightweight version), in our range with our script in it
            const documentFragment = range.createContextualFragment(script)
            // appends it on the same level of annex div - so that it renders in the correct location
            socialAnnexRef.current?.appendChild(documentFragment)
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [siteId])

    return (
        <div className="mt-8" ref={socialAnnexRef}>
            <div id="socialannex_dashboard" />
        </div>
    )
}

Hope this will help you or somebody else in the future. Thanks!

Happy Coding :-)