使用 Next.js + Docker 打造一个属于你的私人博客

1. Next.js 简介

[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 在生产和开发环境下都能带给你最佳体验,开箱体验,无需任何配置

2. 为什么选择 Next.js

老表,咋回事哦,vue 不能满足了吗,搞这玩意干嘛,vuepress 写所谓的静态博客网站不香?香,还比这简单,但请容许我介绍完这个 Next.js

gs.jpg

2.1 那 Next.js 有哪些优点呢?

  • 图片优化
  • 支持国际化
  • 零配置
  • 支持 SSG + SSR
  • 文件系统路由
  • 优点太多...

yd.jpg

2.2 那 Next.js 比这 vue 和 react 造出来的单页面有何不同?

  1. vue 和 react 造出来的单页面应用 SEO 不友好,搜索引擎抓不到 html 的内容,内容都在 js 里
  2. vue 和 react 造出来的单页面首屏白屏时间过长,在不对项目 webpack 专门优化的情况下,那个 bundle.js 很大,严重影响体验

总结:如果项目对 SEO 要求比较高,建议上 Next 或Nuxt

2.3 Next.js 和传统的 php、jsp 有何区别?

简单了解

  1. 客户端渲染

前后端分离,通过 ajax 进行数据交互,vue 和 react 就是这种模式

  1. 服务端模板渲染

php 和 jsp 是解析模板文件,将数据渲染到文件上,最后将模板文件变为 html,生成 html 返回给浏览器,前后端不用同一套代码

  1. 前后端同构渲染

也是服务端生成 html 返回给浏览器,区别在于前后端会共用一部分组件代码逻辑,这部分代码既可以用于服务端,也可以用于客户端,而模板渲染是两套代码

3. Next.js 主要 api 快速上手

注意:Node.js 版本 12.22.0 起步

3.1 使用create-next-app脚手架创建项目

npx create-next-app@latest
# or
yarn create next-app
复制代码

3.2 项目目录结构

│  .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
复制代码

3.3 路由

  1. 文件系统路由
  • /pages/index.js 路径为 /
  • /pages/posts/about.js 路径为 /posts/about
  • /pages/posts/[id].js 动态路径为 /posts/foo 或者/posts/bar 等等
  1. 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>;
复制代码
  1. 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>
  )
}
复制代码
  1. 动态路由

就是/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 }) {
  // ...
}
复制代码

3.4 Head 组件

用于自定义 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>
  );
}
复制代码

3.5 Image 组件

使用适当的属性,可以大幅优化图像,提升页面渲染

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"
    />
  );
}
复制代码

3.6 Script 组件

import Script from "next/script";

export default function Home() {
  return (
    <>
      <Script
        src="https://Jquery.js"
        onLoad={() => {
          $.ajax({
            // ...
          });
        }}
      />
    </>
  );
}
复制代码

3.7 CSS

  1. CSS Modules(已内置)
  2. Sass(已内置)
  3. styled-jsx(已内置)
  4. styled-components(需自行配置)
  5. Tailwind CSS(需自行配置)

3.8 Next.js 的 3 种基本渲染方式

  1. 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>;
}
复制代码
  1. Static Generation (Recommended)

一般以展示一些静态固定数据为主,打包的时候就直接生成,比如博客页面、固定营销页面、帮助文档等

import { getAllPostIds } from "@/lib/posts";

export async function getStaticProps() {
  const allListData = await getAllPostIds();

  return {
    props: {
      allListData,
    },
  };
}

export default function List({ allListData }) {
  // ...
}
复制代码
  1. Server-side Rendering

    以动态数据为主,每次请求的时候都在服务端执行,对服务器压力比较大

export async function getServerSideProps(context) {
  return {
    props: {
      list: [...]
    }
  }
}

export default function List({ list }) {
  // ...
}
复制代码

4. 部署

4.1 使用 Vercel 快速部署

使用 github 账户注册登录Vercel官网,授权访问该仓库,即可快速部署,部署完即可访问。还能看到部署日志。

4.2 部署到自己的服务器

4.2.1 在服务器上进行 docker 镜像制作,然后部署

这里服务器以 centos 为例

第 1 步:Dockerfile 文件
  1. 使用 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"]
复制代码
  1. 使用自己的 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"]
复制代码
第 2 步: 将源代码搞到服务器上
  1. 使用scp命令手动将本地源代码上传至服务器
scp -r local_dir [email protected]:remote_dir
复制代码
  1. 或者在远程服务器上wget下载 github 源码,然后解压
wget https://github.com/xxx/main.zip -O main.zip && unzip main.zip -d .
复制代码
  1. 使用xshell工具上传文件到服务器

上面 3 种方法都可以把本地文件传到服务器对应目录

第 3 步: docker 镜像制作

前提是安装好了 docker 并启动

# 切换都源码目录执行
docker image build -t blog-demo .
复制代码

接着会看到命令行上正在执行镜像制作过程,顺利的话,就成功了

build.jpg

# 不出意外,可以看到刚才制作的镜像
docker image ls
复制代码
第 4 步: 启动容器

前提要在服务器上开好安全组

docker container run -d -p 80:3000 -it blog-demo
# -d: 后台运行容器
# -p: 前面80是本机服务器开放端口,后面3000是容器暴露出来的端口
# --name:给容器命名
复制代码

不出意外,容器成功运行。可以在浏览器里进行访问了。

4.2.2 使用github actions自动部署

上面手动步骤太麻烦了,需要解放双手

这里也可以选择 dockerhub,注册好后,创建仓库,即可推送。阿里云的镜像容器服务,也需要提前开通准备好(命名空间+私人仓库)。

第 1 步:提前准备好
  • 容器登录账号+密码
  • 服务器的 HOST + 登录账户 + 密码
  • 阿里云或者 dockerhub 的镜像容器仓库
第 2 步:github 该仓库 Settings->Secrets 添加秘钥,即上面准备好的这 5 个

我这里买的是阿里云的屌丝 1 核 2G 机器,生产环境别这么玩,账号密码可能泄露

secrets.jpg

第 3 步:项目根目录添加github yml配置文件

.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 }}

复制代码
第 4 步:提交代码,自动部署

不出意外,在仓库的 Actions 里看到一切 ok

git add .
git commit -m "chore: add github actions yml"
git push -u origin main
复制代码

deploy.jpg

当前博客点击预览

666.png

5. 参考资料

  1. Next.js 官方文档

  2. 如何优化 node 项目的 docker 镜像

  3. Docker 入门教程

  4. 前后端同构和模板渲染的区别是什么呢?

  5. 手把手教你用 Github Actions 部署前端项目

猜你喜欢

转载自juejin.im/post/7050819548722757639