nextjs 动态路由渲染内容不起作用
Posted
技术标签:
【中文标题】nextjs 动态路由渲染内容不起作用【英文标题】:nextjs Dynamic route rendering content not working 【发布时间】:2021-01-11 03:12:18 【问题描述】:我被这个问题困扰了很多天。我正在使用Next.js 并且有 3 页。
pages/index.js pages/categories.js 页面/类别/[slug].jscategories/[slug].js
使用Next.js 获取方法名称getServerSideProps,该方法在每个请求上运行并用于在运行时构建动态页面。 categories/[slug].js
在页面上呈现动态内容,动态内容来自 CMS,作为 API 端点的响应。动态内容只不过是一个包含带有<script />
元素的html 的字符串。
注意:要从 CMS 获取内容,我们必须发送带有 CMS
凭据的 POST
请求,例如用户名、密码和内容的页面 slug。我正在使用axios 库发送一个post 请求,方法在post.js
文件中。
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;
但是对于categories/[slug].js
页面上的渲染内容,我使用Reactjs 道具名称dangerouslySetInnerHTML 来渲染所有HTML,其中还包含JSON 字符串中的<script />
元素。
页面/类别/[slug].js:
<div dangerouslySetInnerHTML= __html: result.html />
根据每个 slug,内容加载良好。但是当我导航到该类别页面时,即pages/categories/index.js
。
<Link href="/categories/[slug]" as="/categories/online-cloud-storage">
<a>Online Cloud Storage</a>
</Link>
它有一个<Link />
元素,当我点击它时。
动态内容加载正常,但动态内容包含无法正常工作的 accordion
和 slider
元素。我认为这些元素中的<script />
不起作用。但是当我刷新页面时,它们工作正常。看这个。
当我设置这样的链接时,它们也可以正常工作。
<Link href="/categories/online-cloud-storage" as="/categories/online-cloud-storage">
<a>Online Cloud Storage</a>
</Link>
但是像上面的方法设置链接后,点击导致页面硬重载。但我不想要这个。一切都应该工作。当用户点击类别链接时。
有没有办法解决这个问题?
为什么当您从categories/index.js
页面点击时内容元素不起作用?
Github repo
代码:
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;
页面/类别/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;
页面/类别/[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;
【问题讨论】:
点击手风琴时控制台是否有任何错误? (在不工作的版本中) 您能否提供 CMS 网址?如果可以,能否提供用户名和密码? @dna 不,没有任何控制台错误。我认为dangerouslySetInnerHTML= __html: result.html
没有运行 javascript <script />
和 <script />
元素总是在客户端运行。
@TopTalent 这是机密凭据。
我认为*** question 会对您有所帮助。实际上,script 标签在 dangerouslySetInnerHTML 中不起作用,所以需要在组件挂载后运行脚本。
【参考方案1】:
恕我直言,我认为脚本加载不正确是由于错误地导入了客户端渲染 (CSR) 和服务器端渲染 (s-s-r) 上的脚本。在这里阅读more,也可以看看this interesting article。这也可以解释链接组件的问题行为。
就您而言,我理解这种行为是由于在 CSR 期间对页面及其组件的生命周期进行了错误处理,因为许多脚本可能需要在页面导航中正确共享,可能在 s-s-r 中。我对NextJS
没有全面的问题或扩展专业知识,但我认为这些脚本应该导入一个地方并可能在服务器上呈现,而不是在每个页面上错误导入,错误地让 CSR以非NextJS
优化的方式完成工作。
建议的方法是为您的应用程序使用自定义 Document
实现(仅限 s-s-r),您可以在其中定义脚本。有关这方面的更多详细信息,请参阅here。另外我想您已经为您的应用设置了custom App file,您将在文档中使用它,用于所有页面通用的 CSR 和 s-s-r 渲染(有关更多信息,请参阅this SO question)。
import Document, Html, Head, Main, NextScript from 'next/document'
class MyDocument extends Document
static async getInitialProps(ctx)
// ...
render()
return (
<Html>
<Head>
/*Your head scripts here*/
</Head>
<body>
<Main />
/*Your body scripts here*/
<NextScript />
</body>
</Html>
)
Head
原生组件在后台做了很多工作,以便为脚本、标记等设置内容。我建议你这样做,而不是直接将脚本添加到每个页面中。
【讨论】:
【参考方案2】:这里的问题是<script>
标签是动态 插入dangerouslySetInnerHTML
或innerHTML
,不会像HTML 5 specification 状态那样执行:
使用 innerHTML 插入的脚本元素在插入时不会执行。
如果您想在页面初始呈现后插入新的<script>
标签,您需要通过 JavaScript 的 document.createElement('script')
接口执行此操作,并使用 element.appendChild()
附加到 DOM 以确保它们被执行。
脚本在更改路由后不起作用,但在您刷新页面后它们起作用与 Next.js 应用程序生命周期过程相关联。
如果刷新页面,Next.js 会在服务器上预渲染整个网站,并将其作为一个整体发送回客户端。因此,该网站被解析为常规静态页面,<script>
标签按正常方式执行。
如果您更改路由,Next.js 不会刷新整个网站/应用程序,而只会刷新其中已更改的部分。换言之,仅获取page
组件,并将其动态 插入到现有布局中,替换之前的page
。因此,<script>
标签不被执行。
简单的解决方案
通过解析 HTML 字符串并重新创建 DOM 树结构,让一些现有的库为您处理繁重的工作。这是它在 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 ;
更难的解决方案
您必须找到一种方法来从您的 HTML 字符串中提取所有 <script>
标记并将它们分别添加到您的页面中。最简洁的方法是修改 API 响应以在两个单独的字符串中提供静态 HTML 和动态脚本。然后,您可以使用dangerouslySetInnerHTML
插入 HTML 并在 JS 中添加脚本:
const scripts = extractScriptTags(result.html); // The hard part
scripts.forEach((script) =>
const scriptTag = document.createElement('script');
scriptTag.innerText = script;
element.current.appendChild(scriptTag);
);
【讨论】:
以上是关于nextjs 动态路由渲染内容不起作用的主要内容,如果未能解决你的问题,请参考以下文章
Tailwind CSS 动画在 ReactJs/NextJs 中不起作用