ssr_next.js
1. 什么是同构
- 同构的项目支持客户端渲染和服务器端渲染
- 客户端渲染缺点
- 首屏速度加载慢
- 不支持 SEO 和搜索引擎优化
- 首页需要通过请求初始化数据
2.Next.js
- Next.js 英文文档,Next.js 中文文档 是一个轻量级的 React 服务端渲染应用框架
- 默认支持服务端渲染
- 自动根据页面进行代码分割
- 基于页面的客户端路由方案
- 基于 Webpack 的开发环境,支持热模块替换
- 可以跟
Koa
或者其它Node.js
服务器进行集成 - 支持 Babel 和 Webpack 的配置项定制
- 静态文件服务 public
3.项目初始化
3.1 创建项目
mkdir zhufengnextjs
cd zhufengnextjs
npm init -y
npm install react react-dom next redux react-redux --save
npm install axios express body-parser cors express-session connect-mongo mongoose koa koa-router --save
3.2 添加脚本
package.json
{"scripts": {"dev": "next dev","build": "next build","start": "next start"}
}
.gitignore
3.3 访问服务
- 以 ./pages 作为服务端的渲染和索引的根目录
- pages 是 next.js 中非常重要的一个目录,其中每一个 js 文件就代表一个页面,但是有两个例外,
_app.js
和_document.js
- pages 是 next.js 中非常重要的一个目录,其中每一个 js 文件就代表一个页面,但是有两个例外,
- next.js 会将 pages 下的 js 文件根据其路径名和文件名自动生成对应的路由
pages
组件代码自动分割- next.js 项目运行之后会自动生成
.next
目录
3.3.1 index.js
function Home() {return <div>Home</div>;
}
export default Home;
3.3.2 pages\user.js
pages\user.js
function User() {return <div>User</div>;
}
export default User;
3.3.3 访问
npm run dev
curl http://localhost:3000/
4.跑通路由和样式
4.1 知识点
- 绑定
styled-jsx
来生成独立作用域的CSS
- 如何支持本地和全局
css
- 路由的使用和两种跳转路径的方法
4.2 pages_app.js
App
组件是每个页面的根组件,页面切换时 App 不会销毁,但是里面的页面组件会销毁,因此可以利用这个来设置全局属性和样式- 全局 css 只能写在这里,否则会报错
- 当页面变化时保持页面布局
- 当路由变化时保持页面状态
- 注入额外数据到页面里
pages_app.js
import App from "next/app";
import Link from "next/link";
import _appStyle from "./_app.module.css";
import "../styles/global.css";
class LayoutApp extends App {render() {let { Component } = this.props;return (<div><style jsx>{`li {display: inline-block;margin-left: 10px;line-height: 31px;}`}</style><header><img src="/images/logo.png" className={_appStyle.logo} /><ul><li><Link href="/">首页</Link></li><li><Link href="/user">用户管理</Link></li><li><Link href="/profile">个人中心</Link></li></ul></header><Component /><footer style={{ textAlign: "center" }}>@copyright 珠峰架构</footer></div>);}
}
export default LayoutApp;
4.3 _app.module.css
pages _app.module.css
.logo {width: 120px;height: 31px;float: left;
}
4.4 global.css
styles\global.css
html,
body {padding: 0;margin: 0;
}
4.5 pages\index.js
pages\index.js
function Home() {return <div>Home</div>;
}
export default Home;
4.6 pages\user.js
pages\user.js
import Link from "next/link";
function User() {return (<div><p>User</p><Link href="/">首页</Link></div>);
}
export default User;
4.7 pages\profile.js
pages\profile.js
import router from "next/router";
function Profile() {return (<div><p>Profile</p><button onClick={() => router.back()}>返回</button></div>);
}
export default Profile;
5.二级路由
5.1 知识点
- 支持二级路由
- 实现二级布局组件
- 路由跳转传递参数
- 页面组件通过
getInitialProps
获取数据
5.2 执行顺序
5.2.1 后台顺序
- LayoutApp getInitialProps
- UseList getInitialProps
- LayoutApp constructor
- UseList constructor
5.2.2 前台顺序
- 初次渲染
- LayoutApp constructor
- UseList constructor
- 路由切换
- LayoutApp getInitialProps
- UseList getInitialProps
- UseList constructor
5.2.3 _app.js
pages_app.js
import App from 'next/app';
import Link from 'next/link';
import _appStyle from './_app.module.css';
import '../styles/global.css';
class LayoutApp extends App {
+ static async getInitialProps({ Component,ctx }) {
+ let pageProps = {};
+ if (Component.getInitialProps)
+ pageProps = await Component.getInitialProps(ctx);
+ return { pageProps };//我:将会成为LayoutApp的属性对象
+ }render() {
+ let { Component, pageProps } = this.props;return (<div><style jsx>{`li{display:inline-block;margin-left:10px;line-height:31px;}`}</style><header><img src="/images/logo.png" className={_appStyle.logo} /><ul><li><Link href="/">首页</Link></li>
+ <li><Link href="/user/list" >用户管理</Link></li><li><Link href="/profile">个人中心</Link></li></ul></header>
+ <Component {...pageProps} /><footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</footer></div>)}
}
export default LayoutApp;
5.2.4 user\index.js
pages\user\index.js
import Link from "next/link";
function UserLayout(props) {return (<div><div><ul><li><Link href="/user/list"><a>用户列表</a></Link></li><li><Link href="/user/add"><a>添加用户</a></Link></li></ul><div>{props.children}</div></div></div>);
}
export default UserLayout;
5.2.5 user\list.js
pages\user\list.js
import Link from "next/link";
import UserLayout from "./";
function UseList(props) {return (<UserLayout><ul>{props.list.map((user) => (<li key={user.id}><Link href={`/user/detail/${user.id}`}>{user.name}</Link></li>))}</ul></UserLayout>);
}
UseList.getInitialProps = async () => {let list = [{ id: 1, name: "张三" },{ id: 2, name: "李四" },];return { list };
};
export default UseList;
5.2.6 user\add.js
pages\user\add.js
import UserLayout from "./";
import React from "react";
function UserAdd() {let nameRef = React.useRef();let passwordRef = React.useRef();let handleSubmit = (event) => {event.preventDefault();let user = {name: nameRef.current.value,password: passwordRef.current.value,};};return (<UserLayout><form onSubmit={handleSubmit}>用户名:<input ref={nameRef} />密码:<input ref={passwordRef} /><button type="submit">添加</button></form></UserLayout>);
}export default UserAdd;
5.2.7 user\detail[id].js
pages\user\detail[id].js
import React from "react";
import UserLayout from "../";
function UserDetail(props) {return (<UserLayout><p>ID:{props.user.id}</p></UserLayout>);
}
UserDetail.getInitialProps = async (ctx) => {//这个上下文ctx是自带的return { user: { id: ctx.query.id } };
};
export default UserDetail;
6.调用接口
- 当服务渲染时,
getInitialProps
将会把数据序列化,就像JSON.stringify
- 所以确保
getInitialProps
返回的是一个普通JS
对象,而不是 Date, Map 或 Set 类型 - 当页面初始化加载时,
getInitialProps
只会加载在服务端。只有当路由跳转(Link 组件跳转或 API 方法跳转)时,客户端才会执行getInitialProps
getInitialProps
将不能使用在子组件中。只能使用在pages
页面中
6.1 方法执行顺序
6.1.1 首次访问
服务器端
LayoutApp getInitialProps 获取LayoutApp初始属性
UseList getInitialProps 调用页面组件的getInitialProps方法
LayoutApp constructor 根据属性创建LayoutAPp的实例
LayoutApp render 调用此实例的render方法,返回react元素
UseList constructor 创建子组件
UseList render 渲染子组件
- 然后会把HTML结构和LayoutApp属性对象序列化后发给客户端
- 客户端再把LayoutApp属性反序列化后变成对象
客户端
LayoutApp constructor
LayoutApp render
UseList constructor
UseList render
6.1.2 切换路由
客户端
LayoutApp getInitialProps
UseList getInitialProps
LayoutApp render
UseList constructor
UseList render
6.2 pages_app.js
pages_app.js
import App from 'next/app';
import Link from 'next/link';
import _appStyle from './_app.module.css';
import '../styles/global.css';
class LayoutApp extends App {
+ constructor(props) {
+ super(props)
+ console.log('LayoutApp constructor');
+ }static async getInitialProps({ Component, ctx }) {
+ console.log('LayoutApp getInitialProps');let pageProps = {};if (Component.getInitialProps)pageProps = await Component.getInitialProps(ctx);return { pageProps };}render() {console.log('LayoutApp render');let { Component, pageProps } = this.props;return (<div><style jsx>{`li{display:inline-block;margin-left:10px;line-height:31px;}`}</style><header><img src="/images/logo.png" className={_appStyle.logo} /><ul><li><Link href="/">首页</Link></li><li><Link href="/user/list" >用户管理</Link></li><li><Link href="/profile">个人中心</Link></li></ul></header><Component {...pageProps} /><footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</footer></div>)}
}
export default LayoutApp;
6.3 pages\user\list.js
pages\user\list.js
import React from 'react';
import Link from 'next/link';
import UserLayout from './';
+import request from '../../utils/request';
class UseList extends React.Component {
+ constructor(props) {
+ super(props);
+ console.log('UseList constructor');
+ }render() {console.log('UseList render');return (<UserLayout><ul>{this.props.list.map((user) => (<li key={user.id}><Link href={`/user/detail/${user.id}`}>{user.name}</Link></li>))}</ul></UserLayout>)}
}UseList.getInitialProps = async () => {
+ console.log('UseList getInitialProps');let response = await request({ url: '/api/users', method: 'GET' }).then(res => res.data);return { list: response.data };
}
export default UseList;
6.4 pages\user\add.js
pages\user\add.js
import UserLayout from './';
import React from 'react';
+import request from '../../utils/request';
import router from 'next/router'
function UserAdd() {let nameRef = React.useRef();let passwordRef = React.useRef();let handleSubmit = async (event) => {event.preventDefault();const user = { name: nameRef.current.value, password: passwordRef.current.value };
+ let response = await request.post('/api/register', user).then(res => res.data);
+ if (response.success) {
+ router.push('/user/list');
+ } else {
+ alert('添加用户失败');
+ }}return (<UserLayout><form onSubmit={handleSubmit}>用户名:<input ref={nameRef} />密码:<input ref={passwordRef} /><button type="submit">添加</button></form></UserLayout>)
}export default UserAdd;
6.5 [id].js
pages\user\detail[id].js
import React from 'react';
import UserLayout from '../';
+import request from '../../../utils/request';
function UserDetail(props) {return (<UserLayout><p>ID:{props.user.id}</p><p>NAME:{props.user.name}</p></UserLayout>)
}
UserDetail.getInitialProps = async (ctx) => {
+ let response = await request({ url: `/api/users/${ctx.query.id}`, method: 'GET' }).then(res => res.data);
+ return { user: response.data };
}
export default UserDetail;
6.6 utils\request.js
utils\request.js
import axios from "axios";
axios.defaults.withCredentials = true;//在跨域的时候携带cookie
const instance = axios.create({baseURL: "http://localhost:5000",
});
export default instance;
7.懒加载
7.1 [id].js 懒加载组件
pages\user\detail[id].js
import React from 'react';
import UserLayout from '../';
import request from '../../../utils/request';
+import dynamic from 'next/dynamic';
//import UserInfo from '@/components/UserInfo';
+const DynamicUserInfo = dynamic(() => import('@/components/UserInfo'));
function UserDetail(props) {const [show, setShow] = React.useState(false);return (<UserLayout><p>ID:{props.user && props.user.id}</p>
+ <button onClick={() => setShow(!show)}>显示/隐藏</button>
+ {
+ show && props.user && <DynamicUserInfo user={props.user} />
+ }
+ </UserLayout>)
}
UserDetail.getInitialProps = async (ctx) => {let response = await request({ url: `/api/users/${ctx.query.id}`, method: 'GET' }).then(res => res.data);return { user: response.data };
}
export default UserDetail;
7.2 components\UserInfo.js 懒加载模块
UserInfo.js
import React, { useState } from "react";
function UserInfo(props) {const [createdAt, setCreatedAt] = useState(props.user.createdAt);async function changeFormat() {const moment = await import("moment");//我:懒加载setCreatedAt(moment.default(createdAt).fromNow());}return (<><p>NAME:{props.user.name}</p><p>创建时间:{createdAt}</p><button onClick={changeFormat}>切换为相对时间</button></>);
}
export default UserInfo;
7.3 jsconfig.json 配置@别名
jsconfig.json
{"compilerOptions": {"baseUrl": ".",//当前目录"paths": {"@/*": ["/*"]}}
}
8.集成 redux
8.1 知识点
- 集成redux
- cookie保存和存递
8.2 pages_app.js
pages_app.js
import App from 'next/app';
import Link from 'next/link';
import _appStyle from './_app.module.css';
import '../styles/global.css';
+import { Provider } from 'react-redux';
+import request from '../utils/request';
+import createStore from '../store';
+import * as types from '../store/action-types';
+function getStore(initialState) {
+ if (typeof window === 'undefined') {
+ return createStore(initialState);//如果是服务器端,每次都返回新的仓库
+ } else {
+ if (!window._REDUX_STORE_) {
+ window._REDUX_STORE_ = createStore(initialState);
+ }
+ return window._REDUX_STORE_;
+ }
+}
class LayoutApp extends App {constructor(props) {super(props)
+ this.store = getStore(props.initialState);console.log('LayoutApp constructor');}static async getInitialProps({ Component, ctx }) {
+ console.log('LayoutApp getInitialProps');
+ let store = getStore();//1.后台创建新仓库 5.每次切换路由都会执行此方法获取老仓库
+ if (typeof window == 'undefined') {//2.后台获取用户信息,服务器环境
+ let options = { url: '/api/validate' };
+ if (ctx.req && ctx.req.headers.cookie) {
+ options.headers = options.headers || {};
+ options.headers.cookie = ctx.req.headers.cookie;
+ }
+ let response = await request(options).then(res => res.data);
+ if (response.success) {
+ store.dispatch({ type: types.SET_USER_INFO, payload: response.data });
+ }
+ }
+ let pageProps = {};
+ if (Component.getInitialProps)
+ pageProps = await Component.getInitialProps(ctx);
+ const props = { pageProps };
+ if (typeof window == 'undefined') {//后台获取用赋值状态,我:为了客户端可以直接用服务端的store初始化仓库
+ props.initialState = store.getState();
+ }
+ return props;}render() {console.log('LayoutApp render');let state = this.store.getState();let { Component, pageProps } = this.props;return (
+ <Provider store={this.store}><style jsx>{`li{display:inline-block;margin-left:10px;line-height:31px;}`}</style><header><img src="/images/logo.png" className={_appStyle.logo} /><ul><li><Link href="/">首页</Link></li><li><Link href="/user/list" >用户管理</Link></li><li><Link href="/profile">个人中心</Link></li><li>{
+ state.currentUser ? <span>{state.currentUser.name}</span> : <Link href="/login">登录</Link>}</li></ul></header><Component {...pageProps} /><footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</footer>
+ </Provider>)}
}
export default LayoutApp;
8.3 action-types.js
store\action-types.js
export const SET_USER_INFO = 'SET_USER_INFO';//设置用户信息
8.4 reducer.js
store\reducer.js
import * as types from './action-types';
let initState = {currentUser: null
}
const reducer = (state = initState, action) => {switch (action.type) {case types.SET_USER_INFO:return { currentUser: action.payload }default:return state;}
}
export default reducer;
8.5 store\index.js
store\index.js
import { createStore } from 'redux';
import reducer from './reducer';export default function (initialState) {return createStore(reducer, initialState);
}
8.6 login.js
pages\login.js
import React from 'react';
import request from '@/utils/request';
import router from 'next/router';
import { connect } from 'react-redux';
import * as types from '@/store/action-types';
function Login(props) {let nameRef = React.useRef();let passwordRef = React.useRef();let handleSubmit = async (event) => {event.preventDefault();let user = { name: nameRef.current.value, password: passwordRef.current.value };let response = await request.post('/api/login', user).then(res => res.data);if (response.success) {props.dispatch({ type: types.SET_USER_INFO, payload: response.data });router.push('/');} else {alert('登录失败');}}return (<form onSubmit={handleSubmit}>用户名:<input ref={nameRef} />密码:<input ref={passwordRef} /><button type="submit">登录</button></form>)
}export default connect(state => state)(Login);
9.loading
- 路由事件
事件 | 触发时机 |
---|---|
routeChangeStart(url) | 路由开始切换时触发 |
routeChangeComplete(url) | 完成路由切换时触发 |
routeChangeError(err, url) | 路由切换报错时触发 |
beforeHistoryChange(url) | 浏览器 history 模式开始切换时触发 |
hashChangeStart(url) | 开始切换 hash 值但是没有切换页面路由时触发 |
hashChangeComplete(url) | 完成切换 hash 值但是没有切换页面路由时触发 |
9.1 _app.js
pages_app.js
import App from 'next/app';
import Link from 'next/link';
import _appStyle from './_app.module.css';
import '../styles/global.css';
import { Provider } from 'react-redux';
import request from '../utils/request';
import createStore from '../store';
import * as types from '../store/action-types';
+import router from 'next/router';
function getStore(initialState) {if (typeof window === 'undefined') {return createStore(initialState);//如果是服务器端,每次都返回新的仓库} else {if (!window._REDUX_STORE_) {window._REDUX_STORE_ = createStore(initialState);}return window._REDUX_STORE_;}
}
class LayoutApp extends App {constructor(props) {super(props)
+ this.state = { loading: false }this.store = getStore(props.initialState);console.log('LayoutApp constructor');}static async getInitialProps({ Component, ctx }) {console.log('LayoutApp getInitialProps');let store = getStore();//1.后台创建新仓库 5.每次切换路由都会执行此方法获取老仓库if (typeof window == 'undefined') {//2.后台获取用户信息let options = { url: '/api/validate' };if (ctx.req && ctx.req.headers.cookie) {options.headers = options.headers || {};options.headers.cookie = ctx.req.headers.cookie;}let response = await request(options).then(res => res.data);if (response.success) {store.dispatch({ type: types.SET_USER_INFO, payload: response.data });}}let pageProps = {};if (Component.getInitialProps)pageProps = await Component.getInitialProps(ctx);const props = { pageProps };if (typeof window == 'undefined') {//后台获取用赋值状态props.initialState = store.getState();}return props;}
+ componentDidMount() {
+ this.routeChangeStart = (url) => {
+ this.setState({ loading: true });
+ };
+ router.events.on('routeChangeStart', this.routeChangeStart);
+ this.routeChangeComplete = (url) => {
+ this.setState({ loading: false });
+ };
+ router.events.on('routeChangeComplete', this.routeChangeComplete);
+ }
+ componentWillUnmount() {
+ router.events.off('routeChangeStart', this.routeChangeStart)
+ router.events.off('routeChangeStart', this.routeChangeComplete)
+ }render() {console.log('LayoutApp render');let state = this.store.getState();let { Component, pageProps } = this.props;return (<Provider store={this.store}><style jsx>{`li{display:inline-block;margin-left:10px;line-height:31px;}`}</style><header><img src="/images/logo.png" className={_appStyle.logo} /><ul><li><Link href="/">首页</Link></li><li><Link href="/user/list" >用户管理</Link></li><li><Link href="/profile">个人中心</Link></li><li>{state.currentUser ? <span>{state.currentUser.name}</span> : <Link href="/login">登录</Link>}</li></ul></header>{
+ this.state.loading ? <div>切换中......</div> : <Component {...pageProps} />}<footer style={{ textAlign: 'center' }} >@copyright 珠峰架构</footer></Provider>)}
}
export default LayoutApp;
10.受保护路由
10.1 profile.js
pages\profile.js
import router from 'next/router';
import { connect } from 'react-redux';
import request from '../utils/request';
function Profile(props) {let { currentUser } = props;return (<div><p>当前登录用户:{currentUser.name}</p><button onClick={() => router.back()}>返回</button></div>)
}Profile.getInitialProps = async function (ctx, store) {const state = store.getState();const currentUser = state.currentUser;if (currentUser){return { currentUser };}else{if (ctx.req) {ctx.res.writeHead(302, { Location: '/login' });//我:服务端重定向ctx.res.end();} else {router.push('/login');//我:客户端重定向}return {};}
}
//我:这部分后面改了了上面那种,因为服务端返回数据的时候已经加载了一次,所以可以利用这个store。Profile.getInitialProps = async function (ctx) {let options = { url: '/api/validate' };if (ctx.req && ctx.req.headers.cookie) {options.headers = options.headers || {};options.headers.cookie = ctx.req.headers.cookie;}let response = await request(options).then(res=>res.data);if (response.success) {return {currentUser:response.data};} else {if (ctx.req) {ctx.res.writeHead(303, { Location: '/login' })ctx.res.end()} else {router.push('/login');}return {};}
}const WrappedProfile = connect(state => state
)(Profile);
export default WrappedProfile;
11.自定义Document
11.1 pages_document.js
pages_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class CustomDocument extends Document {static async getInitialProps(ctx) {const props = await Document.getInitialProps(ctx);return { ...props };}render() {return (<Html><Head><style>{`*{padding:0;margin:0;}`}</style></Head><body><Main /><NextScript /></body></Html>)}
}
export default CustomDocument;
11.2 pages\index.js 这个是做SEO的
pages\index.js
import Head from 'next/head'
export default function (props) {return (<div><Head><title>首页</title><meta name="description" content="这是首页" /></Head><p>Home</p></div>)
}
##用来替换getInitialProps的
12. getServerSideProps
- data-fetching
- getServerSideProps (Server-side Rendering): Fetch data on each request
12.1 list.js
pages\user\list.js
import React from 'react';
import Link from 'next/link';
import UserLayout from './';
import request from '@/utils/request';
class UseList extends React.Component {constructor(props) {super(props);console.log('UseList constructor');}render() {console.log('UseList render');return (<UserLayout><ul>{this.props.list.map((user) => (<li key={user.id}><Link href={`/user/detail/${user.id}`}>{user.name}</Link></li>))}</ul></UserLayout>)}
}-UseList.getInitialProps = async () => {
- console.log('UseList getInitialProps');
- let response = await request({ url: '/api/users', method: 'GET' }).then(res => res.data);
- return { list: response.data };
-}
//每个请求都会调用
+export async function getServerSideProps() {
+ const res = await request.get('http://localhost:5000/api/users')
+ return {
+ props: {
+ list: res.data.data
+ },
+ }
+}
export default UseList;
13. getStaticProps
- data-fetching
- getStaticProps (Static Generation): Fetch data at build time //构建时获取数据
- getStaticPaths (Static Generation): Specify dynamic routes to pre-render based on data // 预渲染所有的路径
13.1 pages\user\list.js
pages\user\list.js
import React from 'react';
import Link from 'next/link';
import UserLayout from './';
import request from '@/utils/request';class UseList extends React.Component {constructor(props) {super(props);console.log('UseList constructor');}render() {console.log('UseList render');return (<UserLayout><ul>{this.props.list.map((user) => (<li key={user.id}><Link href={`/user/detail/${user.id}`}>{user.name}</Link></li>))}</ul></UserLayout>)}
}/* UseList.getInitialProps = async () => {console.log('UseList getInitialProps');let response = await request({ url: '/api/users', method: 'GET' }).then(res => res.data);return { list: response.data };
} */
//每个请求都会调用
export async function getServerSideProps() {const res = await request.get('http://localhost:5000/api/users')return {props: {list: res.data.data},}
}// 这个函数在编译阶段被调用
export async function getStaticProps() {const res = await request.get('http://localhost:5000/api/users');return {props: {list: res.data.data},}
}
export default UseList;
13.2 pages\user\detail[id].js
pages\user\detail[id].js
import React from 'react';
import UserLayout from '../';
import request from '@/utils/request';
import dynamic from 'next/dynamic';
//import UserInfo from '@/components/UserInfo';
const DynamicUserInfo = dynamic(() => import('@/components/UserInfo'));
function UserDetail(props) {const [show, setShow] = React.useState(false);return (<UserLayout><p>ID:{props.user && props.user.id}</p><button onClick={() => setShow(!show)}>显示/隐藏</button>{show && props.user && <DynamicUserInfo user={props.user} />}</UserLayout>)
}
UserDetail.getInitialProps = async (ctx) => {let response = await request.get(`/api/users/${ctx.query.id}`)return { user: response.data.data };
}
export async function getStaticPaths() {const res = await request.get('http://localhost:5000/api/users');const users = res.data;const paths = users.map(user => `/user/detail/${user.id}`);return { paths, fallback: false }
}
export async function getStaticProps({ params }) {console.log('params', params, new Date().toTimeString());const res = await request.get(`http://localhost:5000/api/users/${params.id}`);return {props: {user: res.data}}
}
export default UserDetail;
14.布署
14.1 直接布署
npm run build
npm run start
14.2 集成express布署
14.2.1 编译
npm run build
14.2.2 start.js
start.js
const next = require('next');
const app = next({ dev:false });//我:非开发环境
const handler = app.getRequestHandler();//获取请求处理器
//预编译一下
app.prepare().then(() => {let express = require("express");let bodyParser = require("body-parser");let {UserModel} = require('./model');let session = require("express-session");let config = require('./config');let MongoStore = require('connect-mongo')(session);let app = express();//.......
+ app.get('*', async (req, res) => {+ await handler(req, res);
+ })app.listen(5000, () => {console.log('服务器在5000端口启动!');});
});
15.api.js
const express = require('express');
const cors = require('cors');
+ const logger = require('morgan');//我:打印日志
const session = require('express-session');
const app = express();
+ app.use(logger('dev'));
+ 如果跨域访问要携带cookie,就必须指明哪些可以跨域,需要配置。
app.use(cors({origin: ['http://localhost:3000'],credentials: true,allowedHeaders: "Content-Type,Authorization",methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS"})
);
app.use(session({saveUninitialized: true,resave: true,secret: 'zhufeng'
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const users = [];
app.get('/api/users', (req, res) => {res.json({success: true,data: users});
});
app.post('/api/register', (req, res) => {const user = req.body;user.id = Date.now() + "";user.createdAt = new Date().toISOString();users.push(user);res.json({success: true,data: user})
});
+ 我:通过这种方式解析id这个参数的
app.get('/api/users/:id', (req, res) => {const id = req.params.id;const user = users.find(user => user.id === id);res.json({success: true,data: user})
});
app.post('/api/login', (req, res) => {const user = req.body;req.session.user = user;res.json({success: true,data: user})
});app.get('/api/logout', (req, res) => {req.session.user = null;res.json({success: true,data: null})
});
app.get('/api/validate', (req, res) => {const user = req.session.user;if (user) {res.json({success: true,data: user})} else {res.json({success: false,error: `用户未登录`})}
});
app.listen(5000, () => console.log('api server started on port 5000'));
ssr_next.js相关推荐
- 在js中使用HashMap数据结构,在js中使用K,V数据结构
首先是定义一个HashMap方法,做基类(复制在js中即可,然后引用) //简单的哈希表,begin function HashMap() {/** Map 大小 * */var size = 0;/ ...
- js校验复选框(多选按钮)是否被选中的方法
js校验复选框是否被选中的方法 方法一:(使用下标进行标记) if ($("#checkbox-id")get(0).checked) {// do something } 方法二 ...
- form表单提交前进行ajax或js验证,校验不通过不提交
在使用form表单进行提交数据前,需要进行数据的校验->表单的校验(如:两次密码输入是否相同)+后台数据的校验(如:账号是否存在),这个时候,如果哪步校验不通过,表单将停止提交,同时避免后台主键 ...
- 终止js程序执行的方法
js终止程序执行的方法共有三种 (一)在function里面(普通js方法) (1)return; (2)return false; (二)非function方法里面(如ajax方法) alert(& ...
- JS Uncaught SyntaxError:Unexpected identifier异常报错原因及其解决方法
最近在写ajax的时候,调用js方法,遇到了Uncaught SyntaxError:Unexpected identifier异常报错,开始搞不清原因,很苦恼. 以为是js方法参数个数和长度的问题, ...
- 用js方法做提交表单的校验
基础知识: 原始提交如下: <form action="<%=basePath %>puser/register" method="post" ...
- 【JavaScript总结】JavaScript语法基础:JS高级语法
作用域链: 1.JS中只有函数能够限定作用域的范围: 2.变量处理在制定的函数范围内,还有一个特殊的作用域,就是没有用var 声明的全局作用域 3.js中的作用域链是为了清晰的表示出所有变量的作用范围 ...
- 【JavaScript总结】JavaScript语法基础:JS编码
运算符 数学:+. -. *. / 逻辑:>. < .>= .<=. == . !=.&&.|| . === .!==(完全等于) 对象相关 new delet ...
- js获取html代码中所有图片地址
/** * JS获取html代码中所有的图片地址 * @param htmlstr * @returns imgsrcArr 数组 */ function getimgsrc(htmlstr) { v ...
最新文章
- mysql更新一个表里的字段等于另一个表某字段的值
- 【转载】Zend Studio 10正式版注册破解
- one microblog from 任志强
- 输出以下图案菱形7行_春夏格子图案超流行,三木的一款格子连衣裙,带来田园少女风...
- spring源码编译和导入eclipse
- Java集合Set,List和Map等
- mysql bin 分析_mysql bin log 分析
- 数组的合并,去重,排序
- Sphere-AABB Intersecting test
- Python基础练习题,含答案解析
- 数据结构(严蔚敏 第二版)绪论部分中关于算法的相关知识
- navicat工具能安装在linux,linux下安装navicat
- 大容量sd卡reread之后/dev下概率性出现无设备文件
- 工具系列 | FPM进程管理器详解
- 王者服务器维护公告2月,2月26日全服不停机更新公告
- python 相关性检验_Python中的相关分析correlation analysis的实现
- 如何搭建一个简单的个人网站
- 舆情传播的全过程如何监控监测?
- pytorch 状态字典:state_dict
- 传世私服服务器列表不显示,关于传世私服的人物名字显示设置详解