【问题标题】:nextjs Dynamic route rendering content not workingnextjs 动态路由渲染内容不起作用
【发布时间】:2021-01-11 03:12:18
【问题描述】:

我被这个问题困扰了很多天。我正在使用Next.js 并且有 3 页。

  • pages/index.js
  • pages/categories.js
  • 页面/类别/[slug].js

categories/[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>

它有一个&lt;Link /&gt; 元素,当我点击它时。

动态内容加载正常,但动态内容包含无法正常工作的 accordionslider 元素。我认为这些元素中的&lt;script /&gt; 不起作用。但是当我刷新页面时,它们工作正常。看这个。

当我设置这样的链接时,它们也可以正常工作。

<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 &lt;script /&gt;&lt;script /&gt; 元素总是在客户端运行。
  • @TopTalent 这是机密凭据。
  • 我认为stackoverflow question 会对您有所帮助。实际上,script 标签在 dangerouslySetInnerHTML 中不起作用,所以需要在组件挂载后运行脚本。

标签: reactjs next.js


【解决方案1】:

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

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

建议的方法是为您的应用程序使用自定义 Document 实现(仅限 SSR),您可以在其中定义脚本。有关这方面的更多详细信息,请参阅here。另外我想您已经为您的应用设置了custom App file,您将在文档中使用它,用于所有页面通用的 CSR 和 SSR 渲染(有关更多信息,请参阅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】:

    这里的问题是&lt;script&gt; 标签是动态 插入dangerouslySetInnerHTMLinnerHTML,不会像HTML 5 specification 状态那样执行:

    使用 innerHTML 插入的脚本元素在插入时不会执行。

    如果您想在页面初始呈现后插入新的&lt;script&gt; 标签,您需要通过 JavaScript 的 document.createElement('script') 接口执行此操作,并使用 element.appendChild() 附加到 DOM 以确保它们被执行。

    脚本在更改路由后不起作用,但在您刷新页面后它们起作用与 Next.js 应用程序生命周期过程相关联。

    • 如果刷新页面,Next.js 会在服务器上预渲染整个网站,并将其作为一个整体发送回客户端。因此,该网站被解析为常规静态页面,&lt;script&gt; 标签按正常方式执行。
    • 如果您更改路由,Next.js 不会刷新整个网站/应用程序,而只会刷新其中已更改的部分。换言之,仅获取page 组件,并将其动态 插入到现有布局中,替换之前的page。因此,&lt;script&gt; 标签被执行。

    简单的解决方案

    通过解析 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 字符串中提取所有 &lt;script&gt; 标记并将它们分别添加到您的页面中。最简洁的方法是修改 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);
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-16
      • 2022-01-01
      • 2021-11-04
      • 2022-01-19
      • 2020-10-17
      • 1970-01-01
      • 2016-02-24
      • 1970-01-01
      相关资源
      最近更新 更多