[프론트엔드 배포 파트 5] Docker를 사용하여 단일 페이지 애플리케이션에 대한 nginx 구성 최적화 배포

계속 만들고 성장을 가속화하십시오! 오늘은 "너겟 데일리 뉴플랜 · 6월 업데이트 챌린지"에 참가한 첫날입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다.

안녕하세요 여러분, 저는 Shanyue입니다. 이것은 새로 연 칼럼인 Front-end Deployment Series 입니다. Docker, CICD 등을 포함하여 개요 다이어그램은 다음과 같습니다.

개요

또한 Bilibili는 관련 비디오를 업데이트하고 있습니다. 프런트 엔드

프런트 엔드 배포 시리즈 업데이트 중 : 5/15


지난 기사에서는 Docker의 빌드 캐싱 및 다단계 빌드를 사용한 캐싱 최적화에 대해 설명했습니다.

그러나 단일 페이지 응용 프로그램을 배포할 때 여전히 한 가지 문제가 있습니다. 바로 클라이언트 측 라우팅입니다.

이 기사에서는 간단한 1페이지 라우터가 Docker를 통해 react-router-dom구현 되고 배포됩니다.

추신: 이 프로젝트는 cra-deploy 웨어하우스를 연습으로 사용하며 구성 파일은 router.Dockerfile 에 있습니다.

라우팅

단일 페이지 응용 프로그램 에 대한 경로 react-dom를 . 라우팅은 이 열의 핵심 내용이 아니므로 라우팅 사용법은 생략합니다. 최종 코드는 다음과 같습니다.

소스 코드는 cra-deploy/src/App.js 에 있습니다.

import logo from './logo.svg';
import './App.css';
import { Routes, Route, Link } from 'react-router-dom';

function Home() {
  return (
    <div>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1>当前在 Home 页面</h1>
        <Link to="/about" className="App-link">About</Link>
      </header>
    </div>
  )
}

function About() {
  return (
    <div>
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1>当前在 About 页面</h1>
        <Link to="/" className="App-link">Home</Link>
      </header>
    </div>
  )
}

function App() {
  return (
    <div>
      <Routes>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
      </Routes>
    </div>
  );
}

export default App;
复制代码

이 시점에서 두 가지 경로가 있습니다.

  1. /,첫 장
  2. /about, 페이지 정보

재배포하십시오. 라우팅에 문제가 있습니다.

이전 기사의 docker-compose구성 재배포합니다.

$ docker-compose up --build simple
复制代码

이 시점 https://localhost:4000/about에서 404가 표시됩니다.

404 찾을 수 없음

其实道理很简单:在静态资源中并没有 about 或者 about.html 该资源,因此返回 404 Not Found。而在单页应用中,/about 是由前端通过 history API 进行控制。

解决方法也很简单:在服务端将所有页面路由均指向 index.html,而单页应用再通过 history API 控制当前路由显示哪个页面。 这也是静态资源服务器的重写(Rewrite)功能。

我们在使用 nginx 镜像部署前端应用时,可通过挂载 nginx 配置解决该问题。

nginx 的 try_files 指令

在 nginx 中,可通过 try_files 指令将所有页面导向 index.html

location / {
    # 如果资源不存在,则回退到 index.html
    try_files  $uri $uri/ /index.html;  
}
复制代码

此时,可解决服务器端路由问题。

除此之外,我们还可以通过 nginx 配置解决更多问题。

长期缓存 (Long Term Cache)

在 CRA 应用中,./build/static 目录均由 webpack 构建产生,资源路径将会带有 hash 值。

$ tree ./build/static
./build/static
├── css
│   ├── main.073c9b0a.css
│   └── main.073c9b0a.css.map
├── js
│   ├── 787.cf6a8955.chunk.js
│   ├── 787.cf6a8955.chunk.js.map
│   ├── main.a3facdf8.js
│   ├── main.a3facdf8.js.LICENSE.txt
│   └── main.a3facdf8.js.map
└── media
    └── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg

3 directories, 8 files
复制代码

此时可通过 expires 对它们配置一年的长期缓存,它实际上是配置了 Cache-Control: max-age=31536000 的响应头。

那为什么带有 hash 的资源可设置长期缓存呢: **资源的内容发生变更,他将会生成全新的 hash 值,即全新的资源路径。**而旧有资源将不会进行访问。

location /static {
    expires 1y;
}
复制代码

nginx 配置文件

总结缓存策略如下:

  1. 带有 hash 的资源一年长期缓存
  2. 非带 hash 的资源,需要配置 Cache-Control: no-cache,避免浏览器默认为强缓存

캐시 제어

nginx.conf 文件需要维护在项目当中,经过路由问题的解决与缓存配置外,最终配置如下:

该 nginx 配置位于 cra-deploy/nginx.conf

server {
    listen       80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;

    location / {
        # 解决单页应用服务端路由的问题
        try_files  $uri $uri/ /index.html;  

        # 非带 hash 的资源,需要配置 Cache-Control: no-cache,避免浏览器默认为强缓存
        expires -1;
    }

    location /static {
        # 带 hash 的资源,需要配置长期缓存
        expires 1y;
    }
}
复制代码

Dockerfile 配置文件

此时,在 Docker 部署过程中,需要将 nginx.conf 置于镜像中。

修改 router.Dockerfile 配置文件如下:

PS: 该 Dockerfile 配置位于 cra-deploy/router.Dockerfile

FROM node:14-alpine as builder

WORKDIR /code

# 单独分离 package.json,是为了 yarn 可最大限度利用缓存
ADD package.json yarn.lock /code/
RUN yarn

# 单独分离 public/src,是为了避免 ADD . /code 时,因为 Readme/nginx.conf 的更改避免缓存生效
# 也是为了 npm run build 可最大限度利用缓存
ADD public /code/public
ADD src /code/src
RUN npm run build

# 选择更小体积的基础镜像
FROM nginx:alpine
ADD nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder code/build /usr/share/nginx/html
复制代码

PS: 该 docker compose 配置位于 cra-deploy/router.Dockerfile

version: "3"
services:
  route:
    build:
      context: .
      dockerfile: router.Dockerfile
    ports:
      - 3000:80
复制代码

使用 docker-compose up --build route 启动容器。

  • 访问 http://localhost:3000 页面成功。
  • 访问 http://localhost:3000/about 页面成功。

检验长期缓存配置

访问 https://localhost:3000 页面,打开浏览器控制台网络面板。

此时对于带有 hash 资源, Cache-Control: max-age=31536000 响应头已配置。

此时对于非带 hash 资源, Cache-Control: no-cache 响应头已配置。

응답 헤더 설정 보기

百尺竿头更进一步

在前端部署流程中,一些小小的配置能大幅度提升性能,列举一二,感兴趣的同学可进一步探索。

构建资源的优化:

  1. 使用 terser 压缩 Javascript 资源
  2. 使用 cssnano 压缩 CSS 资源
  3. 使用 sharp/CDN 压缩 Image 资源或转化为 Webp
  4. 使用 webpack 将小图片转化为 DataURI
  5. 使用 webpack 进行更精细的分包,避免一行代码的改动使大量文件的缓存失效

小结

其实,从这里开始,前端部署与传统前端部署已逐渐显现了天壤之别。

기존의 프론트엔드 배치는 운영 및 유지보수가 지배적이며 , 온라인이 될 때마다 운영 및 유지보수 로 완료되는 프로젝트 프론트엔드의 온라인 단계의 운영 및 유지보수를 통지하고 , 프론트 엔드는 배포의 자유가 적습니다.

예를 들어 gzip/brotli압축 Cache-Control응답헤더 제어, 서로 다른 경로의 캐싱전략 등은 운영과 유지보수가 완료되었음을 알려야 하고 버전관리가 어렵다 .

프런트 엔드 배포 자유도의 확장은 다음 두 가지 측면에서 반영됩니다.

  1. Docker를 통해 프런트 엔드를 컨테이너화하여 더 이상 이메일로 운영 및 유지 관리 온라인 단계를 알릴 필요가 없습니다.
  2. Docker 및 nginx 구성 파일을 통해 프런트 엔드에서 nginx를 구성합니다. 프로젝트와 밀접하게 관련된 일부 작고 사소한 구성은 운영 및 유지 관리 개입이 필요하지 않습니다.

이 시점에서 Docker에서 프런트 엔드를 배포하는 방법에 대한 장은 끝났고 실제로 정적 리소스는 CDN에 배치되는 경우가 많습니다.

그럼 어떻게 대처해야 할까요?

추천

출처juejin.im/post/7102057861320015885