[nuxt3] PWA 적용하기

Heegun
더컴퍼스 기술 블로그

--

PWA

모바일과 데스크톱 모두에서 사용할 수 있는 웹 애플리케이션을 말하며, 기존의 웹 기술로 개발이 가능한 것이 특징이다. 커져가는 모바일 시장에 대응하기 위해 개발된 기술이기 때문에 PWA 는 기존의 웹 앱과 네이티브 앱에 비해 다양한 장점을 가지고 있다. 모바일 기기에 특화된 앱인 네이티브 앱은 앱 스토어에서 다운로드 과정을 거쳐야 한다는 번거로움이 있었다. 반면, PWA 는 웹 브라우저의 url을 통해 즉시 접근할 수 있다.

또한, 기존의 웹 앱은 오프라인에서 컨텐츠를 제공할 수 없는 것에 비해, PWA 는 서비스 워커를 사용하여 오프라인에서도 작동할 수 있다.

PWA 는 기술된 장점들 이외에도 많은 기능들을 가지고 있다. 본 글에서는 PWA, 서비스 워커를 Nuxt 프로젝트에서 사용하는 방법에 대해서 작성하겠다.

먼저 Nuxt3 프로젝트를 시작하자.

npx nuxi init <프로젝트명>
초기 프로젝트 구조

프로젝트 세팅이 완료 되면 PWA 를 추가한다.

yarn install
yarn add @nuxtjs/pwa

해당 작업이 완료되고 package.json 파일을 확인해보면 pwa dependency 가 추가된 것을 볼 수 있다.

// package.json
{
"name": "nuxt-app",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@nuxt/devtools": "latest",
"@types/node": "^18.17.0",
"nuxt": "^3.6.5"
},
"dependencies": {
"@nuxtjs/pwa": "^3.3.5"
}
}

다음으로 서비스 워커 파일을 생성한다. 서비스 워커의 경우 프로젝트 전역에서 사용되어야 하므로 루트 경로에 설치하는 것이 좋다. 추가로 app.vue 파일을 다음과 같이 수정해주자.

// app.vue

<template>
<div>
<!-- NuxtWelcome -> NuxtPage -->
<NuxtPage />
</div>
</template>
<style>
</style>
<script setup>
onMounted(() => {
navigator.serviceWorker.register('/sw.js') // 서비스 워커 등록
})
</script>

위에서도 말했듯이 서비스 워커는 전역적으로 사용되어야 하기 때문에 app.vue 파일에서 등록해야 한다. 또한 composition api 를 사용하는 경우 onMounted 훅스를 사용하여 navigator 객체를 호출한다.

개발자 도구(F12) -> 애플리케이션 -> Service Worker

위의 과정을 완료하면 서비스 워커의 동작 상태를 브라우저에서 확인할 수 있다.

오프라인 동작

그럼 이제 서비스 워커를 사용해서 오프라인에서도 동작하는 기능을 구현해보자. 우선, 오프라인에서도 동작하는 페이지와 그렇지 않은 페이지를 준비한다.

// pages/index.vue
<template>
<div>
<h1>Home</h1>
<p>index.vue</p>
</div>
</template>

<script setup>
</script>

<style>
</style>

// pages/offline.vue
<template>
<div>
<h2>Offline</h2>
<p>오프라인</p>
</div>
</template>

<script setup>
</script>

<style>
</style>

// pages/online.vue
<template>
<div>
<h2>Online</h2>
<p>온라인</p>
</div>
</template>

<script setup>
</script>

<style>
</style>

다음으로 sw.js 파일을 작성해준다.

// sw.js
const cacheName = 'cache-name';
const cacheUrl = [
'/',
'/offline',
];

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(cacheName)
.then((cache) => cache.addAll(cacheUrl))
)
});

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});

서비스 워커의 install 이벤트를 등록한다. install 은 서비스 워커가 최초로 설치되는 시점에 실행되는 이벤트로 해당 소스에서는 브라우저에 캐싱을 하는 기능을 한다. ‘cache-name’ 이라는 이름으로 캐시를 생성하고, 해당 캐시 안에 cacheUrl 배열에 있는 경로들을 저장한다. 이러한 캐싱 작업이 완료될 때까지 install 이벤트를 유지하기 위해 waitUntil 메서드를 사용한다.

또한, fetch 이벤트의 respondWith 메서드를 사용해서 네트워크 요청에 대한 응답을 제어할 수 있다. fetch 이벤트는 리소스를 요청할 때마다 발생하는 이벤트이다. match 메서드를 사용하여 캐시에 요청에 대한 응답이 있는지 확인한 후 캐시 내부에 응답이 저장되어 있으면 해당 응답을 반환한다. 만약 캐시 내부에 원본 서버로부터 데이터를 받아와서 캐시에 저장하고 해당 응답을 반환한다.

이후 서비스 워커는 이 캐시를 사용하여 오프라인 상태에서도 해당 리소스들을 불러와 사용할 수 있다. 생성한 캐시는 브라우저에서 확인할 수 있다.

개발자 도구(F12) -> 애플리케이션 -> 캐시 저장공간

캐시에 저장된 경로들(index.vue, offline.vue)은 오프라인 상태가 되어도 접근할 수 있는 반면, 캐시에 저장되지 않은 페이지(online.vue)는 사이트에 연결할 수 없다고 뜨며 접근이 되지 않는 것을 볼 수 있다.

index.vue, offline.vue
online.vue

PWA 는 서비스 워커의 이러한 기능을 통해 네트워크 연결이 불안정하거나 오프라인 환경에서도 컨텐츠를 제공할 수 있도록 할 수 있다.

error

// sw.js
...
const cacheUrl = [
'/',
'/offline',
'/none'
];
...

만약 다음과 같이 존재하지 않는 경로를 캐시에 저장하려고 하면, 다음과 같은 에러가 발생하며 캐시가 저장되지 않는 것을 볼 수 있다.

Uncaught (in promise) TypeError: Failed to fetch

이 오류가 발생하는 이유는 fetch 요청이 올바르게 처리되지 않아서인데, 이 경우는 요청한 리소스의 경로가 잘못되었거나 존재하지 않아서 발생하는 경우이다. 그렇기 때문에 해당 오류가 발생했다면 경로를 올바르게 지정했는지 한번 더 확인하길 바란다.

--

--