node ssr 如何做到首次加载应用时通过服务端渲染,在后续的路由跳转中走客户端渲染

106 阅读1分钟

以 express + vue 为例,实现分为两步

1.服务端

const path = require('path')
const fs = require('fs')
const express = require('express')
const app = express()
const port = 3000

app.use('*', (req, res) => {
    
    const url = req.originalUrl
    console.log('url===>', url)

    let html = fs.readFileSync(path.resolve('./index.html'), 'utf-8')
    html = html.replace('__time__', new Date())
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

app.use 方法的第一个参数支持 4 种类型:

  • A string representing a path. 代表路径的字符串
  • A path pattern. 路径模式匹配
  • A regular expression pattern to match paths. 匹配路径的正则表达式
  • An array of combinations of any of the above. 以上3种的任意组合数组

以上代码使得访问应用的任何地址都会渲染 index.html,在真实 ssr 环境中,在响应 html 之前会调用事先写好的 entry-server.js 中导出的 render 方法:

clipboard.png

2.客户端

跳转使用编程式导航 <router-link> 或 router.push\replace 等操作。

router-link 组件本质上应该时对 a 链接的封装,并对 a 链接进行点击拦截,如果不拦截走的还是服务器渲染即走进了 app.use('*', function() { ... }) 中间件函数。在拦截之后则实现了客户端的 csr 渲染。

完整 demo 如下:

//server.js
const path = require('path')
const fs = require('fs')
const express = require('express')
const app = express()
const port = 3000

app.use('*', (req, res) => {
    
    const url = req.originalUrl
    console.log('url===>', url)

    let html = fs.readFileSync(path.resolve('./index.html'), 'utf-8')
    html = html.replace('__time__', new Date())
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <a href="/" onclick="routeTo()">Home</a>
    <a href="/about" onclick="routeTo()">about</a>

    <div id="app">
        <h1>hello express~~~~ __time__</h1>
        <p>
            <!--使用 router-link 组件进行导航 -->
            <!--通过传递 `to` 来指定链接 -->
            <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
            <router-link to="/">Go to Home</router-link>
            <router-link to="/about">Go to About</router-link>
        </p>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <router-view></router-view>
    </div>
</body>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<script>
    // 1. 定义路由组件.
    // 也可以从其他文件导入
    const Home = { template: '<div>Home</div>' }
    const About = { template: '<div>About</div>' }

    // 2. 定义一些路由
    // 每个路由都需要映射到一个组件。
    // 我们后面再讨论嵌套路由。
    const routes = [
        { path: '/', component: Home },
        { path: '/about', component: About },
    ]

    // 3. 创建路由实例并传递 `routes` 配置
    // 你可以在这里输入更多的配置,但我们在这里
    // 暂时保持简单
    const router = VueRouter.createRouter({
        // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
        history: VueRouter.createWebHistory(),
        routes, // `routes: routes` 的缩写
    })

    // 5. 创建并挂载根实例
    const app = Vue.createApp({})
    //确保 _use_ 路由实例使
    //整个应用支持路由。
    app.use(router)

    app.mount('#app')

    //__________________________________________________________
    function routeTo (event) {
        event = event || window.event
        event.preventDefault()
        console.log(event)
        console.log('渲染对应页面')
    }
</script>

</html>