Use next. Js for server-side rendering

Next. Js Demo Github address

What is server-side rendering

Server-side rendering, in which the page is rendered and generated by the server, and the rendered page is returned to the client. While client rendering is the page generation and data rendering process is done on the client (browser or APP).

Server-side rendering techniques before front and back end separation are: PHP, ASP, JSP and other methods, separated front-end SPA (single page application) rendering has independent routing and page rendering (React, Vue and Angular, etc.), and the biggest problem of SPA is not SEO friendly, when the project has SEO requirements, SPA is not a good choice.

The main advantages of server-side rendering (SSR) over traditional SPA (single-page Application) are:

1, better SEO, thanks to search engine crawler crawler tools can directly view fully rendered pages.

2. Faster time-to-content, especially for slow network conditions or slow-running devices. SPA (single page application) project is too large, the first screen loading is too slow and other problems can be solved.

Disadvantages of server-side rendering (SSR) :

1. High cost of development learning.

2. Increase the burden on the server.

For more information on server-side rendering, see the

The React server rendering framework is recommended for next

Next, js features

Next. Js is a lightweight React server rendering application framework.

Next. Js features:

  • Client Routing (file-system Routing)
  • Automatic Code Splitting makes pages load faster
  • Default Server Side Rendering mode
  • Development environment support for Hot Module Replacement
  • Support for JSX and ES6 syntax
  • Support the typescript
  • Can run on Express and other Node.js HTTP servers
  • You can customize your own Babel and Webpack configurations

How to build a next. Js project

Those familiar with the React and Node framework Express stacks will probably learn next. Js much faster. Note: next. Js only supports React16

Build a next. Js project

Create the next. Js project

// Go to the next project root directory CD learnnextjs-demo/ // initialize the project, add package.json file, NPM init -y // install dependencies yarn add react react-dom next // create pages folder, next.js project must contain pages folder, Error (Couldn't find a 'pages' directory. Please create one under the project root) mkdir pagesCopy the code

After completing the above operations, you can directly run YARN Next to start the next project. You can add the following to the scripts field in package.json:

{
    "scripts": {
        "dev": "next",
        "build": "next build",
        "start": "next start"
    }
}
Copy the code

After starting the project, type http://localhost:3000 in the browser, a 404 page will appear. This is because we did not add index.js to the pages folder. If you want to use another port, You can run NPM run dev — -p < Set port number >.

Create the page

Next. Js generates the page from the server and returns it to the front end for presentation. By default, Next. Js takes the page from the pages directory for rendering and returns it to the front end for display, and takes pages/index.js as the home page of the project for display by default.

In the Pages directory, create index.js (project home page)

// index.js export default () => <div>Welcome to next.js! </div>Copy the code

Next. Js supports webPack hot deployment by default. After adding index.js, you can see that the content of http://localhost:3000/ is automatically updated.

Creating multiple pages

The purpose of using Next. Js is to build non-SPA multi-page applications. Pages in the Pages folder of Next

Create the About and Post pages in the Pages directory

// about.js
export default () => (
    <div>
        <p>This is the about page</p>
    </div>
)
Copy the code
// post.js
export default () => (
  <div>
    <p>This is the post page</p>
  </div>
)
Copy the code

In your browser’s address bar enter http://localhost:3000/about or http://localhost:3000/post you can see the corresponding the about and post page.

In the next. Js project, only page-level components are placed in the Pages folder, and other sub-components can be placed in other folders, such as components.

Next, js routing

In the next. Js project, client-side routing is controlled through the Link API of next.

We create the components folder in the root directory of the project and create Header and Layout components in this directory. Header components are used for the page navigation Header and Layout components are used for the page Layout. The specific code is as follows:

// Header.js
import Link from 'next/link'

const linkStyle = {
    marginRight: 15
}

const Header = () => (
    <div>
        <Link href="/">
            <a style={linkStyle}>Home</a>
        </Link>
        <Link href="/about">
            <a style={linkStyle}>About</a>
        </Link>
    </div>
)

export default Header
Copy the code
// Layout.js
import Header from './Header'

const layoutStyle = {
    margin: 20,
    padding: 20,
    border: '1px solid #DDD'
}

const Layout = (props) => (
  <div style={layoutStyle}>
        <Header />
        {props.children}
  </div>
)

export default Layout
Copy the code

Then modify the pages under the about of js, post, js and index. The js file, just wrote of the Layout of public components used for page Layout, after modify specific code is as follows:

// about.js import Layout from '.. /components/Layout' export default () => ( <Layout> <p>This is the about page</p> </Layout> )Copy the code
// post.js import Layout from '.. /components/Layout' export default (props) => ( <Layout> <h1>{props.url.query.title}</h1> <p>This is the blog post content.</p> </Layout> )Copy the code
// index.js import Layout from '.. /components/Layout' import Link from 'next/link' const PostLink = (props) => ( <li> <Link as={`/p/${props.id}`} href={`/post? title=${props.title}`}> <a>{props.title}</a> </Link> </li> ) export default () => ( <Layout> <h1>My Blog</h1> <ul> <PostLink id="hello-nextjs" title="Hello Next.js"/> <PostLink id="learn-nextjs" title="Learn Next.js is awesome"/> <PostLink id="deploy-nextjs" title="Deploy apps with Zeit"/> </ul> </Layout> )Copy the code

Note: You can use to make linking and preloading happen simultaneously in the background for optimal page performance.

In post.js, we can get the title parameter from the href attribute of the PostLink component using the as attribute of the Link component, which is used for routing overrides. When do not use this property, after click on the link to your browser’s address bar display is http://localhost:3000/post? Title = Hello % 20 next. The js, and used as attribute, the browser address bar shows the http://localhost:3000/p/hello-nextjs, we can see from here used routing covering the biggest advantage is that we can customize the browser display routing address, Can give way by the address display more concise, more beautiful. This page could not be found. This page could not be found.

The reason for this problem is that when the AS attribute is used on the Link component, the browser displays the value of the AS attribute and goes through the client route, while the server maps the value of the href attribute and goes through the server route. Therefore, 404 will appear after refreshing the page. To solve this problem, we need to do a route resolution process in the server route.

Next, we will write the code needed by the server side, so that the server side also supports routing coverage. We need to handle the routing separately. On the server side, I use The Node Web application development framework Express

Install the Express dependency package

yarn add express
Copy the code

After installing Express, create a server.js file in the root directory of your project

// server.js const express = require('express') const next = require('next') const dev = process.env.NODE_ENV ! == 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare() .then(() => { const Server.get ('/p/:id', (req, Res) => {const actualPage = '/post' // route parameter const queryParams = {title: Req.params. id} app.render(req, res, actualPage, queryParams)}) Res) => {return handle(req, res)}) server.listen(3000, (err) => { if (err) throw err console.log('> Ready on http://localhost:3000') }) }) .catch((ex) => { console.error(ex.stack) process.exit(1) })Copy the code

After writing and playing server.js, instead of using next’s default startup method, we used our own server.js as the startup entry and modified package.json file:

{
    "scripts": {
        "dev": "node server.js"
    },
}
Copy the code

Modify the server.js file and restart the project using YARN dev.

In the server.js file, we do route resolution for post page, so as to realize the server and browser request route access to the same page. In the Express process of post page route, we use the parameter actualPage, set this parameter to POST, and at the same time, with the parameter attached to request this page. At this point, when we refresh the page, 404 will not appear.

Request the interface to get the data

With the next-js route covered, the next step is to implement how to request page initialization data in Next-.js.

We need to introduce a plugin isomorphic-Unfetch that supports sending fetch requests on both the client and server side, of course you can also use other tools like AXIos.

yarn add isomorphic-unfetch
Copy the code

In next.js, we use the getInitialProps property in the page-level component to get the data and modify the index.js and post.js files in the Pages folder as follows:

// index.js import Layout from '.. /components/Layout.js' import Link from 'next/link' import fetch from 'isomorphic-unfetch' const Index = (props) => ( <Layout> <h1>Batman TV Shows</h1> <ul> {props.shows.map(({show}) => ( <li key={show.id}> <Link as={`/p/${show.id}`} href={`/post? id=${show.id}`}> <a>{show.name}</a> </Link> </li> ))} </ul> <style jsx>{` h1, a { font-family: "Arial"; } ul { padding: 0; } li { list-style: none; margin: 5px 0; } a { text-decoration: none; color: blue; } a:hover {opacity: 0.6; } `}</style> </Layout> ) Index.getInitialProps = async function() { const res = await fetch('https://api.tvmaze.com/search/shows?q=batman') const data = await res.json() console.log(`Show data fetched. Count: ${data.length}`) return { shows: data } } export default IndexCopy the code
// post.js import Layout from '.. /components/Layout' import fetch from 'isomorphic-unfetch' const Post = (props) => ( <Layout> <h1>{props.show.name}</h1>  <p>{props.show.summary.replace(/<[/]?p>/g, '')}</p> <img src={props.show.image.medium}/> </Layout> ) Post.getInitialProps = async function(context) { console.log(`context: ${context}`); const {id} = context.query const res = await fetch(`https://api.tvmaze.com/shows/${id}`) const show = await res.json() console.log(`Fetch show: ${show.name}`) return {show} } export default PostCopy the code

Then modify the POST route parameters in the server.js file

Server.get ('/p/:id', (req, res) => {console.log('params', req.params); Const actualPage = '/post' // route parameter const queryParams = {id: req.params.id } app.render(req, res, actualPage, queryParams) })Copy the code

Restart the project and based on the above modified code we can draw the following conclusions:

  • We use an asynchronous method, getInitialProps, to load the data when the page is rendered. It gets regular JS objects asynchronously and binds them to props
  • When the service renders, getInitialProps will serialize the data, just like json.stringify. So make sure getInitialProps returns a regular JS object, not a Date, Map, or Set type.
  • When the page is first loaded, getInitialProps is executed only once on the server. GetInitialProps is used only when route switches are performed (such as Link component jumps or route customization jumps).
  • When the page is initially loaded, getInitialProps is only loaded on the server. The client only executes getInitialProps when a route jump (Link component jump or API method jump) occurs.

Note: getInitialProps cannot be used in child components. It can only be used in pages pages.

Support for embedded styles

Styled – JSX is used to generate scoped CSS

export default () => <div> Hello world <p>scoped! </p> <style jsx>{` p { color: blue; } div { background: red; } @media (max-width: 600px) { div { background: blue; } } `}</style> <style global jsx>{` body { background: black; } `}</style> </div>Copy the code

Styled – JSX style is not applied to children. If you want the style to apply to children, you can add the attribute Global to the Styled – JSX tag

Static file services (such as images, styles, etc.)

Creating a new folder in the root directory is called static. Code can use /static/ to import related static resources.

export default () => <img src="/static/my-image.png" alt="my image" />
Copy the code

Note: Do not customize the name of the static folder, just call it static, because only that name next.js will count as a static resource.

Generate Head and do SEO optimization

A large part of the reason we use next.js for server-side rendering is for SEO purposes, so generating Head was necessary for us.

We create the Head component in the Components folder as follows:

Import Head from 'next/ Head 'export default () => <div> <Head> <title> <meta charset=" utF-8 "/> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" /> <meta name="mobile-agent" content="format=html5;" /> <meta name="format-detection" content="telephone=no" /> <meta name="theme-color" content="black" /> <meta name="mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta <meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-title" content=" page title" /> <meta name="description" /> <meta name="description" /> <meta name="author" content="better" /> <meta name="copyright" content=" copyright" /> <meta <meta name="360-site-verification" content="360" /> <link href="http://www.wanshifu.com/assets/images/favicon.ico" rel="shortcut icon" /> </Head> </div>Copy the code

After writing the Head component, we also need to add the Head component to our Header component as follows:

// Header.js import Link from 'next/link' import Head from '.. /components/Head'; const linkStyle = { marginRight: 15 } const Header = () => ( <div> <Head /> <Link href="/"> <a style={linkStyle}>Home</a> </Link> <Link href="/about"> <a  style={linkStyle}>About</a> </Link> </div> ) export default HeaderCopy the code

We can then go to the Elements bar in the browser consoleThe head tagsSee the variety we added inmetaLabel, as shown in figure:

No custom buildHead.The head tagsThe default contains only meta tags in character encoding format<meta charset="utf-8" class="next-head">As shown in figure:

The custom<App>,<Document>,<Error>

We can rewrite the App module to control page initialization by creating a new _app.js file in the Pages folder:

// _app.js import App, {Container} from 'next/app' import React from 'react' import Head from '.. /components/Head'; export default class MyApp extends App { static async getInitialProps ({ Component, router, ctx }) { let pageProps = {} if (Component.getInitialProps) { pageProps = await Component.getInitialProps(ctx) } return {pageProps} } render () { const {Component, pageProps} = this.props return <Container> <Head /> <Component {... pageProps} /> </Container> } }Copy the code

We initialize the page component by moving the Head component from the Layout component to the rewrite _app.js.

Instead of rewriting, we can rewrite alpha and beta, specifically, See [next. Js official documentation] (https://nextjs.org/docs/#custom-document) or [next. Js] Chinese documents (http://nextjs.frontendx.cn/docs/#%E8%87%AA%E5%A E%9A%E4%B9%89%3Cdocument%3E)

Custom Configuration

In the next. Js project, next does a lot of default configuration for us. If we want to customize the advanced configuration of next.

For example, if we want to use sass in a project, the code looks like this:

// next.config.js const withSass = require('@zeit/next-sass') module.exports = withSass({ cssModules: GenerateEtags: false, // Disable eTag generation})Copy the code

Note: next.config.js is a Node.js module, not a JSON file, and can be used for next to start the service in its construction phase, but not on the browser side.

Project deployment

Next. Js project deployment, need a Node server, we can choose node service application framework Expres or KOA, this article uses express.

In the server.js file, we pass const dev = process.env.node_env! == ‘production’ to distinguish between the development environment and production environment, modify the scripts configuration in package.json file as follows:

{
    "scripts": {
        "build": "next build",
        "dev": "NODE_ENV=dev node server.js",
        "start": "NODE_ENV=production node server.js"
    },
}
Copy the code

First, package the project

yarn build
Copy the code

Start the development environment

yarn dev
Copy the code

The deployment of online

yarn start
Copy the code

Summary: To deploy a production environment with next.js, you must package the build with the build command and then deploy it with the start command.

Use next. Js to quickly start rendering the React server