Theme:

블로그: 오프라인에서도 되는 블로그에 대해 "왜?"라고 물으면 답할 수 있으신가요?

\n\n> 블로그: 오프라인에서도 되는 블로그에 대해 "왜?"라고 물으면 답할 수 있으신가요?\n블로그에 PWA를 적용하면 한 번 방문한 글을 오프라인에서도 읽을 수 있습니다. 지하철에서 글 읽다가 터널 들어가도 끊기지 않는다는 거죠.

솔직히 개인 블로그에 PWA가 꼭 필요한가 싶기도 했는데, vite-plugin-pwa 하나면 설정이 끝나니까 안 할 이유가 없었습니다.


PWA가 뭔지 간단히

기능설명
오프라인 지원캐시된 페이지를 네트워크 없이 볼 수 있음
홈 화면 추가앱 아이콘으로 바로가기 생성
전체화면 모드브라우저 주소창 없이 앱처럼 동작

핵심은 Service Worker 입니다. 브라우저와 네트워크 사이에 프록시처럼 동작하면서, 요청을 가로채서 캐시된 응답을 돌려줍니다.


vite-plugin-pwa 설정

Service Worker를 직접 작성하면 꽤 복잡한데, vite-plugin-pwa가 Workbox 기반으로 자동 생성해줍니다.

JAVASCRIPT
// vite.config.js
import { VitePWA } from "vite-plugin-pwa";

export default defineConfig({
    plugins: [
        react(),
        VitePWA({
            registerType: "autoUpdate",
            manifest: {
                name: "sim.junghun — 개발 블로그",
                short_name: "sim.junghun",
                description: "개발 블로그",
                theme_color: "#0a0a0f",
                background_color: "#0a0a0f",
                display: "standalone",
                start_url: "/",
                scope: "/",
                icons: [
                    { src: "/web-app-manifest-192x192.png", sizes: "192x192", type: "image/png", purpose: "any" },
                    { src: "/web-app-manifest-192x192.png", sizes: "192x192", type: "image/png", purpose: "maskable" },
                    { src: "/web-app-manifest-512x512.png", sizes: "512x512", type: "image/png", purpose: "any" },
                    { src: "/web-app-manifest-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
                ],
            },
            devOptions: {
                enabled: false,
            },
        }),
    ],
});

몇 가지 포인트:

  • registerType: "autoUpdate" — 새 버전이 감지되면 자동 업데이트합니다. 사용자가 새로고침 안 해도 최신 버전을 봅니다.
  • display: "standalone" — 홈 화면에서 열면 브라우저 UI 없이 앱처럼 보입니다.
  • devOptions.enabled: false — 개발 중에는 Service Worker를 끕니다. 안 끄면 코드 수정이 캐시에 가려서 반영 안 되는 지옥을 맛봅니다.

Workbox 캐싱 전략

어떤 리소스를 어떻게 캐싱할지 설정하는 부분입니다.

JAVASCRIPT
workbox: {
    // 프리캐싱: 빌드 결과물을 Service Worker 설치 시 미리 캐싱
    globPatterns: ["**/*.{js,css,html,ico,svg,woff,woff2}"],
    maximumFileSizeToCacheInBytes: 3 * 1024 * 1024,

    // SPA 라우팅: 모든 경로에서 index.html 반환
    navigateFallback: "/index.html",

    // 런타임 캐싱: 동적 리소스
    runtimeCaching: [
        {
            urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp)$/i,
            handler: "CacheFirst",
            options: {
                cacheName: "images",
                expiration: {
                    maxEntries: 100,
                    maxAgeSeconds: 30 * 24 * 60 * 60,  // 30일
                },
            },
        },
        {
            urlPattern: /\/src\/assets\/posts\//,
            handler: "CacheFirst",
            options: {
                cacheName: "post-assets",
                expiration: {
                    maxEntries: 200,
                    maxAgeSeconds: 30 * 24 * 60 * 60,
                },
            },
        },
    ],
},

프리캐싱 vs 런타임 캐싱

프리캐싱런타임 캐싱
시점Service Worker 설치 시사용자가 실제 접근할 때
대상JS, CSS, HTML, 폰트이미지, 포스트 에셋
특징방문 전에 미리 다운로드한 번 본 것만 캐시

JS/CSS 같은 핵심 에셋은 프리캐싱으로 미리 받아두고, 이미지처럼 양이 많은 건 런타임에 캐싱합니다.

CacheFirst 전략

캐시에 있으면 캐시에서 바로 반환하고, 없으면 네트워크로 요청합니다. 이미지나 폰트처럼 잘 안 바뀌는 리소스에 적합합니다. 한 번 로드된 이미지는 30일 동안 네트워크 요청 없이 바로 나옵니다.


Web App Manifest

manifest 설정에서 주의할 점 몇 가지:

  • 아이콘은 192px, 512px 두 가지 필수 — 둘 다 없으면 설치 프롬프트가 안 뜸
  • purpose: "maskable" — Android에서 기기별 아이콘 형태(원형, 사각형 등)에 맞게 잘려서 표시됨. anymaskable 둘 다 지정하는 게 좋음
  • theme_color — 모바일 브라우저 상단 색상. 블로그 배경색이랑 맞추면 일체감이 생김

빌드 결과

빌드하면 이런 로그가 나옵니다.

PLAINTEXT
PWA v0.x.x
mode: generateSW
precache: 25 entries (1564.42 KiB)

25개 항목(약 1.5MB)이 프리캐싱됩니다. JS, CSS, HTML, 폰트 파일이 전부 포함된 겁니다.


개발 중 주의사항

개발 모드에서 Service Worker가 켜져 있으면 진짜 골치 아픕니다.

  • 코드 수정해도 캐시된 파일이 계속 나옴
  • "왜 안 되지?" 하고 2시간 삽질 후 캐시 문제인 걸 발견
  • Chrome DevTools에서 "Application > Service Workers > Unregister" 해야 함

그래서 devOptions.enabled: false로 개발 중에는 완전히 꺼뒀습니다. 프로덕션에서만 동작하도록요.


정리

설정역할
vite-plugin-pwaService Worker + Manifest 자동 생성
registerType: autoUpdate새 버전 감지 시 자동 업데이트
globPatterns빌드 에셋 프리캐싱
runtimeCaching (CacheFirst)이미지/포스트 에셋 동적 캐싱
manifest앱 이름, 아이콘, 디스플레이 모드

vite-plugin-pwa 덕분에 Service Worker 코드를 직접 작성하지 않아도 됩니다. 설정 파일 하나면 오프라인 지원이랑 홈 화면 추가까지 전부 됩니다.

댓글 로딩 중...