preface

The React server renders the SSR framework Next. Js, and the Vue server renders the SSR framework nuxt.js.

Why do I need server-side rendering?

  • Reduces the first blank screen time

  • SEO: Search Engine Optimization; To put it simply, when the web page is requested, the content sent from the server can be crawled to the data by the search engine crawler. At this time, the keywords searched from the search engine are included in the content, so the information of the WEBSITE is more easily displayed in the search results

  • Client-side rendering: the appearance of front-end SPA, most of the page content in the browser side through asynchronous request to get data, and then render generation; And from the server is just a shell without content, search engines naturally climb less than things ~

  • Server-side rendering: Previous back-end MCV frameworks used templates to generate content on the server, and then the browser received the data and rendered it directly. This time the content of the web page has followed the site over, the main content does not need additional asynchronous request to obtain, the search engine crawler can climb to these non-asynchronous request data ~

  • SSR of front-end framework: mainly isomorphism of front and back end, aggregation of microservice interface, etc. Of course, just as the rendering layer is the simplest, the interface is taken care of by the backend big guy; React/Vue/Angular have frameworks that use Node.js to render data on the server. The React/Vue/Angular framework can still be used on the front end, but some data is generated on the server side


See the source at 👇 here

As you can see, there is already content when it comes from the server:

Subsequent route changes are equivalent to a single page SPA, but refreshed under a route that is rendered by the server

1. SRC directory structure

. ├ ─ ─ components │ ├ ─ ─ the header │ ├ ─ ─ the hello world - │ └ ─ ─ layout ├ ─ ─ pages │ ├ ─ ─ _app. The TSX │ ├ ─ ─ _error. The TSX │ ├ ─ ─ the about the TSX │ ├ ─ ─ the detail. The TSX │ └ ─ ─ index. The TSX ├ ─ ─ store │ ├ ─ ─ home. Ts │ └ ─ ─ but ts └ ─ ─ styles ├ ─ ─ _error. SCSS ├ ─ ─ the about the SCSS ├ ─ ─ Detail. SCSS └ ─ ─ index. SCSSCopy the code

2, NPM script

TSC needs to be installed

yarn install typescript -g
Copy the code

3, page head

Set page head

import Head from 'next/head';

const Header = (a)= > (
  <Head>
    <title>Next.js test</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
  </Head>
);
Copy the code

It can be used where the page head needs to be changed, for example, /about needs to be changed to about. Incremental modification: If no, add it; if yes, modify it

// ...
<Head>
  <title>about</title>
</Head>
// ...
Copy the code

4, routing,

Routes are automatically generated in the Pages directory at only one layer:

// The first way
pages / home.tsx;
pages / detail.tsx;

styles / home.scss;
styles / detail.scss;

/ / instead
// The second way
pages / home / index.tsx;
pages / home / home.scss;
Copy the code
  • pages/Do not use a folder under the React component. If you use a folder under the React component, the SCSS file will be reported during the build phase. So create a new one outsidestylesFolder for SCSS files of each routing component)
  • Other folders such ascomponentsThere are no errors, you can use the second method, the style and components in the same folder

4.1 the Link component

  • Href: routing, as inhref="about", will renderpage/about.tsxThe content of the
  • As:hrefRename it and then the browser address shows this URL;

    The routing for href must be correct, requiring a file that actually exists in the page directory
  • Prefetch: prefetch, used by the current page

href/as

Displaying the browser URL as About1 below, the pages/about.tsx file is actually rendered, as is the actual route, if the server does not intercept the route otherwise

import Link from 'next/link';

<Link href="/about">
  <a>About</a>
</Link>;
Copy the code

If the AS alias is set and the route is different from the original route, you need to configure another route on the server.

As follows:

  • The client
// ...
return (
  <div>
    <Link href="/detail? id=123" as="/detail/123">
      <a style={linkStyle}>Detail</a>
    </Link>
  </div>
);
// ...
Copy the code
  • The service side
// server.ts
server.get('/detail/:id', (req: Req, res: http.ServerResponse) => {
  app.render(req, res, '/detail', {
    id: req.params.id,
  });
});
Copy the code

prefetch

From the document

Some operations may require latency, but data can be prefetched using Prefetch

  • Link component
<Link href="/about" prefetch={true}>
  <a>About</a>
</Link>
Copy the code
  • imperative
import { withRouter } from 'next/router';

function MyLink({ router }) {
  return (
    <div>
      <a onClick={()= > setTimeout(() => router.push('/dynamic'), 100)}>
        A route transition will happen after 100ms
      </a>
      {// but we can prefetch it!
      router.prefetch('/dynamic')}
    </div>
  );
}

export default withRouter(MyLink);
Copy the code

push/replace

  • object
import Router from 'next/router';

const handler = (a)= > {
  Router.push({
    pathname: '/about'.query: { name: 'Zeit'}}); };function ReadMore() {
  return (
    <div>
      Click <span onClick={handler}>here</span> to read more
    </div>
  );
}

export default ReadMore;
Copy the code

4.2 Obtaining Routing Parameters for Non-Routing Components

React uses withRouter to get route parameters, but this is exported from next/ Router. Function components can also use useRouter

// src/components/header/index.tsx
import Link from 'next/link';
import Head from 'next/head';
import React from 'react';
import styles from './header.scss';

const Header = (a)= > {
  return (
    <div>
      <Head>
        <title>Next.js test</title>
        <meta name="viewport" content="width=device-width,initial-scale=1" />
      </Head>

      <Link href="/">
        <a className={styles.tag}>Home</a>
      </Link>
      <Link href="/about">
        <a className={styles.tag}>About</a>
      </Link>
      <Link href="/detail? id=123" as="/detail/123">
        <a className={styles.tag}>Detail</a>
      </Link>
    </div>
  );
};

export default Header;
Copy the code

4.3 Route Translation

  • willhref="/detail? id=123" çš„ Query Indicates the URL of the queryconvertas="/detail/123" çš„ Params type URL;

    Href /as is static and generated dynamically if necessary<Link>Can be

import Link from 'next/link';

<Link href="/detail? id=123" as="/detail/123">
  <a className={styles.tag}>Detail</a>
</Link>;
Copy the code
  • The matching URL of the server is/detail/:idRouting,

    addParams parametersAnd then render/detailThe correspondingpage/detail.tsxFile;

    The refresh will result in 404 if route interception is not set on the server
// server.ts
// ...

function serverRun() {
  const server = express();
  / / API interface
  const controllers = require('./server/controller');
  const apiRoute = ' '; // '/web';
  server.use(apiRoute, controllers);

  // Match the route with URL '/' and render the corresponding 'page/index.tsx' file
  server.get('/'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/');
  });

  // Match the route with URL '/about' and render the corresponding 'page/about.tsx' file with '/about'
  server.get('/about'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/about');
  });

  // Match the route with URL '/detail/:id', add parameter 'params', and render the' page/detail.tsx 'file corresponding to' /detail '
  server.get('/detail/:id'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/detail', {
      id: req.params.id,
    });
  });

  server.get(The '*'.(req: http.IncomingMessage, res: http.ServerResponse) = > {
    return handle(req, res);
  });

  server.listen(3000.(err: any) = > {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
}
Copy the code

4.4 Routing Methods

Route intercepting router. beforePopState

In the browser, need component loading componentDidMount/useEffect execution, or complains

// src/pages/index.tsx
import Link from 'next/link';
import { /* Router, */ useRouter } from 'next/router';
import React, { useEffect } from 'react';
import Layout from '@/components/layout';
import styles from '@/styles/index.scss';

/** * props */
const Home = (a)= > {
  const router = useRouter();

  useEffect((a)= > {
    // Route interception, which affects browser forward and back rendering results
    // Router.beforePopState(({ url, as, options }: any) => {
    // console.log('url: ', url);
    // console.log('as: ', as);
    // console.log('options: ', options);
    // if (as === '/about') {
    // console.log('about');
    // return true;
    / /}
    // return true;
    // });
  });

  return (
    <Layout>
      <h1>{router.query.title}</h1>
      <img
        className={styles.img}
        src="/static/images/4k-wallpaper-alps-cold-2283757.jpg"
      />
      <div className={styles.content}>
        <p>
          This is our blog post. Yes. We can have a{' '}
          <Link href="/link">
            <a>link</a>
          </Link>
          . And we can have a title as well.
        </p>
        <h3>This is a title</h3>
        <p>And here's the content.</p>
      </div>
    </Layout>
  );
};

export default Home;
Copy the code

4.5 the Router events

Listen for internal events of the route

  • RouteChangeStart (URL) – Triggered when the route starts to change
  • RouteChangeComplete (URL) – Triggered after the route has changed
  • RouteChangeError (err, url) – triggered when a routeChangeError occurs
  • BeforeHistoryChange (URL) – Changes browser history before triggering
  • HashChangeStart (URL) – Triggered when the hash changes
  • HashChangeComplete (URL) – Triggered after the hash changes

Example:

import Router from 'next/router';

const handleRouteChange = (url) = > {
  console.log('App is changing to: ', url);
};

Router.events.on('routeChangeStart', handleRouteChange);
Copy the code

Routing hop

// Select * from the about page where id = a11;
// To refresh the page, the id is asss, so try to keep both the same to avoid unnecessary problems
Router.push('/about? id=a11'.'/about/asss');
Copy the code
  • Shallow Routing: indicates a Shallow route

Modifying the page URL without executing getInitialProps,

Router.push('/about? id=a11'.'/about/asss', { shallow: true });
Copy the code

5, App

The _app component is not destroyed unless manually refreshed

// src/pages/_app.tsx
import React from 'react';
import { NextComponentType } from "next";
import { Router } from 'next/router';
import App, { AppProps } from 'next/app';

interface Props {
  Component: NextComponentType,
  pageProps: AppProps,
  router: Router
}

/** * App */
class myApp extends App<Props> {

  public constructor(props: Props) {
    super(props);
  }

  public componentDidUpdate() {
    console.log('router: '.this.props.router);
  }

  public componentDidMount() {
    console.log('router: '.this.props.router);
  }

  public render() {
    const { Component, pageProps } = this.props;

    return( <React.Fragment> <Component {... pageProps} /> </React.Fragment> ); } } export default myApp;Copy the code

6, components,

6.1 getInitialProps properties

Receives a method in which data can be retrieved and rendered on the server. Documents can only be used in components under Pages /

The refreshing of the current route is performed on the server. If the route is jumped from another route, the refreshing of the page is performed on the browser.

// src/pages/detail.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
import React from 'react';
import { inject, observer } from 'mobx-react';
import { homeStoreType } from '@/store/home';
import { Button, Row } from 'antd';
import Layout from '@/components/layout';
import styles from '@/styles/detail.scss';

function Detail(props: any) {
  const router = useRouter();
  const homeStore: homeStoreType = props.homeStore;

  return (
    <Layout>
      <Head>
        <title>Detail</title>
      </Head>
      <p className={styles.detail}>This is the detail page!</p>
      id: {router.query.id}
      <Row>count: {homeStore.count}</Row>
      <Button onClick={()= > homeStore.setCount(homeStore.count + 1)}>
        count++
      </Button>
    </Layout>
  );
}

Detail.getInitialProps = async function(context: any) {
  /** * If context.req is true and the server has req/res, print 'broswer' on the command line; * If another route jumps to the page without refreshing the page, context.req is false and printed on the browser console. * /
  console.log('render-type: ', context.req ? 'server' : 'broswer');

  return {
    // data: 'detail'
  };
};

const DetailWithMobx = inject('homeStore')(observer(Detail));

export default DetailWithMobx;
Copy the code

The received method takes one parameter context = {pathName, query, asPath, req, res, err} :

  • Pathname: The fixed part of the URL pathname, as defined/post/:id, where pathname is/post
  • Query: The object of the URL query parameter
  • AsPath – Defines the route, for example/post/:id
  • req: HTTP request object (server only)
  • res: HTTP response object (server only)
  • Err: Error reported during rendering

6.2 dynamic import

Implement dynamic components using dynamic and import(); Dynamic The second argument is an object. The loading field is the loading before the loading is complete

// src/components/about.tsx import dynamic from 'next/dynamic'; import Head from 'next/head'; import React from 'react'; import Layout from '@/components/layout'; import styles from '@/styles/about.scss'; const Hello = dynamic(() => import('.. /components/hello-world/index'), { loading: () => <p>... </p>, }); function About() { return ( <Layout> <Head> <title>about</title> </Head> <p className={styles.about}>This is the about page</p> <Hello /> </Layout> ); } // About.getInitialProps = async function(context: any) { // return { // data: 'about' // }; // } export default About;Copy the code

7. Path alias

Configuring webpack directly with babel-plugin-module-alias is not effective

yarn add babel-plugin-module-alias -D
Copy the code
  • Configuration. The babelrc
{
  "plugins": [["module-alias", { "src": "./src"."expose": "@"}]],"presets": [
    "next/babel"]},Copy the code
  • tsconfig.json
{
  "compilerOptions": {..."baseUrl": "src"."paths": {
      "@ / *": [". / *"]}},... }Copy the code

8. Use SCSS

Official plugin: @zeit/next-sass

8.1 SASS

The installation

npm install --save @zeit/next-sass node-sass
Copy the code

or

yarn add @zeit/next-sass node-sass
Copy the code

configuration

When configured, use CSS modules just like in React

// next.config.js
const withSass = require('@zeit/next-sass');
module.exports = withSass({
  cssModules: true.// The default is false, that is, globally valid
  cssLoaderOptions: {
    importLoaders: 1.localIdentName: '[local]___[hash:base64:5]',}});Copy the code

Or custom

// next.config.js
const withSass = require('@zeit/next-sass');
module.exports = withSass({
  webpack(config, options) {
    returnconfig; }});Copy the code

Using postcss

In the root directory of the project, create a new file postcss.config.js

// postcss.config.js
module.exports = {
  plugins: {
    autoprefixer: true,}};Copy the code

9, antd

Refer to this big guy’s article, mainly cssModules and ANTD on demand loading together with the use of other issues according to antD official website can be done, ANTD-Mobile as long as the corresponding ANTD to ANTD-mobile can be

.babelrc

{
  "plugins": / / /"transform-decorators-legacy"["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["module-alias", { "src": "./src"."expose": "@" }],
    ["import", { "libraryName": "antd"."style": "css"}]],"presets": [
    "next/babel"]},Copy the code

next.config.js

The following is the common configuration, which will be incorporated into the configuration of next.config.js

// config/config.common.js
const path = require('path');
const cssLoaderGetLocalIdent = require('css-loader/lib/getLocalIdent.js');
// const isProd = process.env.NODE_ENV === 'production';

if (typeof require! = ='undefined') {
  require.extensions['.css'] = (file) = > {};
}

/* Public configuration */
let configCommon = {
  // assetPrefix: isProd ? 'https://cdn.mydomain.com' : '',
  crossOrigin: 'anonymous'.cssModules: true.cssLoaderOptions: {
    importLoaders: 1.localIdentName: '[local]___[hash:base64:5]'.getLocalIdent: (context, localIdentName, localName, options) = > {
      const hz = context.resourcePath.replace(context.rootContext, ' ');
      // Exclude styles under node_modules
      if (/node_modules/.test(hz)) {
        return localName;
      }
      returncssLoaderGetLocalIdent( context, localIdentName, localName, options ); }},distDir: 'next-build'.// Build the output directory, default '.next'
  generateEtags: true.// Control cache etag, default true
  pageExtensions: ['tsx'.'jsx'.'js'.'scss'].// the file suffix in the pages folder
  webpack(config) {
    if (config.externals) {
      // Resolve the packing CSS error
      const includes = [/antd/];
      config.externals = config.externals.map((external) = > {
        if (typeofexternal ! = ='function') return external;
        return (ctx, req, cb) = > {
          return includes.find((include) = >
            req.startsWith('. ')? include.test(path.resolve(ctx, req)) : include.test(req) ) ? cb() : external(ctx, req, cb); }; }); }returnconfig; }};module.exports = configCommon;
Copy the code

State management MobX

Much like the React+Typescript single-page SPA project, except that the server rendering does not have Windows; So cache here first check whether the browser, then go to the browser API(sessionStorage);

_app.txs is actually rendered in the server, and the cache is restored in the browser. There is a time lag, and there is a warning (actually can be ignored, the server and the client cache does not need to synchronize, sessionStorage is also used because there is no need for a long cache. Of course, you can change it to localStorage as required

Single-page SPAs do not have this problem because they are rendered in the browser

const isBroswer: boolean = process.browser;
Copy the code

10.1 Project Entrance

// src/pages/_app.tsx
import { NextComponentType } from "next";
import { Router } from 'next/router';
import App, { AppProps } from 'next/app';
import React from 'react';
import { Provider } from 'mobx-react';
import store from '.. /store';

interface Props {
  Component: NextComponentType,
  pageProps: AppProps,
  router: Router
}

/** * App */
class myApp extends App<Props> {

  public constructor(props: Props) {
    super(props);
  }

  public componentDidUpdate() {
    console.log('router: '.this.props.router);
  }

  public componentDidMount() {
    console.log('router: '.this.props.router);
  }

  public render() {
    const { Component, pageProps } = this.props;

    return( <React.Fragment> <Provider {... store}> <Component {... pageProps} /> </Provider> </React.Fragment> ); } } export default myApp;Copy the code

10.2 module

Listen to data using Autorun, will be executed once when the data changes; The module is then converted into A JS object using toJS

// src/store/home.ts
import * as mobx from 'mobx';

// Do not modify state directly outside of action
mobx.configure({ enforceActions: "observed"});
const { observable, action, computed, runInAction, autorun } = mobx;

const isBroswer: boolean = process.browser;

/** ** sessionStorage (' sessionStorage '); * However, there is a flash process, because '_app.txs' is actually rendered on the server, and the cache is restored in the browser, * there is a time lag, and there are warnings, depending on the need */
let cache = isBroswer && window.sessionStorage.getItem('homeStore');

// Initialize the data
let initialState = {
  count: 0.data: {
    time: '2019-11-20'}};// Cache data
if(isBroswer && cache) { initialState = { ... initialState, ... JSON.parse(cache) } }class Home {
  @observable
  public count = initialState.count;

  @observable
  public data = initialState.data;

  @computed
  public get getTime() {
    return this.data.time;
  }

  @action
  public setCount = (_count: number) = > {
    this.count = _count;
  }

  @action
  public setCountAsync = (_count: number) = > {
    setTimeout((a)= > {
      runInAction((a)= > {
        this.count = _count; })},1000);
  }

  // public setCountFlow = flow(function *(_count: number) {
  // yield setTimeout(() => {}, 1000);
  // this.count = _count;
  // })
}

const homeStore = new Home();

// Data is cached after data changes
autorun((a)= > {
  const obj = mobx.toJS(homeStore);
  isBroswer && window.sessionStorage.setItem('homeStore'.JSON.stringify(obj));
});

export type homeStoreType = typeof homeStore;
export default homeStore;
Copy the code

10.3 Module Management Output

// src/store/index.ts
import homeStore from './home';

/** * Use mobx state management */
export default {
  homeStore,
};
Copy the code

10.4 Using Components

Here is the use of function components, the use of class components can be seen here

// src/pages/detail.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
import React from 'react';
import { inject, observer } from 'mobx-react';
import { homeStoreType } from '@/store/home';
import { Button, Row } from 'antd';
import Layout from '@/components/layout';
import styles from '@/styles/detail.scss';

function Detail(props: any) {
  const router = useRouter();
  const homeStore: homeStoreType = props.homeStore;

  return (
    <Layout>
      <Head>
        <title>Detail</title>
      </Head>
      <p className={styles.detail}>This is the detail page!</p>
      id: {router.query.id}
      <Row>count: {homeStore.count}</Row>
      <Button onClick={()= > homeStore.setCount(homeStore.count + 1)}>
        count++
      </Button>
      <Button onClick={()= > homeStore.setCountAsync(homeStore.count + 1)}>countAsync++</Button>
    </Layout>
  );
}

Detail.getInitialProps = async function(context: any) {
  /** * If context.req is true and the server has req/res, print 'broswer' on the command line; * If another route jumps to the page without refreshing the page, context.req is false and printed on the browser console. * /
  console.log('render-type: ', context.req ? 'server' : 'broswer');

  return {
    data: 'detail'}; };const DetailWithMobx = inject('homeStore')(observer(Detail));

export default DetailWithMobx;
Copy the code

11. Server

11.1 server. Ts

TSC server.ts generates server.js from TSC server.ts on the command line. In NPM script directly write ok; Don’t know how to automatically compile + restart the service

next(opts: object)

Opts have the following attributes:

  • Dev (bool): indicates whether the development environment is developed-default false
  • Dir (string): Next project location – default ‘.’
  • Quiet (bool): Hides error messages containing server information – Default false
  • Conf (object): Same as the object in next-config.js – default {}
const next = require('next');

constdev = process.env.NODE_ENV ! = ='production';
const app = next({
  dev,
  dir: '. '.quiet: false.conf: {},});Copy the code

Dynamic resource prefix assetPrefix

For example, if CDN is used locally or online, the resource is not the same as the server of the page

// server.ts
const express = require('express');
const next = require('next');
import * as http from 'http';

constdev = process.env.NODE_ENV ! = ='production';
const app = next({ dev });
const handle = app.getRequestHandler();

interface Req extendshttp.IncomingMessage { params? :any;
}

app
  .prepare()
  .then((a)= > {
    serverRun();
  })
  .catch((ex: any) = > {
    console.log(ex.stack);
    process.exit(1);
  });

function serverRun() {
  const server = express();
  / / API interface
  const controllers = require('./server/controller');
  const apiRoute = ' '; // '/web';
  server.use(apiRoute, controllers);

  // Match the route with URL '/' and render the corresponding 'page/index.tsx' file
  server.get('/'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/');
  });

  // Match the route with URL '/about' and render the corresponding 'page/about.tsx' file with '/about'
  server.get('/about'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/about');
  });

  // Match the route with URL '/detail/:id', add parameter 'params', and render the' page/detail.tsx 'file corresponding to' /detail '
  server.get('/detail/:id'.(req: Req, res: http.ServerResponse) = > {
    app.render(req, res, '/detail', {
      id: req.params.id,
    });
  });

  server.get(The '*'.(req: http.IncomingMessage, res: http.ServerResponse) = > {
    return handle(req, res);
  });

  server.listen(3000.(err: any) = > {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
}
Copy the code

11.2 Writing an Interface

Use the default Express

It was supposed to be changed to KOA, but the page will be rendered, the server rendered page will be 404, the content will be rendered (in server-koa.ts)…

The module

// server/controllers/user.js
const Router = require('express').Router();

Router.get('/userInfo/:id', (req, res) => {
  console.log('id: ', req.params.id);

  res.send({
    status: 200.data: {
      name: 'Little brat'.sex: 'male'.age: '3',},message: ' '}); });module.exports = Router;
Copy the code

Module management

// server/controller.js
const Router = require('express').Router();
const user = require('./controllers/user');

Router.use('/user', user);

module.exports = Router;
Copy the code

Mount to the Server service

// server.ts
// ...

function serverRun() {
  const server = express();
  / / API interface
  const controllers = require('./server/controller');
  const apiRoute = ' '; // '/web';
  server.use(apiRoute, controllers);

  // ...
}
Copy the code

Interface call

// src/pages/about.tsx
fetch('/user/userInfo/2')
  .then((res) = > res.json())
  .then((res) = > {
    console.log('fetch: ', res);
  });
Copy the code

Nginx is deployed locally, the request is proxied (proxy_pass) to next-test’s service (PM2 startup).

12, build,

npm script:

// package.json."scripts": {
    "dev": "node server.js"."dev:tsc": "tsc server.ts"."build": "next build"."start": "cross-env NODE_ENV=production node server.js"},...Copy the code

Development environment:

yarn dev
Copy the code

Packaging:

yarn build
Copy the code

The resulting next-build folder (next-build is the configured output directory)

13, deployment,

Don’t know why, the project under the nginx new HTML folder (/ usr/local/etc/nginx/HTML/next – test) start the project and nginx, browser to access has been a 502 (single page SPA is normal)… /usr/local/website/next-test /usr/local/website/

13.1 pm2

With PM2, you can test locally, but ultimately deploy on the server

yarn global add pm2
Copy the code

The terminal goes into the project directory and then:

All commands

pm2 start yarn --name "next-test" -- run start
Copy the code

The script

Sh in the root directory of the project:

#deploy.sh
pm2 start yarn --name "next-test" -- run start
Copy the code

Terminal into the project directory:

. deploy.sh
Copy the code

Several PM2 commands:

  • Pm2 show [id]: displays information about a PM2 application
  • Pm2 List: Displays an overview of all PM2 applications
  • Pm2 stop [id]/all: Stops an application. Multiple applications can be deleted, separated by Spaces, or stopped altogether
  • Pm2 delete [id]/all: Deletes an application. Multiple applications can be deleted and separated by Spaces or all applications can be deleted
  • Pm2 MONIT: monitors the status of pM2 started applications
  • Pm2 restart: restart
  • , etc.

13.2 Nginx

The PM2 service runs the program, and Nginx is responsible for enabling the HTTP service

Several nginx commands

  • Nginx: start nginx
  • Nginx -t: tests whether the nginx.conf configuration is correct
  • Nginx -s stop: Stops the nginx
  • Nginx -s reload: restarts the nginx
  • , etc.

Configurations tested locally (MAC)

/usr/local/website

Nginx. Conf configuration:


#user nobody;
worker_processes  1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    # '$status $body_bytes_sent "$http_referer" '
    # '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log logs/access.log main;

    sendfile        on;
    #tcp_nopush on;

    #keepalive_timeout 0;
    keepalive_timeout  65;

    gzip  on;

    upstream next-test {
	    server 127.0.0.1:3000; # next-test Specifies the service port to start
    }

    # next-test
    server {
        listen       80;
        server_name  localhost; # Configure the domain name here

        #charset koi8-r;

        #access_log logs/host.access.log main;

        error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        location / {
            proxy_pass http://127.0.0.1:3000; The request will be forwarded to the next-test node service
            proxy_redirect off;

            # root html;
            indexindex.html index.htm; }}# movie-DB: single page SPA
    server {
        listen       81;
        server_name  localhost; # Configure the domain name here

        root /usr/local/website/movie-db/;

        location / {
            try_files $uri $uri/ @router;
        }

        location @router {
            rewrite ^.*$/index.html last;
        }

        # error_page 404 /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            roothtml; }}# HTTPS server
    #
    #server {
    # listen 443 ssl;
    # server_name localhost;

    # ssl_certificate cert.pem;
    # ssl_certificate_key cert.key;

    # ssl_session_cache shared:SSL:1m;
    # ssl_session_timeout 5m;

    # ssl_ciphers HIGH:! aNULL:! MD5;
    # ssl_prefer_server_ciphers on;

    # location / {
    # root html;
    # index index.html index.htm;
    #}
    #}
    include servers/*;
}
Copy the code

The next thing I need to do

The database

MySQL/MongoDB

GrahpQL

As if called API query language, the main interface aggregation of a thing it; It is for the aggregation of the back-end microservice interface, and then the front-end page as long as the request through the aggregation of the interface, there is no need to repeatedly request the back-end microservice small interface; I don’t know if I got it right

Some of the problems

  • From a level 1 route to a level 2 route and then refresh, browser back, THE URL changes but the content stays the same! This is a bug, issue #9378, fixed

  • Styles.chunk. CSS is not associated with CSS, but SCSS + CSS modules have been converted.

  • Next. Js seems to have a problem with koA, the server rendered the page to look at the web request there will be 404, but the page rendered; Or dynamic route (/detail/:id) 404… Node.js services built using KOA alone do not have this problem (here)

reference

  • Documents to nextjs.org
  • And some of the big names on the Internet