I am stuck on this problem for many days. I am using Next.js and have 3 pages.
The categories/[slug].js
is using Next.js fetching method name getServerSideProps that runs on each request and used for build dynamic pages on runtime. The categories/[slug].js
is rendering a dynamic content on the page that dynamic content comes from the CMS as a response from the API Endpoint. Dynamic content is nothing but a string that contains HTML with <script />
elements.
Note: To fetch the content from the CMS we have to send a POST
request with the CMS
credentials like username, password, and the page slug for the content. I am using axios library to send a post request and the method is inside post.js
file.
post.js:
import axios from 'axios';
const postCMS = async (slug) => {
const url = `${process.env.CMS_API_URL}/render-page/`;
let pageSlug = slug;
// If the pageSlug is not start with `/`, then create the slug with `/`
if (!pageSlug.startsWith('/')) {
pageSlug = `/${pageSlug}`;
}
const head = {
Accept: 'application/json',
'Content-Type': 'application/json'
};
const data = JSON.stringify({
username: process.env.CMS_API_USERNAME,
password: process.env.CMS_API_PASSWORD,
slug: pageSlug
});
try {
const response = await axios.post(url, data, {
headers: head
});
return response.data;
} catch (e) {
return e;
}
};
export default postCMS;
But for the rendering content on the categories/[slug].js
page, I am using the Reactjs prop name dangerouslySetInnerHTML to render all the HTML which also contains <script />
elements in the JSON string.
pages/categories/[slug].js:
<div dangerouslySetInnerHTML={{ __html: result.html }} />
The content is loading fine based on each slug. But when I navigate to that category page i.e.pages/categories/index.js
.
<Link href="/categories/[slug]" as="/categories/online-cloud-storage">
<a>Online Cloud Storage</a>
</Link>
It has a <Link />
element and when I click it.
The dynamic content is loading fine but that dynamic content contains accordion
and slider
elements they are not working. I think <script />
of these elements is not working. But when I refresh the page they work fine. See this.
They also work fine when I set the Link something like this.
<Link href="/categories/online-cloud-storage" as="/categories/online-cloud-storage">
<a>Online Cloud Storage</a>
</Link>
But after setting the link like the above method, the click is caused to hard reload the page. But I don't want this. Everything should work. When the user clicks on the category link.
Is there a way to fix this?
Why the content elements are not working when you click from the categories/index.js
page?
Github repo
Code:
pages/index.js:
import React from 'react';
import Link from 'next/link';
const IndexPage = () => {
return (
<div>
<Link href="/categories">
<a>Categories</a>
</Link>
</div>
);
};
export default IndexPage;
pages/categories/index.js:
import React from 'react';
import Link from 'next/link';
const Categories = () => {
return (
<div>
<Link href="/categories/[slug]" as="/categories/online-cloud-storage">
<a>Online Cloud Storage</a>
</Link>
</div>
);
};
export default Categories;
pages/categories/[slug].js:
import React from 'react';
import Head from 'next/head';
import postCMS from '../../post';
const CategoryPage = ({ result }) => {
return (
<>
<Head>
{result && <link href={result.assets.stylesheets} rel="stylesheet" />}
</Head>
<div>
<h1>Category page CMS Content</h1>
<div dangerouslySetInnerHTML={{ __html: result.html }} />
</div>
</>
);
};
export const getServerSideProps = async (context) => {
const categorySlug = context.query.slug;
const result = await postCMS(categorySlug);
return {
props: {
result
}
};
};
export default CategoryPage;
The problem here is that <script>
tags which are dynamically inserted with dangerouslySetInnerHTML
or innerHTML
, are not executed as HTML 5 specification states:
script elements inserted using innerHTML do not execute when they are inserted.
If you want to insert new <script>
tag after the page has initially rendered, you need to do it through JavaScript's document.createElement('script')
interface and appended to the DOM with element.appendChild()
to make sure they're executed.
The reason why the scripts don't work after changing routes, but they do work after you refresh the page is tied to Next.js application lifecycle process.
<script>
tags are executed as they normally would.page
component is fetched and it is dynamically inserted into existing layout replacing previous page
. Therefore, the <script>
tags are not executed.Let some existing library handle the hard work for you by parsing the HTML string and recreating the DOM tree structure. Here's how it could look in jQuery:
import { useEffect, useRef } from 'react';
import $ from 'jquery';
const CategoryPage = ({ result }) => {
const element = useRef();
useEffect(() => {
$(element.current).html($(result.html));
}, []);
return (
<>
<Head>
{result && <link href={result.assets.stylesheets} rel="stylesheet" />}
</Head>
<div>
<h1>Category page CMS Content</h1>
<div ref={element}></div>
</div>
</>
);
};
export const getServerSideProps = async (context) => {
/* ... */
return { props: { result } };
}
You would have to find a way to extract all <script>
tags from your HTML string and add them separately to your page. The cleanest way would be to modify the API response to deliver static HTML and dynamic script in two separate strings. Then, you could insert the HTML with dangerouslySetInnerHTML
and add script in JS:
const scripts = extractScriptTags(result.html); // The hard part
scripts.forEach((script) => {
const scriptTag = document.createElement('script');
scriptTag.innerText = script;
element.current.appendChild(scriptTag);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With