
목표
PWA 자체는 사실 Manifest 파일과 Metadata 정보만 있다면 쉽게 구현할 수 있어요. 따로 패키지를 설치할 필요는 없지만, 서비스 워커 및 Workbox를 포함하여 PWA 환경을 위한 여러 기능들을 제공하고 있는 next-pwa 패키지를 이용해도 괜찮습니다. 해당 패키지에 대한 자세한 설명은 여기를 참고해보시면 좋을 것 같아요.
이번 포스트에서는 next-pwa의 기능이 필요하지 않아서 넘어가볼게요.
Manifest 파일을 작성하는 방법은 두 가지가 있습니다. 하나는 직접 Manifest 파일을 작성하는 것이고, 또 다른 하나는 Next.js에서 지원하는 파일 컨벤션을 사용하는 방법이에요. 자세한 내용은 Next.js의 공식문서를 참고해주세요.
Static File로 작성하는 방법은 public폴더에 manifest.json, 또는 manifest.webmanifest라는 이름으로 작성하고, link 태그를 통해 연결하는 방법이에요. 다만, Next.js에서는 해당 과정을 파일 컨벤션을 통해 제공하고 있기 때문에 public 폴더가 아닌 app 폴더의 루트에 저장해주면 됩니다.
.webmanifest 확장자라뇨?
웹에서도 Manifest에 대한 쓰임이 늘어나자 IANA는 표준화된 확장자를 제시했는데, 그것이 요 확장자입니다. 이 파일의 MIME type은 application/manifest+json이에요. 관련 내용은 여기를 참고해주세요.
기본적인 Manifest 예시
{ "lang": "en", "dir": "ltr", "name": "Super Racer 3000", "short_name": "Racer3K", "icons": [{ "src": "icon/lowres.webp", "sizes": "64x64", "type": "image/webp" }, { "src": "icon/lowres.png", "sizes": "64x64" }, { "src": "icon/hd_hi", "sizes": "128x128" }], "scope": "/", "id": "superracer", "start_url": "/start.html", "display": "fullscreen", "orientation": "landscape", "theme_color": "aliceblue", "background_color": "red" }
아니면 app 폴더 루트에 manifest.ts 파일을 작성 후 위에서의 json 정보와 똑같이 작성하면 됩니다.
import { MetadataRoute } from 'next'; export default function manifest(): MetadataRoute.Manifest { return { /** manifest.json에 적는 것 그대로 *\/ }; }
하지만 바뀔 내용이 없는 정적인 정보들이기 때문에 2의 방법보단 1의 방법을 쓰는 것이 나아보여요. 동적으로 Manifest를 관리해주는 상황이 아니라면...?
Metadata 정보를 제공하는 방법은 직접 link 및 meta 태그를 통해 제공하는 것과 Next.js에서 제공하는 파일 컨벤션대로 따라가는 방법이 있습니다.
먼저 link 태그는 주로 아이콘 설정과 iOS의 Splash Image(or startup-image)를 위해 작성됩니다. 보통 아래와 같은 이름으로 작성됩니다.
<link rel="icon" /> <link rel="apple-touch-icon" /> <link rel="apple-touch-startup-image" />
사이즈 별로, 환경 별로 지정을 해줄 수 있는데요, 이 모든 걸 직접 작성하기엔 아주 열받으니 Generator를 활용해 기입해줍시다. 이에 대한 내용은 이전 포스트를 참고해주세요.
그 다음 meta 태그는 정보를 위해서 작성됩니다. SEO도 그렇고, PWA 설정에도 필요합니다. meta 태그의 종류도 여러 개가 있으나, 이 역시 meta tag generator를 활용하여 작성해주면 편리합니다. 관련한 Generator는 검색만 해도 주르륵 나오니 아무거나 골라 쓰면 됩니다. 기본적인 정보는 아래와 같습니다.
<meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <meta name="theme-color" content="#ffffff" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <!-- Primary Meta Tags --> <meta name="title" content=""> <meta name="description" content=""> <!-- Open Graph / Facebook --> <meta property="og:type" content="website"> <meta property="og:url" content=""> <meta property="og:title" content=""> <meta property="og:description" content=""> <meta property="og:image" content=""> <!-- Twitter --> <meta property="twitter:card" content="summary_large_image"> <meta property="twitter:url" content=""> <meta property="twitter:title" content=""> <meta property="twitter:description" content=""> <meta property="twitter:image" content=""> <!-- Optional Meta Tags --> <meta name="keywords" content=""> <meta name="robots" content="index, follow"> <meta http-equiv="Content-Type" content="text/html; charset="> <meta name="language" content=""> <meta name="revisit-after" content=" days"> <meta name="author" content="">
Next.js은 Metadata를 위한 파일 컨벤션이 존재합니다. 해당 컨벤션대로 파일을 만들면 Next.js에서 자동으로 link 태그를 주입시킵니다. 자세한 내용은 여기를 참고해주세요.

개인적으로 Next.js에서 제공하는 Metadata에 대한 파일 컨벤션은 처음 사용해봤는데요, 저에겐 조금 마음에 들지 않더라구요.
- 사이즈가 여러 개면 지저분해지는 폴더 구조
- startup-image 파일 컨벤션 미존재
사이즈를 여러 형태로 제공해주려면 apple-icon뒤에 숫자를 붙여주면 됩니다. 그러면 알아서 파일 사이즈를 파악해서 넣어주게 됩니다...만, 위의 사진에서 확인할 수 있듯이, 여러 개를 제공할 시 폴더 구조가 조금 지저분해집니다.
이에 대해서 assets 폴더를 넣고 관리를 해줄려고 해도 app 폴더 바로 아래가 아니라면 인지하지 못하더라구요.
startup-image는 따로 컨벤션이 없어서 metadata에 추가를 해주어야 하는데요, 그럴 거면 아이콘도 metadata에 넣고 말지! 라는 생각이 드는 건.. 어쩔 수 없나봅니다.
마지막으로 opengraph에 필요한 이미지 역시 제공 중입니다. 하지만 metadata에 필요한 파일을 루트가 아니라 따로 지정해주는 폴더가 있었으면 좋겠다는 생각이 ...
그 다음 최상위 layout.tsx 파일에서 metadata를 export하는 방법입니다.
layout.tsx
export const viewport: Viewport = { themeColor: '#212121', }; export const metadata: Metadata = { title: 'PWA Push Notification Demo', description: 'PWA 환경에서 Push Notiication 기능을 테스트합니다.', applicationName: 'PWA Push Notification Demo', appleWebApp: { capable: true, title: 'PWA Push Notification Demo', statusBarStyle: 'black-translucent', startupImage: [ { media: 'screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)', url: '/assets/splash_screens/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_portrait.png', }, ], }, openGraph: { type: 'website', title: 'PWA Push Notification Demo', description: 'PWA 환경에서 Push Notiication 기능을 테스트합니다.', siteName: 'PWA App', }, formatDetection: { telephone: false, }, };
Metadata 타입에는 기본적으로 meta 태그에 쓰이는 정보들이 들어가있습니다. 조금 살펴볼 것은 appleWebApp과 openGraph인데요, 애플은 사실 Manifest만으로 PWA를 구현하지 않습니다. 따로 meta 정보를 제공해주어야 하는데, 이를 위해 Next.js에서 appleWebApp이라는 속성으로 관련 정보를 제공하고 있습니다.
애플은 startupImage는 기기의 크기와 가로/세로에 따라 맞춤 이미지를 제공해야만 정상적으로 노출됩니다. 따라서 여러 개의 이미지를 받을 수 있게 하였고, media를 통해 반응형 환경을 제공해줄 수 있습니다.
개인적인 생각으론 startupImage와 Icon은 자주 변경되지 않는 이미지이므로, Next.js에서 추적해서 load하기 보단 public에 넣고 쓰는 것이 어떤가 싶은 생각입니다.
컴파일을 하게 되면 아래와 같은 경고가 콘솔에 나타납니다.
⚠ metadataBase property in metadata export is not set for resolving social open graph or twitter images, using "http://localhost:3000". See https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase
이상합니다. metadataBase는 메타데이터 내에서 상대경로를 절대 경로로 바꿔주기 위해 지정해주는 속성인데, 지정해주지 않으면 자동으로 기본값으로 지정해주기 때문이죠.
If not configured, metadataBase is automatically populated with a default value.
If not configured, metadataBase has a default value
When VERCEL_URL is detected: https://${process.env.VERCEL_URL} otherwise it falls back to http://localhost:${process.env.PORT || 3000}.
When overriding the default, we recommend using environment variables to compute the URL. This allows configuring a URL for local development, staging, and production environments.
그래서 생략해줬는데 저런 경고를 내뿜는 건 조금,, 열받네요. 신경쓰이니 아래와 같이 처리해주었습니다.
metadataBase: process.env.NODE_ENV === 'development' ? new URL(process.env.LOCAL_DEV_URL) : new URL(process.env.FRONT_DEPLOY_URL),
이후 진행해야 할 것은 PWA 환경이 잘 지원됐는지 확인해야 합니다. 구글 크롬 Devtools의 Lighthoust에서는 이 PWA가 잘 적용됐는지 확인할 수 있는 옵션이 존재합니다. 이를 체크하고 분석을 진행하면 됩니다.

Installable에 녹색불이 들어오면 PWA를 설치할 수 있다는 아이콘도 표시해줍니다.

만약 빨간 불이 들어온다면 어떤 정보가 필요한지 역시 알려주기 때문에 그대로 따라가시면 됩니다.
여기까지 되면 PWA 환경 제공 끝!
iOS는 Safari에서의 Push Notification을 지원하지 않아요. 다만, PWA 환경에서는 지원하기 때문에 지금까지 PWA 환경을 구성한 것이죠.
실제로 Push Notification에 대한 구현은 이전 포스트를 참고해주세요!
일단 PWA만 보고 로직을 짰더니, 아차차 Safari에서는 Push 관련 로직을 제공하지 않는다는 것을 깜빡했어요. 그래서 에러를 만났습니다.
이를 위해서는 PWA가 아닌 환경에 접속했을 시 어떤 식으로 처리해줄 지 고민해봐야 할 것 같습니다.
iOS에서는 최초로 로드했을 때 바로 알림 권한을 요청할 수 없습니다. 정확히는 정책 상 '사용자의 제스처'가 없인 알림 권한을 요청할 수 없습니다.
이 내용은 알고 있었는데, 권고 사항이며 크롬에서는 잘 되길래 그런가보다~ 했습니다만,, iOS에서는 그렇더라요. 따라서 최초 로드 시에 따로 모달을 띄워 요청에 대한 안내를 표출한 후 사용자 제스처를 통해 알림 권한을 요청하는 것이 올바른 흐름일 것 같습니다.
생각해보니 실제 다른 서비스들도 그렇네요😇

사실 제일 기대했던 것 중에 하나가 Push Notification에서 이미지와 액션 클릭을 받아볼 수 있었다는 점이었어요. 크롬에서는 이에 대한 구현이 되어있어서, iOS에서도 되려나 기대가 컸습니다만,,,
iOS의 PWA는 이미지와 액션 클릭을 지원하지 않는 모양입니다. 우우..

그래도 tag를 지정해서 같은 종류의 알림을 묶을 수 있게는 해주었네요. 고맙다야!
찾아보다가 Push Notification에 대해 좋은 가이드가 나와있는 글을 발견했어요.
요기를 참고하시면 좋을 것 같습니다!