Razzle: 如何解决服务器渲染问题?

创建于 2019-02-05  ·  9评论  ·  资料来源: jaredpalmer/razzle

你好,

我们注意到一个问题,如果您登录到 Google 的搜索控制台,并以 Google 的身份提取,则该网站会呈现为一个空白页面。

我做了一些研究,其中一些使我想到了这篇文章:

https://medium.com/@gajus/react -application-seen-as-a-blank-page-via-fetch-as-google-afb11dff8562

很可能是搜索引擎和 fetch as google 本身的行为方式不同,否则我们现在已经失去了排名,不过我想解决这个问题,因为它会让任何网站所有者保持清醒。

我已经下载了 phantomjs,假设它与 Google 的 fetch 相同,并尝试呈现它会返回许多错误:

image

这个问题可能就像更改正在编译的目标浏览器一样容易解决,但是简单地安装 babel/polyfil 并导入是行不通的。

有人能在这里指出我正确的方向吗?

stale

最有用的评论

所以我的建议(也许我们需要文档中的故障排除指南)是尽可能多地从server.jsclient.jsApp.js和潜在的您的 webpack 覆盖(如果有的话)的各个方面,以使您的 Razzle 应用程序回到“hello world”之类的状态。 我还建议将 Razzle 的 HTML 模板复制回您的server.js文件,以确保所有脚本都正确加载开箱即用。 运行后,打开 Chrome 开发工具并转到“性能”选项卡,选中“屏幕截图”复选框,然后按刷新图标。 请参阅下面的 GIF。

kapture 2019-02-05 at 8 33 42

目标是确保时间轴区域中没有空白的白框。 在您获得没有闪光的正确渲染后,您应该添加越来越多的旧代码。 采取婴儿的步骤。 如果你不小心,这很容易搞砸。

至于你的具体情况,我会首先删除所有谷歌标签管理器的东西,你所有的 CSS,以及你正在做的任何代码拆分。 只需让它渲染并向后工作即可。 接下来,让 Redux 工作。 然后重新添加 react-helmet,然后,一旦您认为自己做对了,还可以通过进行 Lighthouse 测试(审核选项卡)来仔细检查。 将此状态提交到新分支,以便您将来可以返回到它。 接下来,重新添加您的分析脚本并再次检查渲染。 最后,尝试添加回代码拆分。

注意:如果你保留你的 css 并在开发模式下用 Razzle 做上面的分析,你可能会得到一个 FOUC,因为 Razzle 在开发过程中不会在服务器上处理.css样式(只是在客户端)。 因此,如果您正在使用.css文件,要全面测试您是否正在使用样式水化进行适当的 SSR,您应该为生产构建 Razzle 并在本地运行生产服务器。 但是,如果您只是从服务器检查 HTML 是否存在,您可以在开发模式下运行它。

所有9条评论

如果您的页面显示为空白,那么您已经破坏了 SSR 或补水方案。 Razzle 不会受到您所描述的开箱即用的影响,事实上,它用于以 SEO 为重点的网站,例如 BBC.com。 你能粘贴你的 server.js 文件吗? 您是否在服务器上获取数据?

嗨,贾里德,

感谢您的及时回复,是的,我们确实从服务器获取数据。

这是我的 server.js

import App from './App';
import React from 'react';
import Helmet from 'react-helmet';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import express from 'express';
import compression from 'compression';
import { renderToString } from 'react-dom/server';

import configureStore from './store/configureStore';
import { remoteLoader } from './api/remoteLoader';
import serialize from 'serialize-javascript';

import { Capture } from 'react-loadable';
import { getBundles } from 'react-loadable/webpack';
import stats from '../build/react-loadable.json';

import { IS_PRODUCTION } from './components/shared/constants';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);

const server = express();
server.use(compression());

if (!IS_PRODUCTION) {
    server.set('cache', false);
}

server
    .disable('x-powered-by')
    .use(express.static(process.env.RAZZLE_PUBLIC_DIR, { Expires: '30d' }))
    .get('/*', (req, res) => {
        //Ensures clients with old css paths are served the current file
        if (req.path.indexOf('/static/css') > -1 && assets.client.css) {
            const currentCssFile = `${process.env.RAZZLE_PUBLIC_DIR}${assets.client.css}`;
            return res.sendFile(currentCssFile);
        }

        remoteLoader(apiResult => {
            const responseCode = typeof apiResult.status === 'undefined' ? 404 : apiResult.status;

            if (responseCode === 301) {
                return res.redirect(responseCode, apiResult.headers.location);
            }

            // Compile an initial state
            const initialState = {
                remote: {
                    cms: {
                        result: apiResult ? apiResult.data : false,
                        loading: false
                    },
                    myDrewberry: {
                        searchResults: null,
                        loading: false,
                        failed: false
                    }
                }
            };
            // Create a new Redux store instance
            const store = configureStore(initialState);

            const context = {};
            const modules = [];
            const markup = renderToString(
                <Capture report={moduleName => modules.push(moduleName)}>
                    <StaticRouter context={context} location={req.url}>
                        <Provider store={store}>
                            <App />
                        </Provider>
                    </StaticRouter>
                </Capture>
            );
            const helmet = Helmet.renderStatic();

            if (context.url) {
                res.redirect(context.url);
            } else {
                const bundles = getBundles(stats, modules);
                const chunks = bundles.filter(bundle => bundle.file.endsWith('.js'));

                res.status(responseCode).send(
                    `<!doctype html>
                    <html ${helmet.htmlAttributes.toString()}>
                    <head>

                        ${assets.client.css ? `<link rel="stylesheet" href="${assets.client.css}">` : ''}
                        ${helmet.title.toString()}
                        ${helmet.meta.toString()}
                        ${helmet.link.toString()}
                        <link rel="icon" type="image/png" href="/favicon16.png" sizes="16x16"/>
                        <link rel="icon" type="image/png" href="/favicon32.png" sizes="32x32"/>
                        <link rel="icon" type="image/png" href="/favicon96.png" sizes="96x96"/>
                        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/> <!--320-->
                        <meta http-equiv="expires" content="0">
                    </head>
                    <body class="drewberry-preload">

                        <!-- Google Tag Manager -->
                            <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
                            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
                            })(window,document,'script','dataLayer','GTM-TKPXWB');</script>
                        <!-- End Google Tag Manager -->

                        <div id="root">${markup}</div>
                        <script>
                            window.__PRELOADED_STATE__ = ${serialize(initialState)}
                        </script>
                        ${
                            IS_PRODUCTION
                                ? `<script src="${assets.client.js}"></script>`
                                : `<script src="${assets.client.js}" crossorigin></script>`
                        }
                          ${chunks
                              .map(chunk =>
                                  IS_PRODUCTION
                                      ? `<script src="/${chunk.file}"></script>`
                                      : `<script src="http://${process.env.HOST}:${parseInt(process.env.PORT, 10) + 1}/${
                                            chunk.file
                                        }"></script>`
                              )
                              .join('\n')}
                          <script>window.main();</script>
                    </body>
                </html>`
                );
            }
        }, req.path);
    });

export default server;

我可以确认开箱即用的 Razzle 没有受到影响,只是加载了一个干净的安装并使用 phantomjs 没有出现任何警告,这意味着它可能是我们安装的某个库的东西。

你在上面看到了什么明显的东西吗?

此刻的感觉就像是在追赶大雁! :-)

所以我的建议(也许我们需要文档中的故障排除指南)是尽可能多地从server.jsclient.jsApp.js和潜在的您的 webpack 覆盖(如果有的话)的各个方面,以使您的 Razzle 应用程序回到“hello world”之类的状态。 我还建议将 Razzle 的 HTML 模板复制回您的server.js文件,以确保所有脚本都正确加载开箱即用。 运行后,打开 Chrome 开发工具并转到“性能”选项卡,选中“屏幕截图”复选框,然后按刷新图标。 请参阅下面的 GIF。

kapture 2019-02-05 at 8 33 42

目标是确保时间轴区域中没有空白的白框。 在您获得没有闪光的正确渲染后,您应该添加越来越多的旧代码。 采取婴儿的步骤。 如果你不小心,这很容易搞砸。

至于你的具体情况,我会首先删除所有谷歌标签管理器的东西,你所有的 CSS,以及你正在做的任何代码拆分。 只需让它渲染并向后工作即可。 接下来,让 Redux 工作。 然后重新添加 react-helmet,然后,一旦您认为自己做对了,还可以通过进行 Lighthouse 测试(审核选项卡)来仔细检查。 将此状态提交到新分支,以便您将来可以返回到它。 接下来,重新添加您的分析脚本并再次检查渲染。 最后,尝试添加回代码拆分。

注意:如果你保留你的 css 并在开发模式下用 Razzle 做上面的分析,你可能会得到一个 FOUC,因为 Razzle 在开发过程中不会在服务器上处理.css样式(只是在客户端)。 因此,如果您正在使用.css文件,要全面测试您是否正在使用样式水化进行适当的 SSR,您应该为生产构建 Razzle 并在本地运行生产服务器。 但是,如果您只是从服务器检查 HTML 是否存在,您可以在开发模式下运行它。

好的,感谢 Razzle 所做的出色工作,现在为此。 我会很好地审计它并报告回来。

当然,检查任何“空”(不是空白,这是一个错误)seo 页面的第一件事是:您的页面是否依赖于componentDidMount来加载要显示的数据。 您可以使用curl进行快速检查。 如果是,你需要像 After.js 这样的东西来预加载。

如果在 1000 毫秒标记附近有空白页,您能否建议如何使用“性能”选项卡进行调试? 我知道我的问题是图标字体库没有加载到服务器上,因此它在初始加载时显示这些空白的空方块......

screen shot 2019-02-11 at 9 57 30 am

啊,我所有的自定义字体直到很久以后才被加载。 我正在通过 CSS 的 @font-face {} 加载我的字体。

@font-face {
  font-family: 'SFProText';
  font-display: auto;
  src: url('fonts/SFPro/SF-Pro-Display-Regular.otf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

@jaredpalmer我也有同样的问题。 我的客户端页面中未加载字体和资产。

我没有在我的应用程序设置中添加任何 webpack.config 文件,这是我从你的 repo 克隆的。 我刚刚从你的 repo 中获取了副本并安装了 npm modules ,然后我通过运行 npm start 命令开始使用该应用程序。
我错过了什么配置或者必须使用我的新 webpack 文件来解决这个问题?

请帮我解决这个问题。

我的 App.css 文件看起来像这样..
@字体脸{
font-family: 'Roboto-medium';
src: url("./static/fonts/Roboto-Medium-webfont.eot");
src: url("./static/fonts/Roboto-Medium-webfont.eot?#iefix") 格式("embedded-opentype"), url("./static/fonts/Roboto-Medium-webfont.woff")格式("woff"), url("./static/fonts/Roboto-Medium-webfont.ttf") 格式("truetype"); }

@字体脸{
font-family: 'Roboto-regular';
src: url("./static/fonts/roboto-regular-webfont.eot");
src: url("./static/fonts/roboto-regular-webfont.eot?#iefix") 格式("embedded-opentype"), url("./static/fonts/roboto-regular-webfont.woff")格式("woff"), url("./static/fonts/roboto-regular-webfont.ttf") 格式("truetype"); }

谢谢 inadvane :)

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

MaxGoh picture MaxGoh  ·  4评论

pseudo-su picture pseudo-su  ·  3评论

ewolfe picture ewolfe  ·  4评论

kkarkos picture kkarkos  ·  3评论

gabimor picture gabimor  ·  3评论