React 单页程序 转 SEO 渲染踩到各种深坑。

1,663
原文链接: www.jianshu.com

React.js 一般晚上的教程都是React + webpack + Router 来搭建的项目。都是单页项目,我们将他称为SPA项目。 最近有一个项目一开始是使用React.js来的SPA项目,可是后面发现我们这种类型的家居生活馆的项目来说还是要做SEO,这操作的。

由于项目是自己可以把控,了解一下后发现React.js官方是提供了“同架”这个概念,也就是服务端渲染。可是需要自己搭建和了解它的特性,还要利用node写服务端,还有客户端。在时间不允许的情况下,我最后选了了社区版的Next.js。这可以说是很容易移植的一个框架。

笔者在使用的过程中,遇到了几处坑,这里需要提醒一下,看完后你们不用谷歌,也不用百度,看那些人copy的XXX乐事笔记,看那些你不如直接看官方文档。

1.不要使用npm run dev 来运行 next.js

很多文章都是 叫你安装 next.js 后,就叫你在package.json添加

"scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  }

然后运行Next.js项目

笔者这里并不推荐这种方法,由于Next.js访问static静态文件必须放到跟目录的static夹下面,你如今做到很多 开发校验 ,服务商都必须你放到跟目录下,比如 www.xxx.com/robots.txt 这样访问路径,如果在next.js
中 是 www.xxx.com/static/robo… 才能访问到。这时候服务商就并不能通过你的校验

在项目中任意创建个文件比如server.js内容如下

const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { join } = require('path');

const port = parseInt(process.env.PORT, 10) || 3000;

const app = next({ dev: false });  //注意这里!如果你是false的时候需要npm run build 如果是true就是开发模式
const handle = app.getRequestHandler();

app.prepare().then(() => {
    createServer((req, res) => {
        const parsedUrl = parse(req.url, true)
        const rootStaticFiles = ['/robots.txt', '/sitemap.xml', '/favicon.ico']

        if (rootStaticFiles.indexOf(parsedUrl.pathname) > -1) {
            const path = join(__dirname, 'static', parsedUrl.pathname)
            app.serveStatic(req, res, path)
        } else {
            handle(req, res, parsedUrl)
        }
    }).listen(port, err => {
        if (err) throw err;
        console.log(`> Ready on http://localhost:${port}`)
    })
})

rootStaticFiles定义成文件名称数组,其实是使用了next.js的中间件处理。

2.windows location localStorage 对象经常undefined 导致的 异常错误

由于windows location localStorage 这一类都属于客户端js,如果在node【服务端】都没有这一类的全局对象。
我发现只要你在 render() , getInitialProps(),componentWillMount()这三个方法中写客户端全局代码就完全没有问题。
如果非要在render()写的话本人推荐是以下写法

 {
                                            this.state.payTypeValue == 'zhifubao' &&
                                            <div>

                                                <form action={`${ApiUri}/v1/alipay/page`} method="post">
                                                    <input type="hidden" value={this.state.order_sn} name="order_sn" />
                                                    <input type="hidden" value={localStorage.getItem('u_token')} name="token" />
                                                    <Button htmlType="submit">
                                                        确认支付
                                                </Button>
                                                </form>
                                            </div>
                                        }

使用React.Fragment类的方案让next.js 认为你不是在使用服务端渲染。

3.componentWillMount 和 componentDidMount

componentWillMount是不可以添加jqeruy 第三方插件,
componentDidMount中是可以的
添加 第三方插件库 只要不是React组件编写形式 或者 服务端代码都会编译错误 所以不要使用
import xxx from 'xxxx'';

我是建议所有的 客户端代码都要写在componentDidMount方法里面,如果你使用getInitialProps()方法渲染的东西在componentDidMount并没有任何作用。必须在componentWillMount中赋值才生效。

比如:

static async getInitialProps({ query }) {

        const result = await http.get('v1/good/search', {
            params: query
        });
        let data = result.data.data;
        let good_list = data.data;

        const res = await http.get('v1/category/tree');
        let menuList = res.data.data;

        return { good_list, menuList, query };
    }

    componentWillMount() {
        const { good_list, menuList } = this.props;

        if (good_list) {
            this.setState({
                good_list
            });
        }
        if (menuList) {
            this.setState({
                menuList
            })
        }
    }
引用第三方插件如下


    componentDidMount() {
        const node = ReactDOM.findDOMNode(this);

        window.$ = window.jQuery = require('jquery');
        var imagesloaded = require('../../static/vendors/pofo/js/imagesloaded.pkgd.min.js');
        var isotope = require('../../static/vendors/pofo/js/isotope.pkgd.min.js');

        $(node).imagesLoaded(() => {
            $(node).isotope({
                // layoutMode: 'masonry',
                itemSelector: '.grid-item',
                percentPosition: true,
                masonry: {
                    columnWidth: '.grid-sizer'
                }
            });
            $(node).isotope('layout');
        })

        window.addEventListener('resize', () => {
            setTimeout(() => {
                $(node).isotope('layout');
            }, 500)
        }, false);

    }

4.getInitialProps 产生的 props 只作用page的当前操作 js 文件

如上 提到 如果你使用getInitialProps()方法渲染的东西在componentDidMount并没有任何作用。必须在componentWillMount中赋值才生效。

如果你 写了一个 React.js 组件 。在这个组件里 getInitialProps 你是没有办法 调用到 服务端里面的。 同时你在这个组件里面写任何 http 请求 ,他都只会当作 ajax (客户端)操作处理,解决方法可以在这个组件中使用React.children,如果回到父容器中进行渲染,这样也可以进行服务端渲染。

SPA我用了一个月多月做的商城应用,然后移植到SEO中也要话个8天,然后解决以上的遇到问题。感觉成本还是减少了不少,反正现在百度已经在捉去这个网站了。

如果大家遇到问题可以邮件 rainbowmorel@163.com