[The React Framework for Production] Next.js gives you the best developer experience with all the features you need for production: hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more. No config needed.
一顿牛皮下来,就是 Next.js 在生产和开发环境下都能带给你最佳体验,开箱体验,无需任何配置
老表,咋回事哦,vue 不能满足了吗,搞这玩意干嘛,vuepress 写所谓的静态博客网站不香?香,还比这简单,但请容许我介绍完这个 Next.js
- 图片优化
- 支持国际化
- 零配置
- 支持 SSG + SSR
- 文件系统路由
- 优点太多...
- vue 和 react 造出来的单页面应用 SEO 不友好,搜索引擎抓不到 html 的内容,内容都在 js 里
- vue 和 react 造出来的单页面首屏白屏时间过长,在不对项目 webpack 专门优化的情况下,那个 bundle.js 很大,严重影响体验
总结:如果项目对 SEO 要求比较高,建议上 Next 或Nuxt
简单了解
- 客户端渲染
前后端分离,通过 ajax 进行数据交互,vue 和 react 就是这种模式
- 服务端模板渲染
php 和 jsp 是解析模板文件,将数据渲染到文件上,最后将模板文件变为 html,生成 html 返回给浏览器,前后端不用同一套代码
- 前后端同构渲染
也是服务端生成 html 返回给浏览器,区别在于前后端会共用一部分组件代码逻辑,这部分代码既可以用于服务端,也可以用于客户端,而模板渲染是两套代码
注意:Node.js 版本 12.22.0 起步
npx create-next-app@latest
# or
yarn create next-app
│ .eslintrc.json
│ .gitignore
│ next.config.js # next配置文件
│ package.json
│ README.md
│ yarn.lock
│
├─pages # 页面路由
│ │ index.js
│ │ _app.js
│ │
│ └─api # api服务
│ hello.js
│
├─public # 静态资源
│ favicon.ico
│ vercel.svg
│
└─styles # css样式
globals.css # 全局样式
Home.module.css
- 文件系统路由
/pages/index.js
路径为/
/pages/posts/about.js
路径为/posts/about
/pages/posts/[id].js
动态路径为/posts/foo
或者/posts/bar
等等
- Link 组件
Link 组件会自动执行 prefetch 预加载
import Link from "next/link";
export default function Home() {
return (
<Link href="/posts/about">
<a>about page</a>
</Link>
);
}
// 或者不用a标签,传参示例
<Link
href={{
pathname: "/about",
query: { name: "test" },
}}
passHref
>
<p>about page</p>
</Link>;
- useRouter
import { useRouter } from "next/router";
import { useCallback, useEffect } from 'react';
export default List() {
const router = useRouter();
const gotoDetail = useCallback((data) => {
const { fileName: detailid } = data;
// https://www.nextjs.cn/docs/api-reference/next/router#with-url-object
router.push({
pathname: "/posts/[detailid]",
query: {
detailid,
},
});
}, []);
useEffect(() => {
// Prefetch the dashboard page
router.prefetch('/dashboard');
}, []);
return (
<div>
...
</div>
)
}
- 动态路由
就是/pages/posts/[id].js
这样的路由
import { getAllPostIds, getPostData } from "@/lib/posts";
export async function getStaticPaths() {
const allListData = await getAllPostIds();
const paths = allListData.map((item) => {
return {
params: { id: item.fileName },
};
});
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const postData = await getPostData(params.id);
return {
props: {
postData,
},
};
}
export default function List({ postData }) {
// ...
}
用于自定义 head 标签内容
import Head from "next/head";
export default function Layout({ children }) {
return (
<div>
<Head>
<meta charSet="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<meta name="keywords" content="Next.js" />
<meta name="description" content="Next.js" />
<title>Next.js</title>
</Head>
<main>{children}</main>
</div>
);
}
使用适当的属性,可以大幅优化图像,提升页面渲染
import Image from "next/image";
export default function MyImg() {
return (
<Image
className={styles.homeBgImg}
src={bgImg}
layout="fill"
objectFit="cover"
objectPosition="center"
quality={65}
priority={true}
placeholder="blur"
blurDataURL={DEFAULT_BASE64}
alt="img"
/>
);
}
import Script from "next/script";
export default function Home() {
return (
<>
<Script
src="https://Jquery.js"
onLoad={() => {
$.ajax({
// ...
});
}}
/>
</>
);
}
- CSS Modules(已内置)
- Sass(已内置)
- styled-jsx(已内置)
- styled-components(需自行配置)
- Tailwind CSS(需自行配置)
- Client-side Rendering
就是常见的前后端分离
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((res) => res.json());
function Profile() {
const { data, error } = useSWR("/api/user", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
- Static Generation (Recommended)
一般以展示一些静态固定数据为主,打包的时候就直接生成,比如博客页面、固定营销页面、帮助文档等
import { getAllPostIds } from "@/lib/posts";
export async function getStaticProps() {
const allListData = await getAllPostIds();
return {
props: {
allListData,
},
};
}
export default function List({ allListData }) {
// ...
}
- Server-side Rendering
以动态数据为主,每次请求的时候都在服务端执行,对服务器压力比较大
export async function getServerSideProps(context) {
return {
props: {
list: [...]
}
}
}
export default function List({ list }) {
// ...
}
使用 github 账户注册登录Vercel官网,授权访问该仓库,即可快速部署,部署完即可访问。还能看到部署日志。
这里服务器以 centos 为例
- 使用 Next.js官方 Dockerfile
注意:如果使用官方 Dockerfile,比如在阿里云上进行部署,会遇到网络问题,下载某些包会很慢,跟你本地访问 github 官网一样,所以要设置国内镜像下载,速度就会变快
# Install dependencies only when needed
FROM node:alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# Rebuild the source code only when needed
FROM node:alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
# Production image, copy all the files and run next
FROM node:alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs
EXPOSE 3000
ENV PORT 3000
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry.
# ENV NEXT_TELEMETRY_DISABLED 1
CMD ["node_modules/.bin/next", "start"]
- 使用自己的 Dockerfile
这里可以对 Docker 进行多阶段构建,使打包出来的镜像体积变小
# 1. 构建基础镜像
FROM alpine:3.15 AS base
#纯净版镜像
ENV NODE_ENV=production \
APP_PATH=/app
WORKDIR $APP_PATH
# 使用国内镜像,加速下面 apk add下载安装alpine不稳定情况
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 使用apk命令安装 nodejs 和 yarn
RUN apk add --no-cache --update nodejs=16.13.1-r0 yarn=1.22.17-r0
# 2. 基于基础镜像安装项目依赖
FROM base AS install
COPY package.json yarn.lock ./
RUN yarn install
# 3. 基于基础镜像进行最终构建
FROM base
# 拷贝 上面生成的 node_modules 文件夹复制到最终的工作目录下
COPY --from=install $APP_PATH/node_modules ./node_modules
# 拷贝当前目录下的所有文件(除了.dockerignore里排除的),都拷贝到工作目录下
COPY . .
RUN yarn build
EXPOSE 3000
CMD ["yarn", "start"]
- 使用
scp
命令手动将本地源代码上传至服务器
scp -r local_dir root@121.xxx.xxx.xxx:remote_dir
- 或者在远程服务器上
wget
下载 github 源码,然后解压
wget https://github.com/xxx/main.zip -O main.zip && unzip main.zip -d .
- 使用
xshell
工具上传文件到服务器
上面 3 种方法都可以把本地文件传到服务器对应目录
前提是安装好了 docker 并启动
# 切换都源码目录执行
docker image build -t blog-demo .
接着会看到命令行上正在执行镜像制作过程,顺利的话,就成功了
# 不出意外,可以看到刚才制作的镜像
docker image ls
前提要在服务器上开好安全组
docker container run -d -p 80:3000 -it blog-demo
# -d: 后台运行容器
# -p: 前面80是本机服务器开放端口,后面3000是容器暴露出来的端口
# --name:给容器命名
不出意外,容器成功运行。可以在浏览器里进行访问了。
上面手动步骤太麻烦了,需要解放双手
这里也可以选择 dockerhub,注册好后,创建仓库,即可推送。阿里云的镜像容器服务,也需要提前开通准备好(命名空间+私人仓库)。
- 容器登录账号+密码
- 服务器的 HOST + 登录账户 + 密码
- 阿里云或者 dockerhub 的镜像容器仓库
我这里买的是阿里云的屌丝 1 核 2G 机器,生产环境别这么玩,账号密码可能泄露
.github/workflows/deploy.yml
name: Docker Image CI
on:
push: # push 时触发ci
branches: [main] # 作用于main分支
# pull_request:
# branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 拉取main分支代码
- name: Checkout
uses: actions/checkout@v2
# 制作docker镜像并推送到阿里云容器镜像服务
- name: build and push docker image
run: |
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login registry.cn-hangzhou.aliyuncs.com --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
docker image build -t myblog:latest .
docker tag myblog registry.cn-hangzhou.aliyuncs.com/test-blog/myblog:latest
docker push registry.cn-hangzhou.aliyuncs.com/test-blog/myblog:latest
docker logout
# 登录远程服务器,拉取镜像,制作并重启容器
# https://github.com/marketplace/actions/remote-ssh-commands
- name: ssh remote deploy
uses: fifsky/ssh-action@master
with:
command: |
cd /
echo -e "1.docker login start==>"
echo ${{ secrets.ALIYUN_DOCKER_PASSWORD }} | docker login registry.cn-hangzhou.aliyuncs.com --username ${{ secrets.ALIYUN_DOCKER_USERNAME }} --password-stdin
echo -e "2.docker stop myblog container==>"
docker container stop myblog
echo -e "3.docker conatainer rm==>"
docker container rm myblog
echo -e "4.docker image rm==>"
docker image rm registry.cn-hangzhou.aliyuncs.com/test-blog/myblog:latest
echo -e "5.docker pull==>"
docker pull registry.cn-hangzhou.aliyuncs.com/test-blog/myblog:latest
echo -e "6.docker container create and start==>"
docker container run -d -p 80:3000 --name myblog registry.cn-hangzhou.aliyuncs.com/test-blog/myblog:latest
echo -e "7.docker logout==>"
docker logout
host: ${{ secrets.HOST }}
user: ${{ secrets.USER }}
pass: ${{ secrets.PASSWORD }}
不出意外,在仓库的 Actions 里看到一切 ok
git add .
git commit -m "chore: add github actions yml"
git push -u origin main