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

winyh發表於2019-05-25

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

第一次在掘金上釋出文章,本著學習的態度,將自己運用Next.js開發服務端渲染的專案復原總結出來,鞏固知識點,也可以跟同行探討下技術。(文章不斷完善中...)

1.專案背景

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

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頁面,傳送到前臺。

4.Next.js關鍵點

檔案系統:

Next檔案系統規定,在pages資料夾下每個*.js 檔案將變成一個路由,自動處理和渲染

新建 ./pages/index.js 到你的專案中, 專案執行後可以通過 localhost:3000/index 路徑訪問到頁面。同理 ./pages/second.js 可以通過localhost:3000/second訪問到

靜態檔案服務:

如圖片,字型,js工具類

在根目錄下新建資料夾叫static。程式碼可以通過/static/來引入相關的靜態資源 不要自定義靜態資料夾的名字,只能叫static ,因為只有這個名字 Next.js 才會把它當作靜態資源

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

資料獲取:

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

建立一個有狀態、生命週期或有初始資料的 React 元件

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 方法跳轉)時,客戶端才會執行getInitialProps

劃重點: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.js 服務端渲染框架實戰
用瀏覽器除錯工具開啟檢視原始碼,可以看到 根容器_next 下有div元素渲染進去了,資料很多時就會有豐富的利於搜尋引擎爬取html程式碼。

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

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

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檔案中,這裡暫時值說下方法,後續寫實際程式碼實現。

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

需求二:路由美化

通過自定義服務端路由實現路由自定義美化功能。例如在武漢(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

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

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

先安裝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>
複製程式碼

至此頁面上就可以看到列印的資料和展示的資料了

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

下一步根據使用者地理位置確定顯示的頁面城市,解決方案步驟如下【暫時只說方法,稍後完善程式碼】

  • 先請求百度開放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}`)
    })
})
複製程式碼

用瀏覽器開啟除錯皮膚的移動端模式就可以自動渲染移動端頁面了

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

需求五: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測試下,瀏覽器訪問結果

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

需求八:部分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入門演示

相關文章