
매일 말로만 듣던 PWA를 이번 큐시즘 밋업 프로젝트 Glit에서 직접 세팅해볼 기회가 생겼습니다.
글릿은 가벼운 기록으로 무심코 흘려보내는 업무 경험들을 취업 시장의 언어인 ‘직무 역량 데이터’로 치환해 주는 AI 커리어 아카이빙 서비스입니다. 해당 프로젝트는 Next.js 기반인데, 앱 푸시 알림과 앱 레이아웃을 고려했을 때 PWA 도입이 불가피했습니다. 생각보다 간단하니 필요하신 분들은 꼭 참고해보시길 바랍니다!
Progressive Web Application의 약자로, 공식 문서 표현을 빌리자면 "the reach and accessibility of web applications combined with the features and user experience of native mobile apps"입니다. 즉, 웹 기술로 만들었지만 네이티브 앱의 UX를 제공하는 앱이라고 할 수 있습니다.
공식 문서에서 언급하는 PWA의 주요 장점은 세 가지입니다.
Next.js App Router는 PWA에 필요한 기능들을 내장으로 지원하기 때문에 별도 라이브러리 없이도 세팅이 가능합니다.
공식 문서의 Step 1은 Web App Manifest 생성입니다. 브라우저가 이 파일을 읽어서 앱 이름, 아이콘, 화면 모드 등을 결정하고, 이를 통해 사용자가 홈 화면에 PWA를 설치할 수 있게 됩니다(native app-like experience).
Next.js는 app/manifest.ts를 자동으로 감지해서 /manifest.webmanifest 경로로 서빙해줍니다. 공식 문서의 기본 예시는 아래와 같습니다.
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Next.js PWA',
short_name: 'NextPWA',
description: 'A Progressive Web App built with Next.js',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#000000',
icons: [
{ src: '/icon-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512x512.png', sizes: '512x512', type: 'image/png' },
],
}
}
공식 문서에서는 이 파일이 "the name, icons, and how it should be displayed as an icon on the user's device"에 대한 정보를 담아야 한다고 설명합니다. 아이콘 파일은 favicon generator 같은 툴로 생성해서 public/ 폴더에 넣으라고 안내하고 있습니다.
공식 문서에서는 192x192와 512x512 두 가지 사이즈를 기본으로 안내합니다. 실제 서비스에서는 maskable 아이콘을 별도로 준비하는 것이 권장됩니다. 저는 구형 Android 대응을 위해 각 사이즈별로 모두 등록했습니다.
| 파일명 | 사이즈 | Purpose | 용도 |
|---|---|---|---|
| icon-192x192.png | 192×192 | — | 일반 아이콘 (기본값) |
| icon-192x192.png | 192×192 | maskable | Android 적응형 아이콘 |
| icon-512x512.png | 512×512 | — | 스플래시 화면, 고해상도 |
| icon-512x512.png | 512×512 | maskable | Android 적응형 아이콘 (대형) |
아래는 실제 사용한 manifest.ts 파일입니다.
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Glit",
short_name: "Glit",
description: "KUSITMS 33rd 밋업 프로젝트 Glit",
start_url: "/",
display: "standalone",
background_color: "#111111", // 스플래시 화면 배경
theme_color: "#111111", // 상단 상태바 색상
icons: [
{ src: "/icon-192x192.png", sizes: "192x192", type: "image/png" },
{ src: "/icon-192x192.png", sizes: "192x192", type: "image/png", purpose: "maskable" },
{ src: "/icon-512x512.png", sizes: "512x512", type: "image/png" },
{ src: "/icon-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
],
};
}
name / short_name : 홈 화면에 표시되는 앱 이름입니다. short_name은 공간이 부족할 때 사용됩니다.display: "standalone" : 브라우저 주소창 없이 앱처럼 실행됩니다. fullscreen, minimal-ui, browser도 선택 가능합니다.background_color : 앱 로드 전 스플래시 화면의 배경색입니다.theme_color : Android Chrome 상단 상태바 색상입니다. iOS 대응은 layout.tsx의 appleWebApp, viewport 설정으로 별도 처리합니다.purpose: "maskable" : Android 적응형 아이콘 대응입니다. 아이콘을 원형,사각형 등 다양한 모양으로 잘라낼 때 이상하게 보이지 않도록 여백을 포함한 버전으로, 일반 아이콘과 반드시 함께 등록해야 합니다.공식 문서는 manifest.ts 생성까지만 안내하지만, iOS Safari 대응과 viewport 처리를 위해 layout.tsx에도 추가 설정이 필요합니다.
import type { Metadata, Viewport } from "next";
export const metadata: Metadata = {
title: "Glit",
description: "KUSITMS 33rd 밋업 프로젝트 Glit",
manifest: "/manifest.webmanifest",
openGraph: {
images: ["/og-image.png"],
},
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "Glit",
},
};
export const viewport: Viewport = {
themeColor: "#111111",
viewportFit: "cover",
};
iOS에서 홈 화면에 추가했을 때 적용되는 메타 설정입니다.
capable: true를 넣어야 Safari가 아닌 앱처럼 전체화면으로 실행됩니다. statusBarStyle은 상태바 텍스트(시간, 배터리 등) 색상을 제어하며 "default"(검정), "black"(흰색), "black-translucent"(흰색 + 상태바 투명) 중 선택할 수 있습니다. 서비스 배경색에 맞춰 가독성 있는 값으로 설정하면 됩니다.
Next.js 14부터 themeColor는 metadata가 아닌 별도 viewport export로 분리되었습니다.
viewportFit: "cover"는 뷰포트를 상태바 영역까지 확장시켜주는 옵션입니다. themeColor는 Android Chrome의 상단 툴바 색상을 지정하며, iOS Safari에서는 브라우저 탭 색상에 적용됩니다.
위 단계대로 PWA 설정을 마쳤다면, 배포된 서비스를 iPhone에 설치하는 방법을 알아보겠습니다.
공식 문서의 예제는 최소한의 뼈대만 보여주기 때문에 실제 서비스에서는 iOS 대응, maskable 아이콘, viewport 처리까지 직접 챙겨야 합니다. 별도 라이브러리 없이 Next.js 내장 API만으로 PWA 세팅이 가능하다는 게 매우 편했습니다.
서비스 워커와 푸시 알림 구현은 다음 글에서 이어서 다루겠습니다!