"test"
:
"echo
\"
Error: no test specified
\"
&& exit 1"
,
"dev"
:
"next"
,
"build"
:
" next build"
,
"start"
:
"next start"
创建pages文件夹,尝试开发首页代码
Next规定在
pages
下写入的文件,会自动创建对应的路由
新建
index.js
文件,并写入以下代码,Next将自动创建
/
路由
function Index(){
return (
<div>Hello World!</div>
export default Index
二、Next.js基础应用
Pages新建路由
前面已经介绍过如何在next中新建一级路由:只需在pages
目录下新建frontend.js
文件,那么next将自动注册/frontend
路由
那么如果想新建一个二级路由,同理只需调整pages
下的目录结构
├── pages
│ ├── blog
│ | ├── firstblog.js
│ | ├──secondblog.js
Link标签进行路由跳转
新建pages/pageA.js
示范
import Link from 'next/link'
export default ()=>(
<div> This is PageA </div>
<Link href="/"><a>返回首页</a></Link>
Link标签默认不渲染任何东西,所以还要在标签内写上要进行渲染的标签
Link标签通过href
属性进行跳转
Link内部代码不允许兄弟标签并列,必须嵌套一层父标签
利用Router模块,编程式路由跳转
在pages/index.js
示范
import Router from 'next/router'
export default ()=>(
<div> This is Index </div>
<button onClick={()=> Router.push('/pageA')}>前往pageA页面</button>
注意:在Next中只能用query
形式传递参数,即只能用?id=1
形式,而不能用/:id
形式
import React from 'react'
import Link from 'next/link'
import Router from 'next/router'
const Index = () => {
function gotoPageA(){
Router.push({
pathname: '/pageA',
query: { id: 4 }
return(
<div> This is Index </div>
<Link href="/pageA?id=1"><a>go to pageA, id is 1</a></Link>
<Link href={{ pathname: '/pageA', query: {id: 2} }}><a>go to pageA, id is 2</a></Link>
<button onClick={()=> Router.push('/pageA?id=3')}>go to pageA, id is 3</button>
<button onClick={gotoPageA}>go to pageA, id is 4</button>
</div>
export default Index
在组件中接收路由参数
import { withRouter } from 'next/router'
const pageA = ({ router })=>{
return (
<div>This is pageA.</div>
<div>id is {router.query.id}</div>
export default withRouter(pageA)
在上面路由传参实例中,使用了query形式传递路由参数id?id=1
,但在实际开发中,对于参数是id,更习惯用path:id
形式传递,这样url看上去也更贴近真实开发,所以我们可以使用路由映射来解决这个问题
as
属性的使用
import Link from 'next/link'
import Router from 'next/router'
export default () => {
gotoPageA = () => {
Router.push({
pathname: '/pageA',
query: { id: 2 }
}, '/pageA/2')
return (
{/* 利用as属性,实现路由映射 */}
<Link href="/pageA?id=1" as="/pageA/1"><a>go to pageA, id is 1</a></Link>
<button onClick={gotoPageA}>go to pageA, id is 2</button>
注意:使用as
属性处理路由映射后,页面url将会对应改变。此时可以试着刷新页面,会发现竟然出现404访问失败。
原因是:这时的路由不是真实的路由,真实的路由是通过query
形式传递参数(/pageA?id=1
),而使用路由映射后,地址栏的url变成/pageA/1
,刷新页面后,next会进行服务端渲染,服务端会按照映射后的路由去渲染对应的文件,但这个文件是不存在的。比如服务端这时会去找pages/pageA
目录下的1.js
文件,但该文件并不存在,因此会出现404的错误。
解决方法:既然是在服务端渲染过程中出错,当然是在服务端代码中解决。这点将在后续讲述。
路由钩子事件
import Link from 'next/link'
import Router from 'next/router'
const events = [
'routeChangeStart',
'routeChangeComplete',
'routeChangeError',
'beforeHistoryChange',
'hashChangeStart',
'hashChangeComplete'
function makeEvent(type){
return (...args) => {
console.log(type, ...args)
events.forEach(event => {
Router.events.on(event, makeEvent(event));
export default () => {
return (
<Link href="/pageA"><a>go to pageA</a></Link>
<Link href="#one"><a>go to part one</a></Link>
<Link href="/404"><a>go to 404</a></Link>
getInitialProps
在实际开发中,通常会在组件初始化后,请求远端数据。如在vue
的mounted
生命周期中,react
的componentDidMount
生命周期中发起请求。而在在Next.js中提供了getInitialProps
方法用来获取远端数据,而不是放在组件的生命周期里。
使用示例:
yarn add axios
import axios from 'axios'
const pageA = ({ data })=>{
return (
<div>initial props: {data}</div>
pageA.getInitialProps = async () => {
const {status, data} = await axios(url);
if(status === 200){
return { data }
export default pageA
类组件中使用
import React from "react"
class PageA extends React.Component {
static getInitialProps = async () => {
const {status, data} = await axios(url);
if(status === 200){
return { data }
render() {
const { data } = this.props
return (
<div>initial props: {data}</div>
export default PageA;
getInitialProps
默认传入的参数为一个对象,对象属性如下:
具体说明见github + pathname
- URL 的 path 部分 + query
- URL 的 query 部分 + asPath
- 路由映射后,浏览器中显示的路径(包含查询部分) + req
- HTTP 请求对象 (只有服务器端有) + res
- HTTP 返回对象 (只有服务器端有) + jsonPageRes
- 获取数据响应对象 (只有客户端有) + err
- 渲染过程中的任何错误对象
2. 在进行服务端和客户端渲染时都能执行该方法。 + 如果是服务端进行首屏渲染时,会执行该路由组件下的getInitialProps
,并返回相应的props
参数,当组件拿到props
并渲染好后,服务端才会返回,这时客户端不会再执行一遍该组件的getInitialProps
+ 当客户端用Link标签或API方法跳转路由组件时,客户端会去执行对应的路由组件的getInitialProps
,此时服务端则不会执行 3. 只有放在 pages
目录下的路由组件,它的getInitialProps
才会被调用,子组件使用该方法无效
页面CSS
使用Style JSX
编写页面CSS
Next默认不能引用CSS
文件,需要另外配置,但是可以使用style jsx
编写
export default () => {
const fontColor = 'red'
return (
<div className="test">This is pageA.</div>
<div className="test2">This is pageA.</div>
注意使用格式:<style jsx>{`...`}</style>
<style jsx>
.test { color: blue; }
.test2 { color: ${fontColor}; }
</style>
说明:加入Style jsx
代码后,Next会自动生成随机类名jsx-xxxx
,防止CSS
全局污染
引入CSS文件
开发中对于全局样式,通常会引入css文件,而不是<style global jsx>
标签形式。此时,需要让Next支持css文件引入
yarn add @zeit/next-css
新建next.config.js
const withCss = require('@Zeit/next-css');
if(typeof require !== 'undefined'){
require.extensions['.css'] = file => {}
module.exports = withCss({})
在public
or static
目录下新建css文件,在组件中引入即可
import '../public/test.css'
拓展:集成styled-components
styled-components 是基于 Css In Js 方式实现的一个库。可以使用标签模板来对组件进行样式化,是一种广受关注的React的样式方案。
yarn add style-components babel-plugin-styled-components
编辑.babelrc
"presets": ["next/babel"],
"plugins": [
["styled-components", { "ssr": true }]
在组件中使用
import styled from 'styled-components'
const Title = styled.h1`
color: yellow;
font-size: 40px;
export default () => (
<Title>This is a page</Title>
动态加载模块&组件(懒加载)
异步加载模块
yarn add moment
import React, {useState} from 'react'
export default () => {
const [time,setTime] = useState(Date.now())
const handleChangeTime = async ()=>{
const moment = await import('moment')
setTime(moment.default(Date.now()).format())
return (
<div>目前时间:{time}</div>
<button onClick={handleChangeTime}>改变时间格式</button>
可以在控制台面板中观察,当点击按钮时,才会加载1.js,即momnet.js的内容
异步加载组件
新建components/one.js
子组件
export default ()=><div>Lazy Loading Component</div>
在父组件中使用
import React, {useState} from 'react'
import dynamic from 'next/dynamic'
const One = dynamic(import('../components/one'))
export default () => {
const [time,setTime] = useState(Date.now())
const handleChangeTime = async ()=>{
const moment = await import('moment')
setTime(moment.default(Date.now()).format('YYYY-MM-DD'))
return (
<div>目前时间:{time}</div>
<button onClick={handleChangeTime}>改变时间格式</button>
<One />
自定义Head
使用Next的目的其中就是为了优化SEO。对于不同的页面,有时候需要自定义Head,以便更好地支持SEO
import Head from 'next/head'
export default () => (
<title>PageA</title>
<meta charSet='utf-8' />
</Head>
<div>This is pageA</div>
自定义App
在开发中,有时我们需要自定义Layout组件,并全局使用;或者保持一些公用的状态,给页面传入一些自定义数据...此时,可以在pages/_app.js
文件中实现
import App, { Container } from 'next/app'
class MyApp extends App {
static async getInitialProps({ Component, ctx }){
let pageProps
if(Component.getInitialProps){
pageProps = await Component.getInitialProps(ctx)
return {
pageProps
render(){
const { Component, pageProps } = this.props
return (
<Container>
<Component { ...pageProps } />
</Container>
export default MyApp
三、在Next中集成Koa
集成Koa
yarn add koa
根目录下新建server.js
作为同时启动next
和koa
的入口文件
const Koa = require('koa')
const next = require('next')
// 判断处于开发环境或是生产环境
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
// 待pages下的文件编译完成后执行
app.prepare().then(() => {
const server = new Koa()
// 使用next中间件
server.use(async (ctx, next) => {
// ctx.req, ctx.res 为node原生的request/response对象
await handle(ctx.req, ctx.res)
ctx.respond = false
server.listen(3000, () => {
console.log('koa server listening on 3000')
修改package.json
"scripts": {
"dev": "nodemon server.js",
"build": "next build",
"start": "next start"
解决服务端渲染404
之前提到在使用路由映射后,刷新页面,服务端渲染过程中可能会出现404情况。这里提出解决方案
const Koa = require('koa')
const Router = require('koa-router')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = new Koa()
const router = new Router()
/* --- 重点start --- */
router.get('/a/:id', async (ctx) => {
const id = ctx.params.id
await handle(ctx.req, ctx.res, {
pathname: '/a',
query: { id }
ctx.respond = false
/* ---- 重点end ---- */
server.use(router.routes())
server.use(async (ctx, next) => {
await handle(ctx.req, ctx.res)
ctx.respond = false
server.listen(3000, () => {
console.log('koa server listening on 3000')
四、Next中集成Ant Design
yarn add @zeit/next-css
新建next.config.js
支持css
文件引入
const withCss = require('@Zeit/next-css');
if(typeof require !== 'undefined'){
require.extensions['.css'] = file => {}
module.exports = withCss({})
yarn add babel-plugin-import antd
按需加载
根目录下新建.babelrc
"presets": ["next/babel"],
"plugins": [
"import",
"libraryName": "antd"
引入antd全局CSS文件
新建`pages/_app.js`
import App from 'next/app'
import 'antd/dist/antd.css'
export default App
五、PM2 部署Next.js 应用
部署上线这一块实在没什么好说的,简单的话直接起一个node服务的就可以,复杂一点就要包括报警重启等等,都是看个人情况的。
#
yarn dev (启动一个热加载的Web服务器)
#
yarn build (利用webpack编译应用,压缩JS和CSS资源(发布用))
#
yarn start (以生产模式启动一个Web服务器)
Tips: 由于生产环境需要程序持续运行,使用PM2守护进程持续部署前端应用
pm2 start yarn --name "client" --interpreter bash -- start
pm2 startup
此时前端应用已使用PM2跑了起来,执行
pm2 list
查看pm2进程的状态,如果status 是online,就是正常状态,如果是其他状态则不正常,以后每次代码更新完,直接使用命令
pm2 restart xxx (xxx即最开始--name那里取的名字)
赋:PM2常用命令
1.全局安装
npm install pm2 -g
yarn add pm2 -g
2、pm2启动
pm2 start xxx
3、pm2查看进程
pm2 list
pm2 show 1 或者
4、pm2监控
pm2 monit
5、pm2停止
pm2 stop all
pm2 stop 0
6、pm2重启
pm2 restart all
pm2 restart 0
7、pm2删除某个进程
pm2 delete 0
pm2 delete all
六、 Docker部署
打包生成.next目录
BUILD_ID 的版本号和server static一致
docker化node应用
生成Dockerfile & .dockerignore文件
touch Dockerfile
Dockerfile配置
FROM node:14
# Create app directory
WORKDIR /usr/src/app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
EXPOSE 3000
CMD [ "yarn", "start" ]
.dockerignore配置
node_modules
*.log
docker build . -t yourUsername/node-web-app docker run -p 49160:8080 -d yourUsername/node-web-app 浏览器输入3000查看是否成功,如果失败docker logs id 查看
host是一个坑,要注意
购买服务器
阿里云自行选择服务器
ssh root@公网ip //登陆服务器
ssh-copy-id root@公网ip //上传私钥
修改host文件
公网ip 变量名
//下次登陆就很简单啦,直接使用ssh root@变量名 就好啦
创建单独的user
ssh root@id
adduser username
su - username
ssh username@id
ssh-copy-id username@id
6.安装docker
docker官网 7.user添加分组 (解决非root用户无法运行docker)
cat /etc/group 查看分组
usermod -a -G docker username
8. 安装node && yarn (用来打包)
curl -fsSL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install gcc g++ make
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
服务器内部docker化
touch .env.local
yarn build
mkdir blog-data
docker run --network=host -v /home/xiong/blog-data:/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2
yarn typeorm:build
yarn migration:run
docker build -t aslanxiong/node-web-app .
docker run --network=host -p 3000:3000 -d aslanxiong/node-web-app
阿里云安全策略组添加要访问的端口
使用部署脚本
添加bin/deploy.sh 文件
chmod +x bin/deploy.sh
ssh xiong@nextjs "sh /home/。。。"
ssh xiong@nextjs 'bash -s' < bin/deploy.sh
11. 使用nginx
docker run --name nginxNextjs -d nginx:1.19.1
docker run --name nginxNextjs --network=host -v /home/xiong/app/nginx.conf:/etc/nginx/conf.d/default.conf -v /home/xiong/app/.next/static/:/home/xiong/nginx/html/_next/static/ -d nginx:1.19.1
Mr_Ma
高级前端开发工程师
粉丝