vue 服务器渲染 vue项目改造SSR(服务端渲染)
大家好,如果您还对vue 服务器渲染不太了解,没有关系,今天就由本站为大家分享vue 服务器渲染的知识,包括vue项目改造SSR(服务端渲染)的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!
vue项目改造SSR(服务端渲染)
缺点:1、SEO问题
2、首屏速度问题
3、消耗性能的问题
优点:
1、更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
2、首屏渲染速度快
SSR简单来说就是将页面在服务端渲染完成后在客户端直接展示。
index.template.html
server.js
vue项目是通过虚拟 DOM来挂载到html的,所以对spa项目,爬虫才会只看到初始结构。虚拟 DOM,最终要通过一定的方法将其转换为真实 DOM。虚拟 DOM也就是 JS对象,整个服务端的渲染流程就是通过虚拟 DOM的编译成完整的html来完成的。
需要通过Webpack打包生成两份bundle文件:
Client Bundle,给浏览器用。和纯Vue前端项目Bundle类似
Server Bundle,供服务端SSR使用,一个json文件
不管项目先前是什么样子,是否是使用vue-cli生成的。都会有这个构建改造过程。在构建改造这里会用到 vue-server-renderer库,这里要注意的是 vue-server-renderer版本要与Vue版本一样。
打包之后目录结构
vue.config.js
index.template.html
打包成客户端和服务器端
启动node服务
github地址: https://github.com/wang12321/SSR
如何操作vue项目打包给服务器
这次给大家带来如何操作vue项目打包给服务器,vue项目打包给服务器的注意事项有哪些,下面就是实战案例,一起来看一下。
当我们将 vue项目完成后,面临的就是如何将项目进行打包上线,放到服务器中。我使用的是 vue-cli(simple)脚手架,所以就讲一下如何将项目进行打包,并放到 tomcat上。
如果是 vue-cli(非 simple脚手架),这篇文章可能有点帮助。地址链接:vue-cli如何打包上线
先来描述一下期间遇到的问题有哪些:
1、打包后将 dist文件夹和 index.html放到 tomcat,在浏览器中访问时,出现空白页,f12提示 404。
2、打包好的静态资源均是绝对路径,无法引入进项目,也是 404。
1、项目目录结构
这是打包后的,所以有 dist文件夹,打包方式:npm run build。
2、webpack.config.js
这里只是一小部分,因为这边最关键的就是 publicPath,下面会提,这边可以解决静态资源 404无法引入的问题。
3、npm run build打包后的文件。
npm run build打包后生成一个 dist文件夹,这里面的目录:
我对 webpack打包工具的原理不是很清楚,所以文件夹应该生成什么不是了解。我这边是这样子的。主要是一个主要的 build.js,因为我们的 index.html引入的就是这个 js文件。还有一些图片文件和 ElementUI生成的 ttf和 woff。
4、如何放到服务器中。
接下来就是需要将生成的 dist文件夹和 index.html文件放到服务器中,只需要这两个。首先我将这两个文件放在同一个文件夹中,我命名为 gas(随意)。
然后将文件夹放到 tomcat中,我将文件夹放到 tomcat的 webapps文件夹目录下:
ok部署完成,启动 tomcat,你会发现显示一个空白页,一些静态资源都是 404。
5、解决空白页和静态资源无法引入的问题。
1、首先空白页的问题,可以重 f12中看出来都是绝对路径的原因,而我们打包后,应该的引入路径是相对路径,这时我们需要的是修改 index.html页面。
看一下没改之前的:
看我/dist/build.js引用的是绝对路径,这就导致了在 tomcat去访问 index.html页面时,报404。我们需要将路径变成相对路径./dist/build/。多一个点,很关键。好了到这里应该主页面可以显示了。
但是你会发现,我的静态资源,我的图片(不包括 img形式的引入),例如我在 css中 background:url()的图片显示404。
2、解决静态资源失效的问题
这就需要修改我们的 webpack.config.js中的 publicPath了,默认的 vue-cli脚手架环境搭建好后,publicPath是这样的:
可以看到我们的路径是:/dist/。所以这时候我们如果打开页面,静态资源的路径都会是这样子的,并且报错404:http://10.0.0.181:8088/dist/bg.png?fe9b889cea51978538ce352593be0573
显然可以看出和我们想要的路径不一致,上面我贴出来的在 tomcat的文件目录中我将 dist和 index.html都放进了一个 gas的文件夹中。所以正确的路径应该是这样的:
http://10.0.0.181:8088/gas/dist/bg.png?fe9b889cea51978538ce352593be0573
看出区别了吗!
解决:
所以我需要改变一下 webpack.config.js中的输出路径 publicPath:/gas/dist/。将最外层的文件夹路径加进去,这样就可以将静态资源引入进项目了。
ok,到现在为止,最主要的两个问题解决了,一个是 index.html空白页,另一个是静态资源路径不正确的问题。
6、index.html页面中的link和 srcipt引用的资源失效问题:
原因还是路径的地址不对:
妥协的解决方法是:将自己引用的资源手动放到打包出来的 dist文件夹内,然后在 index.html中按照 dist的相对路径进行引用。
代码中的 icon.ico就是我手动将 icon图标放到 dist文件夹中,然后按照对应的引用路径进行引用。其他的 css和 js引用一样。
7、待解决的问题:
1、在我的项目中,使用了 ElementUI框架,但是在打包放到服务器中后,发现按钮样式变了,所有的 padding失效,所有我只能自己手动进行添加样式。
2、在我的 index.html中如果引入 link css文件时,还是没办法引入相对路径,所以我将 css样式都放到了各自的组件中的 style中了,其他的一般都是用 npm注入依赖的形式进行安装。
8、网上搜索到的相关问题和解决方法。
1、求助!Vue项目用Webpack打包后放到服务器上,但访问是空白页?弄了好久了也不知道什么原因
2、vue项目中,npm run build生成的index.html文件只有放在根目录下打开才能生效,怎么解决?
3、Vue应用部署到服务器的正确方式
相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!
推荐阅读:
怎样使用React服务器端渲染
如何使用vue.js做出编辑菜谱功能
怎样使用React服务器端渲染
这次给大家带来怎样使用React服务器端渲染,使用React服务器端渲染的注意事项有哪些,下面就是实战案例,一起来看一下。
React提供了两个方法 renderToString和 renderToStaticMarkup用来将组件(Virtual DOM)输出成 HTML字符串,这是 React服务器端渲染的基础,它移除了服务器端对于浏览器环境的依赖,所以让服务器端渲染变成了一件有吸引力的事情。
服务器端渲染除了要解决对浏览器环境的依赖,还要解决两个问题:
前后端可以共享代码
前后端路由可以统一处理
React生态提供了很多选择方案,这里我们选用 Redux和 react-router来做说明。
Redux
Redux提供了一套类似 Flux的单向数据流,整个应用只维护一个 Store,以及面向函数式的特性让它对服务器端渲染支持很友好。
2分钟了解 Redux是如何运作的
关于 Store:
整个应用只有一个唯一的 Store
Store对应的状态树(State),由调用一个 reducer函数(root reducer)生成
状态树上的每个字段都可以进一步由不同的 reducer函数生成
Store包含了几个方法比如 dispatch, getState来处理数据流
Store的状态树只能由 dispatch(action)来触发更改
Redux的数据流:
action是一个包含{ type, payload}的对象
reducer函数通过 store.dispatch(action)触发
reducer函数接受(state, action)两个参数,返回一个新的 state
reducer函数判断 action.type然后处理对应的 action.payload数据来更新状态树
所以对于整个应用来说,一个 Store就对应一个 UI快照,服务器端渲染就简化成了在服务器端初始化 Store,将 Store传入应用的根组件,针对根组件调用 renderToString就将整个应用输出成包含了初始化数据的 HTML。
react-router
react-router通过一种声明式的方式匹配不同路由决定在页面上展示不同的组件,并且通过 props将路由信息传递给组件使用,所以只要路由变更,props就会变化,触发组件 re-render。
假设有一个很简单的应用,只有两个页面,一个列表页/list和一个详情页/item/:id,点击列表上的条目进入详情页。
可以这样定义路由,./routes.js
import React from'react';
import{ Route} from'react-router';
import{ List, Item} from'./components';
//无状态(stateless)组件,一个简单的容器,react-router会根据 route
//规则匹配到的组件作为 `props.children`传入
const Container=(props)=>{
return(
<p>{props.children}</p>
);
};
// route规则:
//- `/list`显示 `List`组件
//- `/item/:id`显示 `Item`组件
const routes=(
<Route path="/" component={Container}>
<Route path="list" component={List}/>
<Route path="item/:id" component={Item}/>
</Route>
);
export default routes;从这里开始,我们通过这个非常简单的应用来解释实现服务器端渲染前后端涉及的一些细节问题。
Reducer
Store是由 reducer产生的,所以 reducer实际上反映了 Store的状态树结构
./reducers/index.js
import listReducer from'./list';
import itemReducer from'./item';
export default function rootReducer(state={}, action){
return{
list: listReducer(state.list, action),
item: itemReducer(state.item, action)
};
}rootReducer的 state参数就是整个 Store的状态树,状态树下的每个字段对应也可以有自己的reducer,所以这里引入了 listReducer和 itemReducer,可以看到这两个 reducer的 state参数就只是整个状态树上对应的 list和 item字段。
具体到./reducers/list.js
const initialState= [];
export default function listReducer(state= initialState, action){
switch(action.type){
case'FETCH_LIST_SUCCESS': return [...action.payload];
default: return state;
}
}list就是一个包含 items的简单数组,可能类似这种结构:[{ id: 0, name:'first item'},{id: 1, name:'second item'}],从'FETCH_LIST_SUCCESS'的 action.payload获得。
然后是./reducers/item.js,处理获取到的 item数据
const initialState={};
export default function listReducer(state= initialState, action){
switch(action.type){
case'FETCH_ITEM_SUCCESS': return [...action.payload];
default: return state;
}
}Action
对应的应该要有两个 action来获取 list和 item,触发 reducer更改 Store,这里我们定义 fetchList和 fetchItem两个 action。
./actions/index.js
import fetch from'isomorphic-fetch';
export function fetchList(){
return(dispatch)=>{
return fetch('/api/list')
.then(res=> res.json())
.then(json=> dispatch({ type:'FETCH_LIST_SUCCESS', payload: json}));
}
}
export function fetchItem(id){
return(dispatch)=>{
if(!id) return Promise.resolve();
return fetch(`/api/item/${id}`)
.then(res=> res.json())
.then(json=> dispatch({ type:'FETCH_ITEM_SUCCESS', payload: json}));
}
}isomorphic-fetch是一个前后端通用的 Ajax实现,前后端要共享代码这点很重要。
另外因为涉及到异步请求,这里的 action用到了 thunk,也就是函数,redux通过 thunk-middleware来处理这类 action,把函数当作普通的 action dispatch就好了,比如 dispatch(fetchList())
Store
我们用一个独立的./store.js,配置(比如 Apply Middleware)生成 Store
import{ createStore} from'redux';
import rootReducer from'./reducers';
// Apply middleware here
//...
export default function configureStore(initialState){
const store= createStore(rootReducer, initialState);
return store;
}react-redux
接下来实现<List>,<Item>组件,然后把 redux和 react组件关联起来,具体细节参见 react-redux
./app.js
import React from'react';
import{ render} from'react-dom';
import{ Router} from'react-router';
import createBrowserHistory from'history/lib/createBrowserHistory';
import{ Provider} from'react-redux';
import routes from'./routes';
import configureStore from'./store';
// `INITIAL_STATE`来自服务器端渲染,下一部分细说
const initialState= window.INITIAL_STATE;
const store= configureStore(initialState);
const Root=(props)=>{
return(
<p>
<Provider store={store}>
<Router history={createBrowserHistory()}>
{routes}
</Router>
</Provider>
</p>
);
}
render(<Root/>, document.getElementById('root'));至此,客户端部分结束。
Server Rendering
接下来的服务器端就比较简单了,获取数据可以调用 action,routes在服务器端的处理参考 react-router server rendering,在服务器端用一个 match方法将拿到的 request url匹配到我们之前定义的 routes,解析成和客户端一致的 props对象传递给组件。
./server.js
import express from'express';
import React from'react';
import{ renderToString} from'react-dom/server';
import{ RoutingContext, match} from'react-router';
import{ Provider} from'react-redux';
import routes from'./routes';
import configureStore from'./store';
const app= express();
function renderFullPage(html, initialState){
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p id="root">
<p>
${html}
</p>
</p>
<script>
window.INITIAL_STATE=${JSON.stringify(initialState)};
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`;
}
app.use((req, res)=>{
match({ routes, location: req.url},(err, redirectLocation, renderProps)=>{
if(err){
res.status(500).end(`Internal Server Error${err}`);
} else if(redirectLocation){
res.redirect(redirectLocation.pathname+ redirectLocation.search);
} else if(renderProps){
const store= configureStore();
const state= store.getState();
Promise.all([
store.dispatch(fetchList()),
store.dispatch(fetchItem(renderProps.params.id))
])
.then(()=>{
const html= renderToString(
<Provider store={store}>
<RoutingContext{...renderProps}/>
</Provider>
);
res.end(renderFullPage(html, store.getState()));
});
} else{
res.status(404).end('Not found');
}
});
});服务器端渲染部分可以直接通过共用客户端 store.dispatch(action)来统一获取 Store数据。另外注意 renderFullPage生成的页面 HTML在 React组件 mount的部分(<p id="root">),前后端的 HTML结构应该是一致的。然后要把 store的状态树写入一个全局变量(INITIAL_STATE),这样客户端初始化 render的时候能够校验服务器生成的 HTML结构,并且同步到初始化状态,然后整个页面被客户端接管。
最后关于页面内链接跳转如何处理?
react-router提供了一个<Link>组件用来替代<a>标签,它负责管理浏览器 history,从而不是每次点击链接都去请求服务器,然后可以通过绑定 onClick事件来作其他处理。
比如在/list页面,对于每一个 item都会用<Link>绑定一个 route url:/item/:id,并且绑定 onClick去触发 dispatch(fetchItem(id))获取数据,显示详情页内容。
相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!
推荐阅读:
怎样使用JS实现计算圆周率到小数点后100位
怎样使用vue axios给生产与发布环境配置接口地址
关于vue 服务器渲染和vue项目改造SSR(服务端渲染)的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。