如何快速搭建SSR服务之 nextjs

2021-06-23  本文已影响0人  Kelly_zj

初识nextjs


Next.js是一个基于react的服务端渲染框架。提供生产环境所需的所有功能以及最佳的开发体验:包括静态及服务器端融合渲染、 支持 TypeScript、智能化打包、 路由预取等功能 无需任何配置。
主要功能:

基于react渲染的SSR框架nextjs,基于vue渲染的SSR框架nuxtjs

什么是服务端渲染


首先要清楚一个渲染的概念:渲染即是数据与模版组装成html;

为了更好的理解服务端渲染,我们可以将服务端渲染与客户端渲染对比着来看。

为什么需要服务端渲染

总的一句话:实际开发根据实际场景。

接下来我们来动手搭建一个基于nextjs+react+ts+antd的服务端渲染框架。

项目创建

  // 使用ts开发项目,可以通过``--typescript``参数创建ts项目
  npx create-next-app --typescript
  // or
  yarn  create-next-app
//1、安装项目所需
  npm install next react react-dom
  //or
  yarn add next react react-dom

//2、package.json中添加script配置
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }

目录结构

//脚手架生成的目录
├── README.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── hello.ts
│   └── index.tsx
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   ├── common.css
│   ├── common.less
│   └── globals.css
└── tsconfig.json

//改造之后的目录
.
├── api //接口
│   ├── client.js
│   └── server.js
├── axios //请求封装
│   ├── host.js
│   ├── http.js
│   └── index.js
├── components //组件
│   ├── header-custom
│   ├── index-component
│   ├── layout.jsx
├── constants //公共
│   └── menus.js
├── ecosystem.json //pm2发布配置
├── less //公共样式
│   ├── br-common.less
├── lib //公共js
│   ├── cookie.js
│   └── mockuser.js
├── next-env.d.ts
├── next.config.js //next默认配置,可增加webpack配置等
├── package.json 
├── pages //页面即路由
│   ├── _app.js
│   ├── _error.js
│   ├── index
│   └── login
├── server.js 
├── tsconfig.json
└── yarn-error.log

路由

Next.js 是围绕着 页面(pages) 的概念构造的。一个页面(page)就是一个从 pages 目录下的 .js.jsx.ts.tsx 文件导出的 React 组件,例如:pages/about.js 被映射到 /about

//pages/about.js
function About() {
  return (
    <p>
        welcome nextjs!
    </p>
  )
}

export default About

1、link路由切换

import Link from 'next/link'; 
<Link href="/detail?id=123">
  <a>Detail</a>
</Link>;

2、router切换

import Router from 'next/router';
Router.push('/index')

引入antd,使用css

引入antd公共的css(或者是import外部css,less等),需要安装@zeit/next-less,@zeit/next-css,并在next.config.js引入,tips:原因是less3.0之后,默认不开启内联JavaScript,需要传入{ javascriptEnabled:true }手动开启 如下图:

const withLess = require('@zeit/next-less')
const withCss = require('@zeit/next-css')
const config = {
  //1、集成antd插件
        lessLoaderOptions: {//如果是antd就需要,antd-mobile不需要
            javascriptEnabled: true //覆盖默认webpack配置
        },
}
module.exports = withLess(withCss(config))

获取数据

使用一个 async 函数 getInitialProps 来获取数据;自动区分服务端执行or客户端执行;
在当前路由刷新才会在服务端执行,如果是从其他路由跳转过来的,没有刷新页面就会在浏览器端执行的;

function Page({ stars }) {
  return <div>Next stars: {stars}</div>
}

Page.getInitialProps = async (ctx) => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const json = await res.json()
  return { stars: json.stargazers_count }
}

export default Page

自定义APP

Next.js 使用 App 组件来初始化页面。你可以覆盖该 App 组件并控制页面的初始化,比如:切换页面保持页面布局、添加全局css、向页面注入数据等;
要覆盖默认的APP,需要再pages目录下创建_app.js文件;

//引入公共样式,公共布局,向页面注入公共数据
import { useRouter} from "next/router";
import { useEffect } from 'react'
import UserLogin from '../pages/login/index';
import LayoutBasic from '../components/layout';
import "antd/dist/antd.css";

function App({ Component, pageProps,USERINFO }){
    const router = useRouter()
    useEffect(() => {
        if (!USERINFO) {
            router.push('/login')
        }
    }, [USERINFO])

    const { pathname } = router;
    // 判断渲染登录
    let layout = (
        <LayoutBasic userInfo={USERINFO}>
            <Component {...pageProps} {...USERINFO} />
        </LayoutBasic>
    );

    if (pathname == '/login') {
        layout = (
            <UserLogin>
                <Component {...pageProps} />
            </UserLogin>
        );
    }
    return (
        <>
            <style global jsx>
                {`
                #__next,
                .ant-layout {
                    min-height: 100vh;
                }
            `}
            </style>
            {layout}
        </>
    );
}

App.getInitialProps = async ({ Component, router, ctx }) => {
    let pageProps = {};
    let USERINFO = {};
    USERINFO = ctx.userInfo = {name:'zj'};
    if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx);
    }
    return { pageProps, USERINFO };
};
export default App

next.config.js

自定义webpack配置、打包编译目录、重定向、环境变量等。

// webpack配置
// css跟less 并存使用
const path = require('path');
const withLess = require('@zeit/next-less')
const withCss = require('@zeit/next-css')
const {
    PHASE_DEVELOPMENT_SERVER,
    PHASE_PRODUCTION_BUILD,
} = require('next/constants')

module.exports = (phase)=>{
    //自定义环境变量,在package.json中设置不生效,由于next build会执行线上编译
    const isDev = phase === PHASE_DEVELOPMENT_SERVER
    const isDaily = phase === PHASE_PRODUCTION_BUILD && process.env.STAGING !== '1'
    const isProduction =
        phase === PHASE_PRODUCTION_BUILD && process.env.STAGING === '1'

    console.log(`isDev:${isDev}  isDaily:${isDaily}   isProduction:${isProduction}`)
    const nodeEnv = isDev ? 'dev' : isDaily ? 'daily' : 'production';
    const config = {
        //1、集成antd插件
        lessLoaderOptions: {//如果是antd就需要,antd-mobile不需要
            javascriptEnabled: true
        },
        webpack(config, { dev }) {
            const webpack = require('webpack')
            config.resolve.alias = {
                '@components': path.resolve('./components')
            }
            return config;
        },
        env: {
            NODEENV: nodeEnv
        },
        // distDir: 'build' //打包文件目录
    }
    return withLess(withCss(config))
}

服务端【如果是动态路由,拦截路由,接口,操作接口返回等可以增加服务端】

/*
 * @Descripttion: 
 * @Author: zj
 * @Date: 2021-03-17 16:36:22
 * @LastEditors: zj
 * @LastEditTime: 2021-06-08 14:13:28
 */
const Koa = require('koa')
const Router = require('koa-router');
const cors = require('koa-cors');
const bodyParser = require('koa-bodyparser')
const logManager = require('@bairong/lib-util/logger');
const log = logManager.getLogger('systemService');
const next = require('next')// nextjs 作为中间件
const dev = process.env.NODE_ENV == 'dev' ? true : false; //true开启热更新,false不开启
console.log('环境变量',process.env.NODE_ENV,dev);
const app = next({ dev })// 初始化 nextjs,判断它是否处于 dev:开发者状态,还是production: 正式服务状态
const handler = app.getRequestHandler()// 拿到 http 请求的响应
app.prepare().then(() => {
    const server = new Koa()
    const router = new Router();
    /** 这是 Koa 的核心用法:中间件。通常给 use 里面写一个函数,这个函数就是中间件。
   * params:
   *  ctx: Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为请求上下文对象
   *  next: 调用后将执行流程转入下一个中间件,如果当前中间件中没有调用 next,整个中间件的执行流程则会在这里终止,后续中间件不会得到执行
   */
    server.use(
        bodyParser({
            enableTypes: ['json', 'form', 'text']
        })
    );
    server.use(cors()); //处理跨域
    router.get('/login', async (ctx) => {
        await app.render(ctx.req, ctx.res, '/login', ctx.query)
        ctx.respond = false
    })
     router.all('(.*)', async (ctx) => {
        await handler(ctx.req, ctx.res)
        ctx.respond = false
    })
    server.use(async (ctx, next) => {
        ctx.res.statusCode = 200
        await next()
    })
    server.use(router.routes());
    server.listen(3080, () => {
        log.info(`🌎 服务已启动 server is running at http://localhost:3080`)
    })
})

构建、部署

使用pm2发布并做进程守护,首先安装pm2,然后根目录新建ecosystem.json文件,然后修改package.json

sudo npm i pm2 

//ecosystem.json
{
    "apps": [
        {
            "name": "demo", //项目名称
            "script": "server.js" //执行的文件
        }
    ],
    "deploy": {
        "production": { //线上
            "user": "zj", //用户名
            "host": [
                "172.18.30.16" //发布的服务器ip
            ],
            "ref": "origin/master",
            "repo": "git@192.168.23.221:fed/demo.git",
            "ssh_options": "StrictHostKeyChecking=no",
            "path": "/opt/demo",
            "post-deploy": "git pull origin master && npm install --registry=http://registry.shurongdai.cn/ && npm run productionpush",
            "env": {
                "NODE_ENV": "production"
            }
        },
        "daily": { //日常
            "user": "zj",
            "host": [
                "172.18.31.121"
            ],
            "ref": "origin/deploydaily", //拉取分支
            "repo": "git@192.168.23.221:fed/demo.git",
            "path": "/opt/demo",
            "post-deploy": "git pull origin deploydaily && npm install --registry=http://registry.shurongdai.cn/ && npm run dailypush",
            "env": {
                "NODE_ENV": "daily"
            }
        }
    }
}
//package.json
"scripts": {
    "nextdev": "next dev",
    "dev": "cross-env NODE_ENV=dev node server.js",
    "build": "next build",
    "start": "node server.js",
    "dailypush": "cross-env NODE_ENV=daily npm run build && cross-env NODE_ENV=daily PM2_HOME='/opt/demo/.pm2' pm2 startOrRestart ecosystem.json",
    "productionpush": "cross-env NODE_ENV=production npm run build && cross-env NODE_ENV=production PM2_HOME='/opt/demo/.pm2' pm2 startOrRestart ecosystem.json",
    "status": "PM2_HOME='/opt/demo/.pm2' pm2 status",
    "log": "PM2_HOME='/opt/demo/.pm2' pm2 log",
    "stop": "PM2_HOME='/opt/demo/.pm2' pm2 delete all"
  },

参考文档

官网
参考地址

上一篇下一篇

猜你喜欢

热点阅读