10月初有幸接到公司官網改版需求,要求採用服務端渲染模式對原網站進行seo優化。javascript
因爲團隊一直使用的vue技術棧,因此我第一時間想到的就是採用vue 服務端渲染(SSR)來實現該需求,即能減小團隊其餘成員後期維護的成本,又能把現有其餘項目封裝好的內容稍微改改就能直接copy過來使用,大大節省了開發時間(除去ui中途調整,總體改造時間只花了2-3天)。css
一、原公司網站(改版前),採用 vue(SPA) 模式開發html
網址:www2.nicomama.com/ 暫時已經關閉vue
二、新公司網站(改版後),採用 Vue 服務端渲染(SSR) 模式開發java
網址:www.nicomama.com/node
固然直接從瀏覽器打開只能看到兩個網站只是在風格上和界面上作了升級。webpack
接下來讓咱們看看兩個網站區別在哪裏,使用chrome瀏覽器分別打開兩個網站,右擊查看源碼。ios
一、改版前git
二、改版後github
能夠明顯看出改版後網站源碼增長了不止幾倍之多,簡而言之服務端渲染的模式就是:在請求一個網址的時候,服務端收到請求以後把html的內容先生成好而後再返回給瀏覽器。這樣子搜索引擎就能夠經過你返回的a標籤抓取到網站的其餘頁面了,依此類推搜索引擎就能夠收錄網站的全部(暴露出來的)路徑了,後面還會給你們看一下網站改版後的一些搜錄數據變化。
在看下面內容以前建議你們先去看下《Vue SSR指南》,這是文檔地址ssr.vuejs.org/zh/#%E4%BB%…
接下來這一塊Vue SSR的概念介紹和好處壞處對比的內容是對文檔說起的概念摘要,看過文檔的能夠直接忽略~
簡而言之就是將原本要放在瀏覽器執行建立的組件,放到服務端先建立好,而後生成對應的html將它們直接發送到瀏覽器,最後將這些靜態標記"激活"爲客戶端上徹底可交互的應用程序。
更好的 SEO,因爲搜索引擎爬蟲抓取工具能夠直接查看徹底渲染的頁面。
更快的內容到達時間(time-to-content),特別是對於緩慢的網絡狀況或運行緩慢的設備。
1)開發條件所限。瀏覽器特定的代碼,只能在某些生命週期鉤子函數(lifecycle hook)中使用;一些外部擴展庫(external library)可能須要特殊處理,才能在服務器渲染應用程序中運行。
2)涉及構建設置和部署的更多要求。與能夠部署在任何靜態文件服務器上的徹底靜態單頁面應用程序(SPA)不一樣,服務器渲染應用程序,須要處於 Node.js server 運行環境。
3)更多的服務器端負載。在 Node.js 中渲染完整的應用程序,顯然會比僅僅提供靜態文件的 server 更加大量佔用 CPU 資源(CPU-intensive - CPU 密集),所以若是你預料在高流量環境(high traffic)下使用,請準備相應的服務器負載,並明智地採用緩存策略。
相比vue SPA(單頁應用),Vue增長了一些擴展工具,首先咱們來看一下比較重要的一個工具vue-server-renderer,從名字能夠看出它是在服務端渲染的時候用的。
讓咱們來看一下它的功能和用法
一、建立一個空項目 mkdir vuessr && cd vuessr
二、運行 npm init
進行初始化
三、安裝咱們須要的依賴 cnpm install vue vue-server-renderer --save
四、建立index.js
代碼以下:
// 第 1 步:建立一個 Vue 實例
const Vue = require('vue')
const app = new Vue({
template: `<div>Hello World</div>`
})
// 第 2 步:建立一個 renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:將 Vue 實例渲染爲 HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
複製代碼
五、運行 node index.js
能夠看到在控制檯輸出了
<div data-server-rendered="true">Hello World</div>
複製代碼
咱們再將生成好的html放到指定的html模版裏面再返回到瀏覽器不就實現服務端渲染功能了?
六、安裝依賴 cnpm install express --save
七、建立app.js
代碼以下:
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>訪問的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body>${html}</body> </html> `)
})
})
server.listen(8080)
複製代碼
八、運行node app.js
九、打開瀏覽器輸入http://localhost:8080/
發現咱們的內容已經顯示出來了,若是有同窗發現有中文亂碼的問題,能夠設置一下編碼:
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
//設置編碼
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.end(` <!DOCTYPE html> <html lang="zh"> <head><title>Hello</title></head> <body>${html}</body> </html> `)
})
複製代碼
而後從新運行一下試試。
十、固然直接採用字符串來拼接html內容是很是不優雅的,並且容易出錯,咱們能夠改寫成模版形式,建立文件index.template.html
代碼以下:
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
複製代碼
注意:<!--vue-ssr-outlet-->
註釋--這裏將是應用程序 HTML 標記注入的地方。
十一、修改app.js
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8')
})
//***
renderer.renderToString(app, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.end(html)
})
複製代碼
十二、從新運行一下試試,看看是否能正常運行。
1三、它還支持模板插值操做,修改文件index.template.html
,代碼以下:
<html>
<head>
<!-- 使用雙花括號(double-mustache)進行 HTML 轉義插值(HTML-escaped interpolation) -->
<title>{{ title }}</title>
<!-- 使用三花括號(triple-mustache)進行 HTML 不轉義插值(non-HTML-escaped interpolation) -->
{{{ meta }}}
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
複製代碼
修改文件index.js
,須要調整的代碼以下:
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8')
})
const context = {
title: 'hello vuessr',
meta: ` <meta charset="utf-8"> `
}
server.get('*', (req, res) => {
const app = new Vue({
data: {
url: req.url
},
template: `<div>訪問的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, context, (err, html) => {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.end(html)
})
})
server.listen(8080)
複製代碼
1四、再次從新運行一下試試,看看是否能正常運行。如下是查看瀏覽器源碼的截圖,能夠看到模板定義的內容已經替換成對應的數據了。
到此爲止咱們已經實現一個最基礎的vue 服務端渲染的工程了。
是否是很簡單?不過要從頭搭建整套Vue SSR仍是一個很是繁瑣的過程。
後續我也會給你們介紹VueSSR開箱即用的框架Nuxt.js,其實它就是對Vue SSR的一個封裝,概念仍是同樣的。固然再沒了解Vue SSR的基本實現過程,直接去使用Nuxt.js仍是會一頭霧水(大神忽略~)。
從圖中咱們能夠大體看出vue ssr的運做過程:咱們首先經過 webpack 打包 - 服務器須要「服務器 bundle」而後用於服務器端渲染(SSR),而「客戶端 bundle」會發送給瀏覽器,用於混合靜態標記。有了理論基礎以後讓咱們一塊兒實踐一下吧。
選擇vue-cli的webpack模版生成的代碼基本上能夠複用到VueSSR能夠省去繁瑣的webpack配置的過程。
一、安裝vue-cli,參考文檔cli.vuejs.org/zh/guide/cl…,這裏就不作過多介紹,我採用的版本是2.9.6。
二、運行 vue init webpack vuessr-vuecli
選擇配置以下
? Project name vuessr-vuecli
? Project description A Vue.js project
? Author taoxinhua <taoxhsmile@163.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm
複製代碼
三、進入文件夾,運行 cnpm run dev
;看下項目是否能正常運行
四、接下來讓咱們一塊兒對代碼進行改造吧~
新增
src/components
--about.vue
--home.vue
複製代碼
about.vue
<template>
<div class="hello">
<h1>這是關於我頁面</h1>
</div>
</template>
複製代碼
home.vue
<template>
<div class="hello">
<h1>這是首頁</h1>
</div>
</template>
複製代碼
修改
src/router
--index.js
複製代碼
index.js
import Vue from 'vue'
import Router from 'vue-router'
import home from '@/components/home'
import about from '@/components/about'
Vue.use(Router)
export default () => {
return new Router({
mode:'history',
routes: [
{
path: '/',
name: 'home',
component: home
},
{
path: '/about',
name: 'about',
component: about
}
]
})
}
複製代碼
新增
src
--app.js
--App.vue
--entry-client.js 客戶端打包入口文件
--entry-server.js 服務端打包入口文件
複製代碼
app.js
import Vue from 'vue'
import createRouter from './router'
import App from './App.vue'
// 實例 每次請求都會建立新的實例
export default (context) => {
const router = createRouter()
const app = new Vue({
router,
components: { App },
template: '<App/>'
})
return { router, app }
}
複製代碼
App.vue
<template>
<div id="app">
<router-link to="/">首頁</router-link>
<router-link to="/about">關於我</router-link>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
複製代碼
entry-client.js
import createApp from './app'
let { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
複製代碼
entry-server.js
// 服務端這邊,須要把訪問的路徑給到vue-router
import createApp from './app'
// 外面的express服務使用 {url: / /about}
export default (context) => {
return new Promise((resolve, reject) => {
let { app, router } = createApp(context);
router.push(context.url);
router.onReady(() => {
// 訪問路徑,可定匹配到組件
let matchedCompoents = router.getMatchedComponents();
if (!matchedCompoents.length) {
return reject({ code: 404 })
}
resolve(app)
}, reject)
})
}
複製代碼
新增
build/dev-server.js
server.js
複製代碼
dev-server.js
const serverConf = require('./webpack.server.conf');
const webpack = require('webpack')
const fs =require('fs')
const path = require('path');
const Mfs = require('memory-fs')
const axios = require('axios')
module.exports = (cb) => {
const webpackComplier = webpack(serverConf);
var mfs = new Mfs();
webpackComplier.outputFileSystem = mfs;
webpackComplier.watch({}, async (error, stats) => {
if (error) return console.log(error);
stats = stats.toJson();
stats.errors.forEach(err => console.log(err))
stats.warnings.forEach(err => console.log(err))
// server Bundle json文件
let serverBundlePath = path.join(
serverConf.output.path,
'vue-ssr-server-bundle.json'
)
let serverBundle = JSON.parse(mfs.readFileSync(serverBundlePath, "utf-8"))
//console.log(serverBundle)
// client Bundle json文件
let clientBundle = await axios.get('http://localhost:8080/vue-ssr-client-manifest.json')
// 模板
let template = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf-8');
cb(serverBundle, clientBundle, template)
})
}
複製代碼
server.js
const devServer = require('./build/dev-server');
const express = require('express');
const app = express();
const vueRenderer = require('vue-server-renderer')
const path = require('path');
app.get('*', async (req, res) => {
res.status(200);
res.setHeader('Content-Type', 'text/html;charset=utf-8;')
devServer(function(serverBundle,clientBundle,template){
let renderer = vueRenderer.createBundleRenderer(serverBundle,{
template,
clientManifest: clientBundle.data,
runInNewContext: false
})
renderer.renderToString({ url: req.url }).then((html) => {
res.end(html)
}).catch(err => console.log(err))
})
})
app.listen(5000, () => {
console.log('啓動成功')
})
複製代碼
修改
build/webpack.dev.conf.js
複製代碼
webpack.dev.conf.js
//...忽略
const portfinder = require('portfinder')
//新增內容-start
const vueSSRClientPlugin = require('vue-server-renderer/client-plugin')
//新增內容-end
const HOST = process.env.HOST
//...忽略
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
]),
//新增內容-start
new vueSSRClientPlugin()
//新增內容-end
//...忽略
複製代碼
新增文件
build/webpack.server.conf.js
複製代碼
webpack.server.conf.js
const webpack = require('webpack');
const merge = require('webpack-merge')
const base = require('./webpack.base.conf');
const vueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const webpackNodeExternals = require('webpack-node-externals')
module.exports = merge(base,{
target: 'node',
devtool: 'source-map',
entry: './src/entry-server.js',
output: {
filename: 'server-bundle.js',
libraryTarget: 'commonjs2'
},
externals: [webpackNodeExternals({
whitelist: /\.css$/
})],
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"devlopment"',
'process.env.VUE_ENV': '"server"'
}),
new vueSSRServerPlugin()
]
})
複製代碼
修改
index.html
複製代碼
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>vuessr-vuecli</title>
</head>
<body>
<div id="app">
<!--vue-ssr-outlet-->
</div>
<!-- built files will be auto injected -->
</body>
</html>
複製代碼
修改
package.json
增長server腳本
複製代碼
package.json
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js",
"server": "node server.js"
},
複製代碼
到此爲止咱們的文件都已經調整完畢了,讓咱們先來運行一下看看吧。
cnpm run dev
cnpm run server
複製代碼
能夠看到咱們的程序已經正常運行了,再來看看網頁源代碼是不是經過服務端渲染完畢以後再返回的。
沒錯到這一步咱們算是大功高成了,接下來咱們回過頭來看看整個項目的運行原理吧~ 畢竟這纔是重點。
首先回想一下前面的運行原理圖,第一步是否是先經過webpack分別打包出給服務端用的bundle和客戶端用的bundle。
咱們運行 cnpm run dev
實際上就是用來生成客戶端用的bundle。這一步比較簡單,回想一下咱們是否是調整了 webpack.dev.conf.js
增長了一個插件 vue-server-renderer/client-plugin
就是用來生成客戶端用的bundle, 只不過咱們並無直接把這個bundle生成具體的文件,而是放在了緩存中,咱們能夠直接經過瀏覽器訪問 http://localhost:8080/vue-ssr-client-manifest.json
查看到這份json文件。
其實它也生成在緩存中,build/dev-server.js
首先咱們經過 webpackComplier.outputFileSystem = mfs;
修改了webpack的輸出形式(改爲輸出到緩存中),而後在從緩存中拿到該文件
let serverBundlePath = path.join(
serverConf.output.path,
'vue-ssr-server-bundle.json'
)
let serverBundle = JSON.parse(mfs.readFileSync(serverBundlePath, "utf-8"))
複製代碼
server.js
devServer(function(serverBundle,clientBundle,template){
let renderer = vueRenderer.createBundleRenderer(serverBundle,{
template,
clientManifest: clientBundle.data,
runInNewContext: false
})
renderer.renderToString({ url: req.url }).then((html) => {
res.end(html)
}).catch(err => console.log(err))
})
複製代碼
能夠看到咱們將拿到的serverBundle(服務端Bundle),clientBundle(客戶端Bundle),template(index.html模版) 最終交由vue-server-renderer
來進行最終處理。
大功高成~ 原理大概就是這樣,代碼的細節還須要你們自行去查看和消化
在進入下一個環節以前,仍是建議你們先去看官方文檔 zh.nuxtjs.org/guide/
簡而言之Nuxt.js就是Vue SSR的一個開箱即用的框架。安裝好就能夠直接寫業務代碼,而不須要作過多的配置。
既然是一個框架,那咱們的就要按照它的規則來配置和寫代碼,前面提供的官方文檔提供了很是詳細的介紹。這裏就不作過多介紹,咱們直接進行實戰吧。 首先咱們先建立一個新項目。
在改造官網期間恰好Nuxt.js 2.0發佈了,看了一下改動仍是比較多了,爲了確保項目正常上線,決定仍是先採用穩定的1.0+版本進行編碼(畢竟新版本方發佈多少會有一點問題)。如下也是針對1.0+版本開發過程當中遇到的一些問題,你們能夠借鑑如下。
如下內容主要針對實戰過程當中遇到的一些問題來進行分享:
第一步讓咱們先建立一個新項目吧,安裝文檔地址:zh.nuxtjs.org/guide/insta… 咱們採用 create-nuxt-app
命令來安裝,運行
npx create-nuxt-app vuessr-nuxt
複製代碼
或者
cnpm install -g create-nuxt-app
create-nuxt-app vuessr-nuxt
複製代碼
選擇配置以下
? Project name vuessr-nuxt
? Project description My unreal Nuxt.js project
? Use a custom server framework express
? Use a custom UI framework none
? Choose rendering mode Universal
? Use axios module yes
? Use eslint no
? Use prettier no
? Author name taoxinhua
? Choose a package manager npm
複製代碼
而後進入進入項目運行,cnpm run dev
看看項目是否能正常運行,若是不能運行,第一步先檢查如下node版本,個人node版本是v8.12.0。第二步若是node升級以後仍是不行,運行cnpm install
看下是否有依賴包少安裝了。若是這兩步還不能解決問題,你們能夠在評論區提問。
打開瀏覽器進入 http://127.0.0.1:3000
,能夠看到咱們的項目已經能夠運行了。沒錯就是這麼簡單
一、使用axios遇到的坑。
在使用1.0+版本開發過程當中發現每次修改文件,服務端代碼都會從新加載並執行一遍,若是直接把axios的鉤子函數放到plugin中去執行,會發現每次修改完畢以後鉤子函數都會重複添加一次,致使參數重複被處理,好比我發送請求以前要把傳遞的data轉成字符串的形式。會發現下面代碼的config.data會出現重複累加的狀況。
axios.interceptors.request.use(function (config) {
let data = config.data || {};
let auth = buildHttpHeaders();
config.url = getApiUrl(config.url);
config.data = qs.stringify({
data: JSON.stringify(data),
auth: JSON.stringify(auth)
})
//在請求發出以前進行一些操做
return config;
}, function (err) {
//Do something with request error
return Promise.reject(err);
});
複製代碼
建立文件
assets/js/config/config-axios.js
import Axios from 'axios'
import qs from 'qs'
import { getUUID } from '~/assets/js/tools/index'
import { getApiUrl } from '~/assets/js/config/config-urls.js'
function buildHttpHeaders() {
return {
"x-user-id": '',
"x-access-token": '',
"x-platform": 'pc',
"x-client-token": getUUID(),
"x-system-version": '10.1.1',
"x-client-version": '2.0.1',
"x-method-version": '1.0',
"x-network-type": '3g',
}
}
let axios = Axios.create();
// 添加一個請求攔截器
axios.interceptors.request.use(function (config) {
let data = config.data || {};
let auth = buildHttpHeaders();
config.url = getApiUrl(config.url);
config.data = qs.stringify({
data: JSON.stringify(data),
auth: JSON.stringify(auth)
})
//在請求發出以前進行一些操做
return config;
}, function (err) {
//Do something with request error
return Promise.reject(err);
});
//添加一個響應攔截器
axios.interceptors.response.use(function (res) {
//在這裏對返回的數據進行處理
return res.data;
}, function (err) {
//Do something with response error
return Promise.reject(err);
});
export default axios
複製代碼
每次經過Axios.create返回一個全新的axios實例。
以上是項目裏面拷貝出來的代碼,沒法單獨運行,你們能夠針對各自的需求進行相應調整。
plugins/axios.js
import Vue from 'vue';
import axios from '~/assets/js/config/config-axios'
Vue.prototype.$$axios = axios;
export default ({ app }, inject) => {
// Set the function directly on the context.app object
app.$$axios = axios;
}
複製代碼
這樣每次代碼進行熱更新就不會出現上面的問題了。
這裏還把axios掛到了Vue的原型下面和app對象下面,頁面不須要引用axios就進行調用了。
二、asyncData
在nuxt中組件文件申明的asyncData方法會被忽略,因此全部數據的加載都要放到對應page的asyncData中。
三、如何增長額外的全局js文件
咱們能夠經過定製模版來處理,在根目錄下建立app.html
模版文件,nuxt默認的模版爲
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
複製代碼
好比咱們要增長搜索引擎的收錄代碼咱們能夠
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
{{ HEAD }}
<!-- 百度自動收錄腳本 -->
<script>
(function(){
var bp = document.createElement('script');
var curProtocol = window.location.protocol.split(':')[0];
if (curProtocol === 'https') {
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
}
else {
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(bp, s);
})();
</script>
<!-- 360自動搜錄腳本 -->
<script>(function(){
var src = (document.location.protocol == "http:") ? "http://js.passport.qihucdn.com/11.0.1.js******":"https://jspassport.ssl.qhimg.com/11.0.1.js?******";
document.write('<script src="' + src + '" id="sozz"><\/script>');
})();
</script>
<!-- 百度統計 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?******";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
複製代碼
四、讓項目支持ip訪問 在package.json
文件中增長以下配置便可
"config": {
"nuxt": {
"host": "0.0.0.0",
"port": "3000"
}
},
複製代碼
五、正式環境、測試環境區分配置
package.json
"scripts": {
"dev": "cross-env API_ENV=local nuxt",
"build_beta": "cross-env API_ENV=beta nuxt build",
"build_pro": "cross-env API_ENV=pro nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint"
},
複製代碼
添加build_beta、build_pro,而後經過cross-env來設置環境
nuxt.config.js
env: {
API_ENV: process.env.API_ENV
},
複製代碼
這樣就能夠在代碼中,經過process.env.API_ENV
來獲取到環境變量的值了。
六、經過pm2啓動項目
pm2-config.json
{
"apps": [
{
"name": "nicomama-site",
"script": "npm",
"args" : "start",
"watch": [".nuxt"]
}
]
}
複製代碼
七、項目部署
先運行 cnpm run build_beta
或者 cnpm run build_pro
進行打包,而後經過運行 pm2 start pm2-config.json 來運行項目
後續有空再給你們理一份項目的結構
能夠看到網站的文章已經基本上都被谷歌收錄到了,至於百度收錄的數據比較少多是由於網站總體改版的緣由形成的,搜索引擎須要必定時間去更新,這個還有待觀察。
那麼就先到這裏了
by:Tao