Next.js 服務端渲染框架實戰

基於React.js 技術棧的服務端渲染框架Next.js 實戰記錄

第一次在掘金上發佈文章,本着學習的態度,將本身運用Next.js開發服務端渲染的項目復原總結出來,鞏固知識點,也能夠跟同行探討下技術。(文章不斷完善中...)php

1.項目背景

公司原有項目基於PHP和jQuery混合開發的,提出重構需求。可是後端技術棧由PHP更替爲Java微服務,前端技術棧也從jQuery更替爲React.js。由於公司所處行業須要作線上推廣,那項目重構必須得考慮對SEO優化友好了,本身平時更多的是用React.js技術棧作前端開發,因而找到了Next.js(基於React)這個服務端渲染框架。css

2.基本需求

  • 搜索引擎收錄: SEO友好,利於搜索引發爬取頁面信息
  • 路由美化: 路由須要按照規律來展示
  • 根據不一樣城市顯示不一樣數據: 須要根據具體城市(IP定位)來展現不一樣城市數據
  • PC端/M端: 根據設備判斷渲染不一樣客戶端的組件模板
  • SEO信息可配置: 每一個首屏子頁面(如首頁、關於咱們、公司介紹頁)支持SEO信息可配置
  • 支持開發/正式環境切換: 根據命令行判斷當前環境,配置API接口前綴
  • 微信受權文件部署:微信支付受權文件*.txt 文件在項目根目錄部署
  • 部分Http請求代理處理:部分接口跨域處理(http-proxy-middleware)
  • 相似重定向處理:訪問當前域名,拉取不一樣域名下的頁面數據,展現在當前路由下
  • 本地數據模擬:會是嘗試用mock.js / json-server / faker.js 方式 [api文檔管理工具能夠用yapi]
  • 項目部署方式:項目兩種部署方式,基於Docker部署和Node部署(本地也會用docker調試)

3.Next.js原理

中文官網 Next.js 是一個輕量級的 React 服務端渲染應用框架。 服務端渲染的理解:其實不少人接觸過服務端渲染,最傳統的PHP嵌套靜態html頁面就是服務端渲染的一種。PHP經過模板引擎把從數據庫取到的數據渲染到html種,當前端訪問指定路由時,php發送給前臺指定的頁面,這個頁面在瀏覽器端識別到的是.html 文件(Content-type:text/html),瀏覽器按照靜態html文件格式解析頁面渲染後展現出來,用瀏覽器查看源代碼時就是豐富的html標籤還有標籤裏的文本信息,例如SEO信息,文章標題/內容等。這樣的頁面搜索引擎就能夠很容易抓取到了。Next.js 原理相似,只不事後端的語言是Node而已,在React組件中嵌入getInitialProps方法獲取到的服務端動態數據,在服務端把React組件渲染成html頁面,發送到前臺。html

4.Next.js關鍵點

文件系統:

Next文件系統規定,在pages文件夾下每一個*.js 文件將變成一個路由,自動處理和渲染前端

新建 ./pages/index.js 到你的項目中, 項目運行後能夠經過 localhost:3000/index 路徑訪問到頁面。同理 ./pages/second.js 能夠經過localhost:3000/second訪問到node

靜態文件服務:

如圖片,字體,js工具類react

在根目錄下新建文件夾叫static。代碼能夠經過/static/來引入相關的靜態資源 不要自定義靜態文件夾的名字,只能叫static ,由於只有這個名字 Next.js 纔會把它看成靜態資源webpack

export default () => <img src="/static/my-image.png" alt="my image" />
複製代碼

數據獲取:

Next.js 能實現服務端渲染的關鍵點就在這裏了。getInitialProps函數提供獲取數據的生命週期鉤子git

建立一個有狀態、生命週期或有初始數據的 React 組件github

import React from 'react'

export default class extends React.Component {
  static async getInitialProps({ req }) {
    const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
    return { userAgent } // 這裏綁定userAgent數據到Props,組件裏就能夠用 this.props.userAgent訪問到了
  }

  render() {
    const { userAgent } = this.props // ES6解構賦值 
    return (
      <div>
        Hello World {userAgent}
      </div>
    )
  }
}

==========================================

// 無狀態組件定義getInitialProps *這種方式也只能用在pages目錄下
const Page = ({ stars }) =>
  <div>
    Next stars: {stars}
  </div>

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

export default Page
複製代碼

上面代碼經過異步方法 getInitialProps 獲取數據,綁定在props。服務渲染時,getInitialProps將會把數據序列化,就像JSON.stringify。頁面初始化加載時,getInitialProps只會加載在服務端。只有當路由跳轉(Link組件跳轉或 API 方法跳轉)時,客戶端纔會執行getInitialPropsweb

劃重點:getInitialProps將不能使用在子組件中。只能使用在pages頁面中 子組件能夠經過pages文件夾下的頁面獲取數據,而後Props傳值到子組件

getInitialProps入參對象的屬性以下

  • pathname - URL 的 path 部分
  • query - URL 的 query 部分,並被解析成對象
  • asPath - 顯示在瀏覽器中的實際路徑(包含查詢部分),爲String類型
  • req - HTTP 請求對象 (只有服務器端有)
  • res - HTTP 返回對象 (只有服務器端有)
  • jsonPageRes - 獲取數據響應對象 (只有客戶端有)
  • err - 渲染過程當中的任何錯誤

用 組件實現客戶端的路由切換

若是須要注入pathname, query 或 asPath到你組件中,你可使用withRouter高階組件

// pages/index.js
import Link from 'next/link'

export default () =>
  <div>
    Click{' '}
    <Link href="/about">
      <a>here</a>
    </Link>{' '}
    to read more
  </div>
  
// 高階組件
import { withRouter } from 'next/router'

const ActiveLink = ({ children, router, href }) => {
  const style = {
    marginRight: 10,
    color: router.pathname === href? 'red' : 'black'
  }

  const handleClick = (e) => {
    e.preventDefault()
    router.push(href)
  }

  return (
    <a href={href} onClick={handleClick} style={style}>
      {children}
    </a>
  )
}

export default withRouter(ActiveLink)
複製代碼

5.項目目錄

從這一步開始就是實際建立項目寫代碼的過程了,因爲是公司項目,這裏所有用模擬數據,可是上文提到的項目需求都會從零開始一項項實現。

安裝

1.首先新建目錄 ssr 在ssr目錄下執行
cnpm install --save next react react-dom // 須要設置 npm鏡像

2.執行完命令後目錄下出現文件夾node_module 和文件package.json 
ssr
    -node_modules
    -package.json

package.json 文件內容以下
{
  "dependencies": {
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}

3.添加腳本到package.json文件. 咱們能夠在這裏自定義npm腳本命令
{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}

4.在ssr目錄下新建文件夾 pages | static | components ... ,而後在pages下新建文件 index.js,文件內容以下

export default () => <div>Welcome to next.js!</div>

最終新目錄結構以下 [暫時沒提到的文件和目錄後續會講到]
ssr
    -node_modules
    -package.json
    -components
    -static
        -imgs
            -logo.png
        -fonts
            -example.ttf
        -utils
            -index.js
    -pages
        -index.js
        -about.js
    -.gitignore
    -README.md
    -next.config.js
    -server.js

5.運行 npm run dev 命令並打開 http://localhost:3000
  執行npm run start 以前須要先執行 npm run build 否則會報錯

複製代碼

用瀏覽器調試工具打開查看源代碼,能夠看到 根容器_next 下有div元素渲染進去了,數據不少時就會有豐富的利於搜索引擎爬取html代碼。

這裏跟SPA單頁面應用對比更好理解,SPA應用只有一個掛載組件的root根容器。容器裏面不會看到其餘豐富的html代碼

6.項目需求實現

項目是爲了利於SEO作的服務端渲染,說到SEO,須要設置html文檔裏的head頭部信息。這裏有三個很是關鍵的信息,kywords | description | title 分別表示當前網頁的關鍵字,描述,網頁標題。搜索引擎會根據這幾個標籤裏的內容爬取網頁的關鍵信息,而後用戶在搜索的時候根據這些關鍵字匹配程度作搜索結果頁面展示。(固然展示算法遠遠不止參考這些信息,頁面標籤的語意化,關鍵字密度,外鏈,內鏈,訪問量,用戶停留時間...)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="keywords" content="Winyh | Next.js | React.js | Node.js | ...">
    <meta name="description" content="這是一個跟next.js服務端相關的頁面">
    <title>基於React.js 技術棧的服務端渲染框架Next.js 實戰記錄</title>
</head>
<body>
    
</body>
</html>
複製代碼

需求一:SEO信息可配置

這個實現了,搜索引擎搜錄也算是簡單實現了。要實現搜索引擎友好其實有上述不少方面的能夠優化。

  • 設置一個內置組件來裝載到頁面中,將文件命名字爲HeadSeo.js
// components/Common/HeadSeo.js 文件裏代碼以下
import Head from 'next/head'

export default () =>
    <Head>
        <meta charSet="UTF-8"> // 注意這裏的charSet大寫,否則React jsx語法 會報錯
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="keywords" content="Winyh | Next.js | React.js | Node.js | ...">
        <meta name="description" content="這是一個跟next.js服務端相關的頁面">
        <title>基於React.js 技術棧的服務端渲染框架Next.js 實戰記錄</title>
    </Head>
    
    
// pages/index.js 文件裏代碼以下
import Layout from "../components/Layouts/PcLayout"

export default () => 
    <Layout>
        <div>Welcome to next.js!</div>
    </Layout>

相應目錄結構爲
ssr
    -node_modules
    -package.json
    -components
        -Common   // 公共組件
            -HeadSeo.js
        -Layouts  // 佈局文件
            -PcLayout.js
            -MLayout.js
    -static // 靜態資源
        -imgs
            -logo.png
        -fonts
            -example.ttf
        -utils
            -index.js
    -pages 
        -index.js
        -about.js
    -.gitignore
    -README.md
    -next.config.js // 配置文件
    -server.js // 服務端腳本
複製代碼

打開localhost:3000 能夠看到相關 head 頭部seo信息已經渲染出來了。若是須要在服務端動態渲染數據,能夠在pages目錄下的文件請求後臺數據,經過Props傳值的方式渲染到HeadSeo文件中,這裏暫時值說下方法,後續寫實際代碼實現。

需求二:路由美化

經過自定義服務端路由實現路由自定義美化功能。例如在武漢(wuhan)站點時,訪問首頁須要路由是這樣的

城市 首頁 關於咱們
武漢 /wuhan/index /wuhan/about
上海 /shanghai/index /shanghai/about
南京 /nanjing/index /nanjing/about

建立服務端腳本文件 server.js,服務端用Express作服務器

// 安裝 express 服務端代理工具也一塊兒安裝了 http-proxy-middleware
cnpm i express http-proxy-middleware --save
複製代碼
const express = require('express')
const next = require('next')
const server = express()

const port = parseInt(process.env.PORT, 10) || 3000 // 設置監聽端口
const dev = process.env.NODE_ENV !== 'production' // 判斷當前開發環境
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {

    server.get('/:city', (req, res) => {
        const actualPage = '/index';
        const queryParams = { city: req.params.city}; // 經過 req 請求對象訪問到路徑上傳過來的參數
        console.log(req.params)
        app.render(req, res, actualPage, queryParams);
    });

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

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

    server.get('/:city/posts/:id', (req, res) => {
      return app.render(req, res, '/posts', { id: req.params.id })
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
})
複製代碼

修改package.json 文件的腳本以下:而後運行命令 npm run ssrdev 打開3000端口,至此能夠經過美化後的路由訪問到頁面了 localhost:3000/wuhan/index
localhost:3000/wuhan/about

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "ssrdev": "node server.js", // 能夠經過nodemon 來代替node,這樣server.js 文件修改後不須要從新運行腳本
    "ssrstart": "npm run build && NODE_ENV=production node server.js", // 須要先執行 npm run build
    "export": "npm run build && next export"
  },
  "dependencies": {
    "express": "^4.17.0",
    "http-proxy-middleware": "^0.19.1",
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}
複製代碼

需求三:根據不一樣城市顯示不一樣數據

根據用戶地理位置展現對應城市站點首頁,獲取不一樣城市的數據。這裏開始就數據模擬和服務端數據獲取了。 本次項目實踐會嘗試兩種數據模擬的方式

  • json-server

  • mock.js (這種方式更簡單,後續加上)

首先安裝開源的 json-server具體使用方式參照github

cnpm install -g json-server
複製代碼

在ssr目錄下新建mock文件下,而後在mock下新建 data.json,文件數據以下

{
    "index":{
        "city":"wuhan",
        "id":1,
        "theme":"默認站點"
    },
    "posts": [
      { "id": 1, "title": "json-server", "author": "typicode" }
    ],
    "comments": [
      { "id": 1, "body": "some comment", "postId": 1 }
    ],
    "profile": { "name": "typicode" },
    "seo":{
        "title":"基於React.js 技術棧的服務端渲染框架Next.js 實戰記錄",
        "keywords":"Winyh, Next.js, React.js, Node.js",
        "description":"Next.js服務端渲染數據請求模擬頁面測試"
    }
}
複製代碼

在當前目錄新建路由規則文件 routes.json 爲模擬api添加/api/前綴。文件類型以下

{
    "/api/*": "/$1"
}
複製代碼

修改package.json 文件,添加數據模擬命令行腳本

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "ssrdev": "nodemon server.js",
    "ssrstart": "npm run build && NODE_ENV=production nodemon server.js",
    "export": "npm run build && next export",
  + "mock": "cd ./mock && json-server --watch data.json --routes routes.json --port 4000"
  },
  "dependencies": {
    "express": "^4.17.0",
    "http-proxy-middleware": "^0.19.1",
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  }
}

複製代碼

運行命令 npm run mock 啓動模擬數據服務器便可訪問數據

localhost:4000/api/seo

先安裝ajax請求工具

cnpm install isomorphic-unfetch --save
複製代碼

更新pages/index.js文件內容爲

import React, { Component } from 'react';
import Layout from "../components/Layouts/PcLayout"
import 'isomorphic-unfetch'

class index extends Component {
    constructor(props) {
		super(props);
		this.state = {
			city:"武漢"
		};
    }

    static async getInitialProps({ req }) {
        const res = await fetch('http://localhost:4000/api/seo')
        const seo = await res.json()
        return { seo }
    }

    componentDidMount(){
        console.log(this.props)
    }
    
    render(){
        const { seo } = this.props;
        return (
            <Layout seo={seo}>
                <div>Welcome to next.js!</div>
                <div>{seo.title}</div>
            </Layout>
        )
    }
}
export default index
複製代碼

/Layouts/Pclayout.js 文件內容修改成

import HeadSeo from '../Common/HeadSeo'

export default ({ children, seo }) => (
  <div id="pc-container">
    <HeadSeo seo={ seo }></HeadSeo>
    { children }
  </div>
)
複製代碼

/components/Common/HeadSeo.js 文件內容修改成

import Head from 'next/head'

export default ({seo}) =>
    <Head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="keywords" content={seo.keywords} />
        <meta name="description" content={seo.description} />
        <title>{seo.title}</title>
    </Head>
複製代碼

至此頁面上就能夠看到打印的數據和展現的數據了

下一步根據用戶地理位置肯定顯示的頁面城市,解決方案步驟以下【暫時只說方法,稍後完善代碼】

  • 先請求百度開放api根據ip定位獲取到城市名稱和惟一碼
  • 經過惟一碼做爲參數請求惟一碼對應城市數據
  • 路由美化後返回對應城市頁面數據到前臺展現

需求四:PC端/M端渲染不一樣頁面

基本原理:根據請求頭user-agnet 判斷終端,而後渲染不一樣的組件 在static文件夾下新建 js文件夾,在 js文件夾下新建 util.js工具類模塊,代碼以下

// 根據 user-agent 請求頭判斷是否移動端
const util = {

    isMobile: (req) => {
        const deviceAgent = req.headers["user-agent"];
        return /Android|webOS|iPhone|iPod|BlackBerry/i.test(deviceAgent)
    },

};

module.exports  = util
複製代碼

在pages文件夾下新建mindex.js文件,做爲移動端渲染的首頁

import React, { Component } from 'react';
import Layout from "../components/Layouts/MLayout"
import 'isomorphic-unfetch'

class index extends Component {
    constructor(props) {
		super(props);
		this.state = {
			city:"武漢"
		};
    }

    static async getInitialProps({ req }) {
        const res = await fetch('http://localhost:4000/api/seo')
        const seo = await res.json()
        return { seo }
    }

    componentDidMount(){
        console.log(this.props)
    }
    
    render(){
        const { seo } = this.props;
        return (
            <Layout seo={seo}>
                <div>Welcome to next.js!</div>
                <div>移動端頁面</div>
            </Layout>
        )
    }
}
export default index
    
複製代碼

修改server.js文件內容以下

const express = require('express')
const next = require('next')
const server = express()

const util = require("./static/js/util");

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {

    server.get('/:city', (req, res) => {
        const actualPage = util.isMobile(req) ? '/mindex' : '/index'; // 這裏是關鍵
        const queryParams = { city: req.params.city};
        console.log(req.params.city, actualPage)
        app.render(req, res, actualPage, queryParams);
    });

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

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

    server.get('/:city/posts/:id', (req, res) => {
      return app.render(req, res, '/posts', { id: req.params.id })
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
})
複製代碼

用瀏覽器打開調試面板的移動端模式就能夠自動渲染移動端頁面了

需求五:SEO信息可配置

其實基本已經實現了,前臺經過不一樣的頁面路由頁面參數請求後端子頁面首屏初始化數據接口,請求在服務端渲染時getInitialProps方法裏完成。 例如:/wuhan/index 能夠根據 index 做爲參數獲取後臺配置給index頁面的seo信息 /wuhan/posts 能夠根據 posts 做爲參數獲取後臺配置給posts頁面的seo信息

需求六:支持開發/正式環境切換

服務端server.js可經過以下方法實現

const dev = process.env.NODE_ENV !== 'production';
複製代碼

客戶端能夠經過配置文件next.config.js實現

/*
* @Author: winyh
* @Date:   2018-11-01 17:17:10
 * @Last Modified by: winyh
 * @Last Modified time: 2018-12-14 11:01:35
*/
const withPlugins = require('next-compose-plugins')
const path = require("path");
const sass = require('@zeit/next-sass')

const isDev = process.env.NODE_ENV  !== 'production'

console.log({
    isDev
})

// api主機 
const host = isDev ? 'http://localhost:4000':'http://localhost:4001'

const {
    PHASE_PRODUCTION_BUILD,
    PHASE_PRODUCTION_SERVER,
    PHASE_DEVELOPMENT_SERVER,
    PHASE_EXPORT,
} = require('next/constants');

const nextConfiguration = {
    //useFileSystemPublicRoutes: false, 
    //distDir: 'build',
    testConfig:"www",
    webpack: (config, options) => {

        config.module.rules.push({
            test: /\.(jpe?g|png|svg|gif|ico|webp)$/,
            use: [
              {
                loader: "url-loader",
                options: {
                  limit: 20000,
                  publicPath: `https://www.winyh.com/`,
                  outputPath: `/winyh/static/images/`,
                  name: "[name].[ext]"
                }
              }
            ]
        })
    
        return config;
    },

    serverRuntimeConfig: { // Will only be available on the server side
        mySecret: 'secret'
    },
    publicRuntimeConfig: { // Will be available on both server and client
        mySecret: 'client',
        host: host,
        akSecert:'GYxVZ027Mo0yFUahvF3XvZHZzAYog9Zo' // 百度地圖ak 密鑰
    }
}

module.exports = withPlugins([
    
    [sass, {
        cssModules: false,
        cssLoaderOptions: {
          localIdentName: '[path]___[local]___[hash:base64:5]',
        },
        [PHASE_PRODUCTION_BUILD]: {
          cssLoaderOptions: {
            localIdentName: '[hash:base64:8]',
          },
        },
    }]

], nextConfiguration)
複製代碼

pages/index.js經過配置文件修改api主機地址碼,代碼以下(fetch請求後面會封裝成公用方法)

import React, { Component } from 'react';
import Layout from "../components/Layouts/PcLayout"
import 'isomorphic-unfetch'
import getConfig from 'next/config' // next自帶的配置方法
const { publicRuntimeConfig } = getConfig() // 取到配置參數

class index extends Component {
    constructor(props) {
		super(props);
		this.state = {
			city:"武漢"
		};
    }

    static async getInitialProps({ req }) {
        const res = await fetch(publicRuntimeConfig.host + '/api/seo') // 從配置文件裏獲取
        const seo = await res.json()
        return { seo }
    }

    componentDidMount(){
        console.log(this.props)
    }
    
    render(){
        const { seo } = this.props;
        return (
            <Layout seo={seo}>
                <div>Welcome to next.js!</div>
                <div>{seo.title}</div>
            </Layout>
        )
    }
}
export default index
複製代碼

需求七:微信受權文件部署

在網頁端作微信支付或者受權時須要經過微信服務器的安全校驗,微信服務器下發一個密鑰文件*.txt,通常放在項目根目錄,須要支持訪問,例如:localhost:3000/MP_verify_HjspU6daVebgWsvauH.txt

  • 將根目錄設置爲能夠訪問 server.use(express.static(__dirname)),這個太不安全了,根目錄全部文件都暴露了

  • 在server.js文件里加上處理.txt文件的方法

server.get('*', (req, res) => {
const express = require('express')
const next = require('next')
const server = express()

const util = require("./static/js/util");

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
  .then(() => {

    server.get('/:city', (req, res) => {
        const txt = req.url; // 獲取請求路徑
        // 這裏須要作一個請求攔截判斷
        if(txt.indexOf(".txt") > 0){
          res.sendFile(__dirname + `/${txt}`);
        }
        const actualPage = util.isMobile(req) ? '/mindex' : '/index';
        const queryParams = { city: req.params.city};
        console.log(req.params.city, actualPage)
        app.render(req, res, actualPage, queryParams);
    });

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

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

    server.get('/:city/posts/:id', (req, res) => {
      return app.render(req, res, '/posts', { id: req.params.id })
    })

    // server.get('*', (req, res) => {
    //   return handle(req, res)
    // })

    server.get('*', (req, res) => {
      const txt = req.url; // 獲取請求路徑
      if(txt.indexOf(".txt") > 0){
        res.sendFile(__dirname + `/${txt}`);
      } else {
        return handle(req, res)
      }
    })

    server.listen(port, (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
})
複製代碼

在根目錄建立一個文件MP_verify_HjspU6daVebgWsvauH.txt測試下,瀏覽器訪問結果

需求八:部分Http請求代理處理

在server.js 文件裏添加以下代碼,當訪問/proxy/*路由時自動匹配代理到http://api.test-proxy.com

const proxyApi = "http://api.test-proxy.com"
server.use('/proxy/*', proxy({ 
  target: proxyApi, 
  changeOrigin: true 
}));
複製代碼

需求九:類重定向處理

當訪問localhost:3000/winyh路由時須要顯示 ww.redirect.com/about?type_… 頁面上的內容。 先安裝工具 cnpm i urllib --save

// 修改server.js 文件代碼

server.get('/winyh', async (req, res) => {
  const agent  = req.header("User-Agent");
  const result = await urllib.request(
    'http://ww.redirect.com/about?type_id=3', 
    {   
      method: 'GET',
      headers: {
        'User-Agent': agent
      },
    })
    res.header("Content-Type", "text/html;charset=utf-8");
    
    res.send(result.data);// 須要獲取result.data 否則顯示到前臺的數據時二進制 45 59 55 
})
複製代碼

需求十:本地數據模擬

上述文章有提到,已實現

需求十一:項目部署方式

主要是編寫Dockerfile文件,本地VsCode能夠啓動容器調試,後續演示

FROM mhart/alpine-node

WORKDIR /app
COPY . .

RUN yarn install
RUN yarn build

EXPOSE 80

CMD ["node", "server.js"]
複製代碼

最後總結:

  • 還有不少細節能夠完善,但願能幫到你們,也但願評論交流,相互學習。
  • 後面會把數據請求改爲Graphql.js方式。個人這篇文章裏GraphQL.js 與服務端交互的新方式寫了一個GraphQL入門演示
相關文章
相關標籤/搜索