即使使用getStaticProps(useEffect和useState),NextJS也不会在服务器端挂载页面内容

eanckbw9  于 2022-11-23  发布在  其他
关注(0)|答案(2)|浏览(147)

我开发了一个网站,其中所有站点部分都是从JSON有效负载呈现的。也就是说,从一个具有定义结构的JSON中,我可以组装和重新组装几个页面,而无需更改代码,例如,只需通过外部工具修改JSON即可。
GetStaticProps方法根据请求的路由路径(例如localhost:3000/example gets /example payload)向外部工具请求特定页面的负载。然后,它将此负载发送到其props的页面组件。到目前为止,一切都很好,但我发现了一个SEO问题,即此负载如何成为一个页面,这将在下面解释。
在引用页面的文件中,有一个逻辑来执行每个section组件的动态导入。首先,使用getStaticProps有效负载,对包含所有数据的数组进行迭代,以组装一个section。然后,对于该数组的每个元素,该页面采用引用导入的组件名称的值,并使用React Suspension执行延迟导入,以导入组件并传递通过spread操作符将它的其余属性(可以在下面的代码中看到)。

[...]

const importSection = (sectionName: string) =>
    lazy(() =>
        import(`@templates/sections/${sectionName}`).catch(
            () => import("@components/others/nullView")
        )
    );

[...]

const sectionPromise = sections.map(async (data: any) => {
    const componentName = data.component_name;
    const Section = await importSection(componentName);

    return <Section key={shortid.generate()} {...sectionData} />;
});

[...]

一切都运行得很好。每个页面都正确地呈现了它的部分。然而,这个过程发生在客户端,例如,对于Lighthouse,或者从浏览器检查它,生成的HTML是完美的。但是对于搜索引擎,这不是很好,因为他们没有看到装载的页面,即使我使用getStaticProps并在服务器端装载了一个静态页面。
发生这种情况是因为函数执行组件的延迟导入并转发到页面的触发器是使用两个著名的React钩子完成的:使用效果(在客户端运行)和useState(这是异步的)。我使用useEffect作为触发器,捕获页面的mount事件并触发函数执行延迟导入,传递getStaticProps返回的有效负载。在此过程中,React的Suspense有一个加载组件。但是,useEffect只在客户端运行。换句话说,我无法在服务器端挂载页面,这导致了我的SEO丢失。因此,我重构了useEffect,以便在服务器端使用IIFE自动执行这个延迟导入函数来拥有这个挂载功能。解决方案最初是有效的,但我无法用节组装页面。即使导入组件也是如此,这是因为我使用useState接收组件列表,而useState异步运行,因此,导入组件并不总是在组装页面之前结束。
因此,在生成以交付页面的过程中,组件仍然没有呈现内容,因为页面在存储值的状态之前完成了其组装过程 从延迟导入返回的已完成更新(div __next没有子项)。我首先尝试实现一个状态池,允许我在服务器端使用状态控制。然而,计时问题仍然存在。我尝试了无数种方法使useState同步,但都不起作用。因此,我需要一个解决方案,以便我可以在服务器端运行惰性导入,并且在构建期间,应用程序等待延迟导入完成,以便从服务器端得到的页面具有完美组装的HTML。
下面是包含useEffect和useState的完整代码。

[...]

const importSection = (sectionName: string) =>
    lazy(() =>
        import(`@templates/sections/${sectionName}`).catch(
            () => import("@components/others/nullView")
        )
    );

const GenericPage = ({ pageContent }: SectionPayload): ReactElement => {
    const [sections, setSections] = useState<Array<ReactElement>>([]);

    useEffect(() => {
        (async (): Promise<void> => {
            const payloadSections = pageContent?.data.sections || [];

            const sectionPromise = payloadSections.map(async (data: any) => {
                const Section = await importSection(componentName);
                return <Section key={shortid.generate()} {...data} />;
            });

            Promise.all(sectionPromise)
                .then(setSections)
                .catch(() => console.warn("ERROR"));
        })();
    }, [pageContent]);

    return (
        <Suspense fallback={<LoadingPage />}>
            <Main>{sections}</Main>
        </Suspense>
    );
};

export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext) => {
    if (!context.params) {
        throw new Error("Params not found");
    }

    const pageID = context.params.genericPageID;

    const pageContent = await getPageContent(
        pageID,
        `populate[head][populate]=%2A&populate[dynamic_zone][populate]=%2A&locale=${context.locale}`
    );

    return {
        props: { pageContent },
        revalidate: 60,
    };
};

export const getStaticPaths: GetStaticPaths = async () => {
    [...]
};

export default GenericPage;
fcg9iug3

fcg9iug31#

您可以使用Next.js dynamic import

// Import all your components/sections/whatever you call it with "dynamic" import

import dynamic from 'next/dynamic'

const Title = dynamic(() => import('./Title'), {
  suspense: true
});
const Image = dynamic(() => import('./Image'), {
  suspense: true
});
const List = dynamic(() => import('./List'), {
  suspense: true
});

然后遍历您的数据并返回每个section类型所需的组件:

<Suspense fallback={'Loading...'}>
      {components.map((item, index) => {
        if (item.type === 'link') {
          return <Link href={item.href}>{item.text}</Link>;
        }

        if (item.type === 'title') {
          return <Title key={index} title={item} />;
        }

        if (item.type === 'image') {
          return <Image key={index} image={item} />;
        }

        if (item.type === 'list') {
          return <List key={index} list={item} />;
        }

        return <div key={index}>Unknown component</div>;
      })}
    </Suspense>

当然,如果是简单的if块,你可以使用任何你想要的,例如,制作某种组件Map:

const componentsMap = {
  title: Title,
  list: List,
  // ...
}

我希望你能明白这个想法!不要忘记用Suspense Package 整个东西,或者如果你愿意,你可以 Package 单独的组件来制作不同的加载器。

4smxwvx5

4smxwvx52#

免责声明:这是我的项目https://github.com/hacknlove/orgasmoproject
link avovbe是一个NextJS插件,它提供了您正在尝试编写的动态呈现内容。
您只需:
1.编写一些组件,并使用后缀.dynamic.tsx(或'.dynamic.tsx')将它们声明为dynamic
1.编写一些驱动程序方法从数据源获取数据,或者使用可用的驱动程序(MongoDB、strapi、json文件)
1.在数据库中定义页面,如果需要,可以使用提供的平台。

相关问题