Using Next.js to build React server rendering application

  next.js, react.js

Introduction to next.js

Recently, I am learning React.js. React officials recommend using the next.js framework as a website for building server-side rendering, so let’s study the use of next.js today.

Next.js, as a lightweight application framework, is mainly used to build static websites and back-end rendering websites.

Frame features

  • Use backend rendering
  • Automatic code splitting for Faster Web Page Loading
  • Concise Front End Routing Implementation
  • Build with webpack and support Hot Module Replacement.
  • Can interface with mainstream Node servers (such as express)
  • Configurations for babel and webpack can be customized.

Use method

Create project and initialize

mkdir server-rendered-website
cd server-rendered-website
npm init -y

Js

Npm or yarn is used for installation, because React application is created, react and react-dom are installed at the same time.

npm:
npm install --save react react-dom next
yarn:
yarn add react react-dom next

Add a folder under the project root directorypages(Be sure to name it pages, which is the mandatory convention for next, otherwise the page will not be found), and then add script to the package.json file to start the project:

"scripts": {
"dev": "next"
}

The following figureimage

Create view

Create an index.js file under the pages folder. the contents of the file are as follows:

const Index = () => (
<div>
<p>Hello next.js</p>
</div>
)

export default Index

run

npm run next

Open in browserhttp://localhost:3000/, the webpage displays as follows:
image

This completes the simplest next website.

Front-end routing

Next.js front-end routing is very simple to use. let’s add a page called about, which reads as follows:

const About = () => (
 <div>
 <p>This is About page</p>
 </div>
 )
 
 export default About;

When we request in the browserhttps://localhost:3000/about, you can see the corresponding content displayed on the page. (= = It should be noted here that the path of the request url must be the same as the case of the file name of page to access it. If you visit localhost:3000/About, you cannot find the about page. ==)

We can use the traditional A tag to jump between pages, but every time we jump, we need to go to the server to request it. In order to increase the access speed of the page, it is recommended to use the front-end routing mechanism of next.js to jump.

Next.js uses next/link to jump between pages. The usage is as follows:

import Link from 'next/link'

const Index = () => (
<div>
<Link href="/about">
<a>About Page</a>
</Link>
<p>Hello next.js</p>
</div>
)

export default Index

In this way, clicking on the AboutPage link of index page can jump to about page, and clicking on the browser’s return button also jumps through the front-end route.Official documents say that there will be no network request using front-end route jump, but there will actually be a request for about.js file, and this request comes from the script tag inserted dynamically in the page. However, about.js will only request once, and access will not be requested after that. after all, the same script tag will not be inserted repeatedly.However, compared with back-end routing, it still greatly saves the number of requests and network traffic. The request comparison between front-end route and back-end route is as follows:

Front-end routing:

image

Backend routing:

image

The Link tag supports any react component as its child element. It is not necessary to use the A tag as long as the child element can respond to onClick events., just like the following:

<Link href="/about">
<div>Go about page</div>
</Link>

Link tags do not support adding attributes such as style and className. if you want to add styles to links, you need to add them to child elements.

<Link href="/about">
 <a className="about-link" style={{color:'#ff0000'}}>Go about page</a>
 </Link>

Layout

The so-called layout is to add the same header, footer, navbar and other common parts to different pages without writing duplicate codes. Js can implement layout by sharing some components.

Let’s add a public header component and place it under the components folder in the root directory (page level components in pages and public components in components):

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;

Then the header component is introduced into the index and about pages, thus realizing the header of the common layout:

import Header from '../components/Header';

const Index = () => (
<div>
<Header />
<p>Hello next.js</p>
</div>
)

export default Index;

If you want to add footer, you can also follow the header method.
In addition to introducing multiple header and footer components, we can implement a whole Layout component to avoid the trouble of introducing multiple components, and also add a Layout.js file to components, which is as follows:

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

In this way, we only need to introduce Layout components in the page to achieve the purpose of layout:

import Layout from '../components/Layout';
 
 const Index = () => (
 <Layout>
 <p>Hello next.js</p>
 </Layout>
 )
 
 export default Index;

Inter-page value transfer

Through the url parameter (query string)

The method of transferring values between pages in next can also be implemented with url parameters like traditional web pages. Let’s make a simple blog application:

First, replace the content of index.js with the following to show the blog list:

import Link from 'next/link';
 import Layout from '../components/Layout';
 
 const PostLink = (props) => (
 <li>
 <Link href={`/post?  title=${props.title}`}>
 <a>{props.title}</a>
 </Link>
 </li>
 );
 
 export default () => (
 <Layout>
 <h1>My Blog</h1>
 <ul>
 <PostLink title="Hello next.js" />
 <PostLink title="next.js is awesome" />
 <PostLink title="Deploy apps with Zeit" />
 </ul>
 </Layout>
 );

By adding to Link’s hreftitleParameters can be passed.

Now let’s add the details page of the blog.post.js

import { withRouter } from 'next/router';
 import Layout from '../components/Layout';
 
 const Post = withRouter((props) => (
 <Layout>
 <h1>{props.router.query.title}</h1>
 <p>This is the blog post content.</p>
 </Layout>
 ));
 
 export default Post;

The above code injects next’s router as a prop into component through withRouter to access url parameters.

After operation, the display is as follows:

List page

image

Click to enter the details page:

image

Using query string can transfer values between pages, but it will cause the url of the page to be less concise and beautiful, especially when there are more values to transfer. So next.js provides Route Masking as a feature to beautify routes.

Route Masking

The official name of this feature is Route Masking, and no official Chinese name was found, so it was translated literally into route disguise. The so-called route disguise means that the url displayed in the browser address bar is different from the url actually visited by the page. The method to realize route camouflage is also very simple, throughLinkComponent ofasThe property tells the browser href to display the url accordingly. The index.js code is modified as follows:

import Link from 'next/link';
 import Layout from '../components/Layout';
 
 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="next.js is awesome" />
 <PostLink id="deploy-nextjs" title="Deploy apps with Zeit" />
 </ul>
 </Layout>
 );

Running results:

image

The url of the browser has been revised as scheduled, so it looks much more comfortable. Moreover, the route camouflage is also very friendly to history. clicking back and moving forward can still open the details page normally. However, if you refresh the details page and report 404 errors, as shown in the figure:

image

This is because refreshing the page will directly request the url from the server, and the server does not have a page corresponding to the url, so an error is reported. In order to solve this problem, the custom service interface (custom server API) provided by next.js is needed.

Custom service interface

Before customizing the service interface, we need to create a server and install Express:

npm install --save express

Create a server.js file under the root directory of the project, with the following contents:

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 = express();
 server.get('*', (req, 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);
 });

Then change dev script in package.json to:

"scripts": {
"dev": "node server.js"
}

runnpm run devAfter the project can run as before, then we need to add a route to match the disguised url with the real url and add in server.js:

......
const server = express();
server.get('/p/:id', (req, res) => {
const actualPage = '/post';
const queryParams = { title: req.params.id };
app.render(req, res, actualPage, queryParams);
});
......

In this way, we map the disguised url to the real url, and the query parameter is also mapped. After restarting the project, the details page can be refreshed without error. However, there is a small problem. The front-end routing page is different from the back-end routing page title, because the back-end routing passes the id, while the front-end routing page displays title. This problem can be avoided in actual projects, because in actual projects we usually get title through id and then show it. As Demo, we steal a little lazy and directly use id as the title of the back-end routing page.

Before, our presentation data were static. Next, we realized to obtain data from remote services and present them.

Remote data acquisition

Next.js provides a standard interface for acquiring remote data:getInitialPropsThroughgetInitialPropsWe can get remote data and assign it to props of the page.getInitialPropsThat is to say, it can be used at the service end or at the front end. Next, let’s write a little Demo to show its usage. We plan to start fromTVMaze APIGet some TV program information and display it on my website. First, we installisomorphic-unfetch, which is a network request library implemented based on fetch:

npm install --save isomorphic-unfetch

Then we modify index.js as follows:

import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?  id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
);
})}
</ul>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?  q=marvel');
const data = await res.json();
return {
shows: data
}
}

export default Index;

The logic of the above code should be very clear, we are ingetInitialPropsIn this way, the props in Index can obtain the program data, and then traverse and render it into a program list.

After running the project, the page is perfectly displayed:

image

Next, let’s implement the details page. First, let’s put/p/:idThe route of is modified to:

...
 server.get('/p/:id', (req, res) => {
 const actualPage = '/post';
 const queryParams = { id: req.params.id };
 app.render(req, res, actualPage, queryParams);
 });
 ...

We use id as a parameter to get the detailed content of TV programs, and then modify the content of post.js as follows:

import fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';

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) {
const { id } = context.query;
const res = await fetch(` https://api.tvmaze.com/shows/${id}`);
const show = await res.json();
return { show };
}

export default Post;

Restart the project (the contents of server.js need to be restarted) and enter the details page from the list page. the details of the TV program have been successfully obtained and displayed:

image

Add style

So far, all the websites we have created have been quiet, so let’s add some styles to the website to make it beautiful.

For React applications, there are many ways to add styles. There are mainly two types:

  1. Use traditional CSS files (including SASS, PostCSS, etc.)
  2. Insert CSS into JS file

There are many problems in using traditional CSS files, so next.js recommends the second method. Next.js internal default usestyled-jsxThe framework inserts CSS into js files. The styles introduced in this way will not affect each other between different components, even between parent and child components.

styled-jsx

Next, let’s look at how to use styled-jsx. Replace index.js with the following:

import Link from 'next/link';
import Layout from '../components/Layout';
import fetch from 'isomorphic-unfetch';

const Index = (props) => (
<Layout>
<h1>Marvel TV Shows</h1>
<ul>
{props.shows.map(({ show }) => {
return (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?  id=${show.id}`}>
<a className="show-link">{show.name}</a>
</Link>
</li>
);
})}
</ul>
<style jsx>
{`
*{
margin:0;
padding:0;
}
h1,a{
font-family:'Arial';
}
h1{
margin-top:20px;
background-color:#EF141F;
color:#fff;
font-size:50px;
line-height:66px;
text-transform: uppercase;
text-align:center;
}
ul{
margin-top:20px;
padding:20px;
background-color:#000;
}
li{
list-style:none;
margin:5px 0;
}
a{
text-decoration:none;
color:#B4B5B4;
font-size:24px;
}
a:hover{
opacity:0.6;
}
`}
</style>
</Layout>
);

Index.getInitialProps = async function () {
const res = await fetch('https://api.tvmaze.com/search/shows?  q=marvel');
const data = await res.json();
console.log(`Show data fetched. Count: ${data.length}`);
return {
shows: data
}
}

export default Index;

Run the project, the home page becomes:
image

After adding a little style, it looks a little better than before. We found that the style of navigation bar has not changed. Because Header is an independent component, the styles between components will not affect each other. Js:

import Link from 'next/link';

const Header = () => (
<div>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<style jsx>
{`
a{
color:#EF141F;
font-size:26px;
line-height:40px;
text-decoration:none;
padding:0 10px;
text-transform:uppercase;
}
a:hover{
opacity:0.8;
}
`}
</style>
</div>
)

export default Header;

The effect is as follows:
image

Global style

Underline when we need to add some global styles, such as rest.css or mouse hovering over the a tag. at this time we just need tostyle-jsxAdded on labelglobalKey words will do, we modify Layout.js as follows:

import Header from './Header';
 
 const layoutStyle = {
 margin: 20,
 padding: 20,
 border: '1px solid #DDD'
 }
 
 const Layout = (props) => (
 <div style={layoutStyle}>
 <Header />
 {props.children}
 <style jsx global>
 {`
 a:hover{
 text-decoration:underline;
 }
 `}
 </style>
 </div>
 )
 
 export default Layout

In this way, the mouse will appear underlined when hovering over all A labels.

Js application deployment

Build

Before deployment, we first need to be able to build the project for the production environment and add script to package.json:

"build": "next build"

Next, we need to be able to start the project to serve the contents of our build and add script to package.json:

"start": "next start"

Then it is executed in sequence:

npm run build
 npm run start

The content completed by build will be generated to.nextIn the folder,npm run startAfter that, what we actually visited was.nextThe contents of the folder.

Run multiple instances

If we need to scale out (Horizontal Scale) to improve the speed of website access, we need to run multiple instances of websites. First, we modify the start script of package.json:

"start": "next start -p $PORT"

For windows systems:

"start": "next start -p %PORT%"

Then run build:npm run build, then open the two command lines and navigate to the root directory of the project to run separately:

PORT=8000 npm start
PORT=9000 npm start

Open when run is completelocalhost:8000Andlocalhost:9000Can be accessed normally:

image

Although the above method can be packaged and deployed, there is a problem that our custom service server.js is not running, resulting in 404 errors when the details page is refreshed, so we need to add custom service to app logic.

Deploy and Use Custom Services

We modified start script to read:

"start": "NODE_ENV=production node server.js"

In this way, we have solved the deployment of custom services. After restarting the project, refreshing the details page can also be accessed normally.

So far, we have learned most of the usage methods of next.js. If you have any questions, you can check the official documents of next.js or leave me a message to discuss.

Thank you for reading. In addition, I’m here to help my friends raise funds for love. I hope everyone can give some love. My friend’s mother is suffering from rectal cancer. She is currently receiving treatment in Beijing Armed Police General Hospital. Please leave a message and leave your contact information. Thank you in the future!

clipboard.png