nextjs动态路由呈现内容不起作用

时间:2020-09-24 19:16:59

标签: reactjs next.js

我在这个问题上困扰了很多天。我正在使用Next.js,共有3页。

  • pages / index.js
  • pages / categories.js
  • pages / categories / [slug] .js

categories/[slug].js使用Next.js获取方法名称getServerSideProps,该方法名称在每个请求上运行,并在运行时用于构建动态页面。 categories/[slug].js在页面上呈现动态内容,该动态内容来自CMS,是API端点的响应。动态内容不过是包含带有<script />元素的HTML的字符串。

CMS Response

注意:要从CMS中获取内容,我们必须发送一个POST凭据,并带有CMS凭据,例如用户名,密码和该内容的页面标记。我正在使用axios库发送发布请求,并且该方法位于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来呈现所有在JSON中也包含<script />元素的HTML字符串。

页面/类别/[slug].js

<div dangerouslySetInnerHTML={{ __html: result.html }} /> 

根据每个段的内容,内容加载正常。但是当我导航到该类别页面时,即pages/categories/index.js

<Link href="/categories/[slug]" as="/categories/online-cloud-storage">
      <a>Online Cloud Storage</a>
</Link>

它有一个<Link />元素,当我单击它时。

动态内容可以很好地加载,但是该动态内容包含accordionslider元素,它们不能工作。我认为这些元素中的<script />无效。但是,当我刷新页面时,它们可以正常工作。看到这个。

Preview the problem

当我设置“链接”时,它们也可以正常工作。

<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;

2 个答案:

答案 0 :(得分:1)

这里的问题是,用<script>dangerouslySetInnerHTML动态插入innerHTML标签没有按HTML 5 specification状态执行:< / p>

使用innerHTML插入的

script元素在插入时不会执行。

如果要在页面最初呈现后插入新的<script>标签,则需要通过JavaScript的document.createElement('script')接口进行操作,并使用element.appendChild()附加到DOM上以确保它们被执行。

更改路由后脚本不起作用,但是刷新页面后它们起作用的原因与Next.js应用程序生命周期过程有关。 / p>

  • 如果刷新页面,则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);
});

答案 1 :(得分:0)

恕我直言,我认为脚本加载不正确是由于在客户端渲染(CSR)和服务器端渲染(SSR)上错误导入了脚本。在此处阅读more,但也可以看看this interesting article。这也可以解释您的链接组件的问题行为。

在您的情况下,我了解此行为是由于CSR期间对页面及其组件的生命周期的错误处理所致,因为许多脚本可能需要在页面导航中正确共享(可能在SSR中)。我没有全面了解问题所在或在NextJS上拥有丰富的专业知识,但是我认为这些脚本应该在一个位置导入并可能在服务器上呈现,而不是在每个页面上错误地导入让CSR以非NextJS优化的方式完成工作。

建议的方法是为应用程序使用自定义Document实现(仅SSR),您可以在其中定义脚本。有关更多详细信息,请参见here。同样,我想您已经为您的App设置了custom App file,将在文档中使用它来实现所有页面通用的CSR和SSR渲染(有关更多信息,请参见this SO question)。 / p>

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本机组件在后台做了很多工作,以便为脚本,标记等设置东西。我建议您采用这种方式,而不是直接将脚本添加到每个页面中。 / p>