react服务器端ssr

The term "universal" is a community-coined term for building web apps that render happily on a server. You might be familiar with "isomorphic" as well but the goal of this article is not to debate names; we are going to learn how to build server-rendered React apps with Next.js.

术语“通用”是社区创建的术语,用于构建可在服务器上愉快地渲染的Web应用程序。 您可能也熟悉“同构”,但是本文的目的不是辩论名称; 我们将学习如何使用Next.js构建服务器渲染的React应用。

We've talked about building React server-side before. Today we'll discuss the topic more since it's an important one.

我们之前已经讨论过构建React服务器端 。 今天,我们将对该主题进行更多讨论,因为它很重要。

为什么要构建通用应用程序。 ( Why Build Universal Apps. )

React apps implement the virtual DOM ideology which is an abstraction of the real/original DOM. This abstraction is very useful to app performance because we can take a portion of the DOM, bind whatever data we need to bind, and insert back to the original DOM tree. This is in no way standardized and is just a concept that front-end frameworks utilize to make better user experience a true story.

React应用程序实现了虚拟DOM意识形态,它是真实/原始DOM的抽象。 这种抽象对应用程序性能非常有用,因为我们可以获取DOM的一部分,绑定需要绑定的所有数据,然后再插入回到原始DOM树中。 这绝不是标准化的,而只是前端框架用来使更好的用户体验真实故事的概念。

Just as every great thing comes with a price, Virtual DOM poses a problem. The original DOM which was received based on some information provided by the server has been lost. You might be wondering why that matters -- it does and we will see how.

就像每件伟大的事情都要付出代价一样,虚拟DOM也会带来问题。 基于服务器提供的某些信息接收到的原始DOM已丢失。 您可能想知道为什么这很重要-确实如此,我们将看看如何。

Search Engines do not care about how your app is architected or whatever ideology was used so as to adjust and fetch the right content. Their bots are not as smart as using your apps like a real user would. All they care about is that once they send their spiders to crawl and index your site, whatever the server provides on the first request is what gets indexed. That is bad news because at that time your app lacks the actual content on the server. This is what the spider takes home from your website:

搜索引擎不关心您的应用程序的结构或使用何种意识形态来调整和获取正确的内容。 他们的机器人并不像真正的用户那样聪明地使用您的应用程序。 他们所关心的是,一旦他们发送了Spider来对您的网站进行爬网并编制索引,那么服务器在第一个请求上提供的内容就是被索引的内容。 这是个坏消息,因为那时您的应用程序缺少服务器上的实际内容。 这是蜘蛛从您的网站带回家的东西:

react操作session react ssh_react操作session

Preview

预习

Source

资源

Things even get worst when you try to share your app on a social media platform like Facebook or Twitter. You are going to end up with an unexpected behavior because your actual content is not loaded on the server, it's just the entry point (probably the content of some index.html) that is.

当您尝试在Facebook或Twitter等社交媒体平台上共享应用程序时,事情甚至变得更糟。 由于您的实际内容未加载到服务器上,因此最终将导致意外的行为,它只是入口点(可能是某些index.html的内容)。

How do we tackle these problems?

我们如何解决这些问题?

与Next.js通用 ( Go Universal with Next.js )

Universal apps are architected in such manner that your app renders both on the client and the server. In React's case, the virtual DOM is eventually dropped on the server as well as using some mechanisms that might give you headache if you don't choose the right tool.

通用应用程序的构建方式使您的应用程序既可以在客户端又可以在服务器上呈现。 在React的情况下,虚拟DOM最终会被丢弃到服务器上,并且会使用某些机制,如果您选择的工具不正确,可能会让您头疼。

I have worked with few solutions but Next.js it is very promising. Next is inspired by the 7 Principles of Rich Applications. The idea is to have a good experience while using web app as well as building it. The experience should feel natural.

我使用过很少的解决方案,但是Next.js的前景很好。 接下来是“丰富应用程序的7条原则”的启发。 这个想法是要在使用和构建Web应用程序时获得良好的体验。 经验应该很自然。

Next offers more out of the box:

Next提供了更多开箱即用的功能:

  • No configuration or additional setup
  • Easy component and global style with Glamor 魅力十足,组件简单,整体风格时尚
  • Automatic transpilation and bundling (with webpack and babel)
  • Hot code reloading
  • Static file serving. ./static/ is mapped to /static/
  • Route prefetching. Coming soon 路由预取。 快来了

演示:巴克莱英超联赛表 ( DEMO: Barclays Premier League Table )

Let's do something fun with Next.js together. We will use the Football Data API to build a simple small app that shows the Barclays Premier League ranking table. If that sounds like fun to you, then let's get started.

让我们一起使用Next.js做一些有趣的事情。 我们将使用Football Data API构建一个简单的小型应用程序,以显示巴克莱英超排名表。 如果您觉得这很有趣,那么就让我们开始吧。

(Prerequisites)

Developers hate long stories when it comes to setting up a new project. Worry not, Next.js is just an npm package and all you need do is install locally and start building your app:

在建立新项目时,开发人员讨厌冗长的故事。 不用担心,Next.js只是一个npm软件包,您所需要做的就是在本地安装并开始构建您的应用程序:

# Start a new project
npm init
# Install Next.js
npm install next --save

Once the installation is completed, you can update the start script to run next:

安装完成后,您可以更新start脚本以next运行:

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

The Next installation installs React as well so no need to do that again. All you need to do now to put a server-rendered web page out there is to create a pages directory in the root of the app and add an index.js file:

Next安装也会安装React,因此无需再次执行。 现在要将服务器渲染的网页放到外面的所有操作是在应用程序的根目录中创建一个pages目录,并添加index.js文件:

// ./pages/index.js

// Import React
import React from 'react'

// Export an anonymous arrow function
// which returns the template
export default () => (
  <h1>This is just so easy!</h1>
)

Now run the following command to see your app running at localhost:3000:

现在运行以下命令以查看您的应用程序在localhost:3000上运行:

# Start your app
npm start

react操作session react ssh_java_02

Preview

预习

Source

资源

I bet this was easier than you even expected. You have a running app in about 5 minutes that is server-rendered. We are making history!

我敢打赌这比您甚至预期的要容易。 您在大约5分钟内就拥有一个正在运行的应用,该应用是通过服务器呈现的。 我们正在创造历史!

(Page Head)

We can add a head section in the page so as to define global styles and set meta details:

我们可以在页面中添加一个head部分,以便定义全局样式并设置meta详细信息:

// ./pages/index.js
import React from 'react'
// Import the Head Component
import Head from 'next/head'

export default () => (
  <div>
    <Head>
        <title>League Table</title>
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
        <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
    </Head>
    <h1>This is just so easy!</h1>
  </div>
)

The Head component which is provided by Next is used to attach head to the DOM of a page. We just wrap the supposed head DOM content in Head Next component.

Next提供的Head组件用于将head附加到页面的DOM。 我们只是将假定的head DOM内容包装在Head Next组件中。

Ajax请求 ( Ajax Requests )

Next provides a getInitialProps which can be used with async (optional) which assists in performing async operations. You can use the await keyword to handle operations that are deferred. See the following example:

Next提供了一个getInitialProps ,可与async (可选)一起使用,以帮助执行异步操作。 您可以使用await关键字来处理延迟的操作。 请参见以下示例:

import React from 'react'
import Head from 'next/head'
import axios from 'axios';

export default class extends React.Component {
    // Async operation with getInitialProps
    static async getInitialProps () {
        // res is assigned the response once the axios
        // async get is completed
        const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
        // Return properties
        return {data: res.data}
      }
 }

We are using the axios library to carry out HTTP requests. The requests are asynchronous so we need a way to catch the response in the future when it is available. With async...await, we can actual handle the async request without having to use callbacks or chain promises.

我们正在使用axios库执行HTTP请求。 这些请求是异步的,因此我们需要一种在将来可用时捕获响应的方法。 使用async...await ,我们可以实际处理异步请求,而不必使用回调或链式承诺。

We pass the value to props by returning an object which properties can be accessed from the props like this.props.data:

我们通过返回一个对象来将值传递给props ,该对象可以从this.props.data这样的props访问属性:

import React from 'react'
import Head from 'next/head'
import axios from 'axios';

export default class extends React.Component {
  static async getInitialProps () {
    const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
    return {data: res.data}
  }
  render () {
    return (
      <div>
        <Head>
            <title>League Table</title>
            <meta name="viewport" content="initial-scale=1.0, width=device-width" />
            <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
        </Head>
        <div className="pure-g">
            <div className="pure-u-1-3"></div>
            <div className="pure-u-1-3">
              <h1>Barclays Premier League</h1>
              <table className="pure-table">
                <thead>
                  <tr>
                    <th>Position</th>
                    <th>Team</th>
                    <th>P</th>
                    <th>GL</th>
                    <th>W</th>
                    <th>D</th>
                    <th>L</th>
                  </tr>
                </thead>
                <tbody>
                {this.props.data.standing.map((standing, i) => {
                  const oddOrNot = i % 2 == 1 ? "pure-table-odd" : "";
                  return (
                      <tr key={i} className={oddOrNot}>
                        <td>{standing.position}</td>
                        <td><img className="pure-img logo" src={standing.crestURI}/></td>
                        <td>{standing.points}</td>
                        <td>{standing.goals}</td>
                        <td>{standing.wins}</td>
                        <td>{standing.draws}</td>
                        <td>{standing.losses}</td>
                      </tr>
                    );
                })}
                </tbody>
              </table>
            </div>
            <div className="pure-u-1-3"></div>
        </div>
      </div>
    );
  }
}

We can now bind the data to the rendered template by iterating over the standing property on the data and printing each of the standings. The class names are as a result of the Pure CSS style included in the head which is a very simple CSS library to get you started.

现在,我们可以通过遍历数据上的standing属性并打印每个stant,将数据绑定到渲染的模板。 类名是由于头部包含了Pure CSS样式而导致的,这是一个非常简单CSS库,可以帮助您入门。

(Routing)

You might not be aware but we already have routes in our application. Next does not require any additional configuration to setup routing. You just keep adding routes as pages to the pages directory. Let's create another route to show more details about a team:

您可能不知道,但是我们的应用程序中已经有路由。 Next不需要任何其他配置即可设置路由。 您只需将路由作为页面添加到pages目录。 让我们创建另一条路线来显示有关团队的更多详细信息:

// ./pages/details.js
import React from 'react'
export default () => (
  <p>Coming soon. . .!</p>
)

You can navigate from one route to another using the Link component from Next:

您可以使用“下一步”中的“ Link组件从一条路线导航到另一条路线:

// ./pages/details.js
import React from 'react'

// Import Link from next
import Link from 'next/link'

export default () => (
  <div>
      <p>Coming soon. . .!</p>
      <Link href="/">Go Home</Link>
  </div>
)

It's time to update the details page to display more information about a given team. The team's position will be passed as a query parameter, id, to the page. The id will be used to then filter the team's information:

现在该更新详细信息页面以显示有关给定团队的更多信息。 团队的位置将作为查询参数id传递到页面。 该id将用于过滤团队的信息:

import React from 'react'
import Head from 'next/head'
import Link from 'next/link'
import axios from 'axios';

export default class extends React.Component {
    static async getInitialProps ({query}) {
        // Get id from query
        const id = query.id;
        if(!process.browser) {
            // Still on the server so make a request
            const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable')
            return {
                data: res.data,
                // Filter and return data based on query
                standing: res.data.standing.filter(s => s.position == id)
            }
        } else {
            // Not on the server just navigating so use
            // the cache
            const bplData = JSON.parse(sessionStorage.getItem('bpl'));
            // Filter and return data based on query
            return {standing: bplData.standing.filter(s => s.position == id)}
        }
    }

    componentDidMount () {
        // Cache data in localStorage if
        // not already cached
        if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data))
    }

    // . . . render method truncated
 }

This page displays dynamic content based on the query parameter. We receive the query from the getInitialProps and then use the id value to filter the data array from a given team based on their position on the table.

此页面基于查询参数显示动态内容。 我们从getInitialProps接收query ,然后使用id值根据给定团队在表上的位置过滤数据数组。

The most interesting aspect of the above logic is that we are not requesting data more than once in the app's lifecycle. Once the server renders, we fetch the data and cache it with sessionStorage. Subsequent navigation will be based on the cached data. sessionStorage is preferred to localStorage because the data will not persist once the current window exits.

上述逻辑最有趣的方面是,我们在应用程序的生命周期中对数据的请求不止一次。 服务器渲染后,我们将获取数据并使用sessionStorage对其进行缓存。 随后的导航将基于缓存的数据。 sessionStorage优于localStorage因为一旦当前窗口退出,数据将不会持久保存。

The storage is done using componentDidMount and not getInitialProps because when getInitialProps is called, the browser is not prepared so sessionStorage is not known. For this reason, we need to wait for the browser and a good way to catch that is with componentDidMount React lifecycle method. getInitialProps is said to be isomorphic.

存储使用进行componentDidMount而不是getInitialProps因为当getInitialProps之称,是没有准备的浏览器,这样sessionStorage不得而知。 出于这个原因,我们需要等待浏览器,并通过componentDidMount React生命周期方法找到一种很好的捕获方法。 据说getInitialProps是同构的。

Let's now render to the browser:

现在让我们渲染到浏览器:

// . . . truncated

export default class extends React.Component {
    // . . . truncated
    render() {

        const detailStyle = {
            ul: {
                marginTop: '100px'
            }
        }

        return  (
             <div>
                <Head>
                    <title>League Table</title>
                    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
                    <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
                </Head>

                <div className="pure-g">
                    <div className="pure-u-8-24"></div>
                    <div className="pure-u-4-24">
                        <h2>{this.props.standing[0].teamName}</h2>
                        <img src={this.props.standing[0].crestURI} className="pure-img"/>
                        <h3>Points: {this.props.standing[0].points}</h3>
                    </div>
                    <div className="pure-u-12-24">
                        <ul style={detailStyle.ul}>
                            <li><strong>Goals</strong>: {this.props.standing[0].goals}</li>
                            <li><strong>Wins</strong>: {this.props.standing[0].wins}</li>
                            <li><strong>Losses</strong>: {this.props.standing[0].losses}</li>
                            <li><strong>Draws</strong>: {this.props.standing[0].draws}</li>
                            <li><strong>Goals Against</strong>: {this.props.standing[0].goalsAgainst}</li>
                            <li><strong>Goal Difference</strong>: {this.props.standing[0].goalDifference}</li>
                            <li><strong>Played</strong>: {this.props.standing[0].playedGames}</li>
                        </ul>
                        <Link href="/">Home</Link>
                    </div>
                </div>
             </div>
            )
    }
}

Our index page does not implement this performance related feature in details page. We can update the component accordingly:

我们的index页面未在details页面中实现此与性能相关的功能。 我们可以相应地更新组件:

// . . . imports truncated

export default class extends React.Component {
  static async getInitialProps () {
    if(!process.browser) {
      const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable')
      return {data: res.data}
    } else {
      return {data: JSON.parse(sessionStorage.getItem('bpl'))}
    }
  }

  componentDidMount () {
    if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data))
  }
  // . . . render method truncated
}

The index template should also be updated to include links that point to each team's details page:

index模板也应该更新为包括指向每个团队的详细信息页面的链接:

// . . . some imports truncated
import Link from 'next/link'

export default class extends React.Component {
 // . . . truncated
  render () {
    const logoStyle = {
      width: '30px'
    }

    return (
      <div>
        <Head>
            <title>League Table</title>
            <meta name="viewport" content="initial-scale=1.0, width=device-width" />
            <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
        </Head>
        <div className="pure-g">
            <div className="pure-u-1-3"></div>
            <div className="pure-u-1-3">
              <h1>Barclays Premier League</h1>

              <table className="pure-table">
                <thead>
                  <tr>
                    <th>Position</th>
                    <th>Team</th>
                    <th>P</th>
                    <th>GL</th>
                    <th>W</th>
                    <th>D</th>
                    <th>L</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody>
                {this.props.data.standing.map((standing, i) => {
                  const oddOrNot = i % 2 == 1 ? "pure-table-odd" : "";
                  return (
                      <tr key={i} className={oddOrNot}>
                        <td>{standing.position}</td>
                        <td><img className="pure-img logo" src={standing.crestURI} style={logoStyle}/></td>
                        <td>{standing.points}</td>
                        <td>{standing.goals}</td>
                        <td>{standing.wins}</td>
                        <td>{standing.draws}</td>
                        <td>{standing.losses}</td>
                        <td><Link href={`/details?id=${standing.position}`}>More...</Link></td>
                      </tr>
                    );
                })}
                </tbody>
              </table>
            </div>
            <div className="pure-u-1-3"></div>
        </div>
      </div>
    );
  }
}

错误页面 ( Error Pages )

You can define custom error pages by creating a _error.js page to handle 4** and 5** errors. Next already displays errors so if that is fine with you, then no need to create the new error page.

您可以通过创建_error.js页面来处理4 **和5 **错误来定义自定义错误页面。 Next已经显示了错误,因此,如果您满意,则无需创建新的错误页面。

// ./pages/_error.js
import React from 'react'

export default class Error extends React.Component {
  static getInitialProps ({ res, xhr }) {
    const statusCode = res ? res.statusCode : (xhr ? xhr.status : null)
    return { statusCode }
  }

  render () {
    return (
      <p>{
        this.props.statusCode
        ? `An error ${this.props.statusCode} occurred on server`
        : 'An error occurred on client'
      }</p>
    )
  }
}

This is what a 404 looks like with the default error page:

这是带有默认错误页面的404外观:

react操作session react ssh_vue_03

...but with the custom error page, you get:

...但是使用自定义错误页面,您得到:

react操作session react ssh_java_04

部署中 ( Deploying )

Next is always production ready at all time. Let's deploy to now to see how simple deploying is. First install now:

接下来总是随时准备生产。 让我们部署到现在 ,看看部署有多简单。 now首先安装:

npm install -g now

You need to get an account by downloading the desktop app

您需要通过下载桌面应用程序来获取帐户

Update the scripts in package.json to include a build command for our app:

更新package.json的脚本,以包含适用于我们应用程序的build命令:

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

Now run the build and start command to prepare for deploying:

现在运行build and start命令以准备部署:

# Build
npm run build
# Start
npm start

You can just deploy by running now:

您可以通过运行刚刚部署now :

now

##Additional Resources

##其他资源

  • Zeit Blog Zeit博客
  • Styles and Layout 样式和布局
  • Adding Redux 添加Redux
  • Next Road Map 下一步路线图

翻译自: https://scotch.io/tutorials/react-universal-with-next-js-server-side-react

react服务器端ssr