
PWA ํ๋ฃจ ๋ง์ ๋์ ํ๊ธฐ(์ฝ์ง๊ธฐ)
์ด๋ฒ ํฌ์คํ
์์๋ ํ์๊ฐ ํ์ฌ์์ 2019๋
7์ 5์ผ ๊ธ์์ผ ํ๋ฃจ ๋์ ๊ธฐ์กด ์ดํ๋ฆฌ์ผ์ด์
์ PWA(Progressive Web Application)
๊ธฐ๋ฅ์ ๋ถํ ์ฝ์ง๊ธฐ๋ฅผ ๊ธฐ๋กํ๋ ค๊ณ ํ๋ค. PWA
๋ ์ง์ํ์ง ์๋ ๋ธ๋ผ์ฐ์ ์ ๋ํ ์์ธ์ฒ๋ฆฌ๋ง ๊ผผ๊ผผํ๊ฒ ํด์ฃผ๋ฉด UX
, ์ฑ๋ฅ
, SEO
๋ฑ์์ ๋ฌด์กฐ๊ฑด ํ๋ฌ์ค ์์ธ์ด๊ธฐ ๋๋ฌธ์ ์์ ๋ถํฐ ๊ณ์ ํด๋ณด๊ณ ์ถ์๋ค.
ํ์ง๋ง ์๊ฐ์ด ์์ด์ ๊ณ์ ๋ฏธ๋ฃจ๊ณ ์์๋๋ฐ ๋ง์นจ ์ด์ ๊ฐ๋ง์ ํ์์๊ฒ ์ฌ์ ์๊ฐ์ด ์ฃผ์ด์ก๋ค.
ํ์๊ฐ ์ง๊ธ ์์
ํ๊ณ ์๋ ํ๋ก์ ํธ๊ฐ ํ์ฌ์ ๋น์ฆ๋์ค ๋ชจ๋ธ๊ณผ ๋ฐ์ ํ ๊ด๋ จ์ด ์๋ ํ๋ก์ ํธ์ด๊ณ , ๋ ์๋ ์ด ๊ธฐ๋ฅ์ ๊ด๋ จ๋ ์ฌ๋ด ์ดํด๊ด๊ณ์(Stakeholder)
๋ค์ด ๋ง์์ PO
๊ฐ ํ
์คํธ๋ฅผ ์ข ๋ ๊ผผ๊ผผํ ํ๊ณ ์ถ๋ค๊ณ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ ๊ธ์์ผ ํ๋ฃจ, ์ ํํ ๋งํ๋ฉด ์ค์ ์๋ ๋ฒ๊ทธ ํฐ์ง๊ฑฐ ํ๋ ํซํฝ์คํ๊ณ ์ ์ฌ๋จน๊ณ ๋์ ์๋ฃ ์กฐ์ฌ๋ ํ์๊ฐ ์ฏค ํด๋ณธ ๋ค์์ 15์ 30๋ถ
์ฏค๋ถํฐ ์์ํด์ 22์ 55๋ถ
๊น์ง ๋ฌ๋ ธ๋ค. ์๋ ํ์๋ ๊ทผ๋ฌด์๊ฐ์ ์ด์ฌํ ํ๊ณ ๋ค๋ฅธ ์๊ฐ์ ๋์๊ฒ ํฌ์ํ์๋ ์ฃผ์๋ผ ์ผ๊ทผ์ ์ ๋งํ๋ฉด ์ํ๋๋ฐ, ํ์์๊ฒ ์ฃผ์ด์ง ์๊ฐ์ด ํ๋ฃจ ๋ฐ์ ์์ด์ ๊ทธ ์์ ๋ฌด์กฐ๊ฑด ๋๋ด์ผ ํ๋ ๊ฒ๋ ์์ง๋ง ์ฌ์ค ์ ์ผ ํฐ ์ด์ ๋โฆ

๋ค, ์ฝ๊ฒ ๋ณด๊ณ ๋ค๋ณ๋ค๊ฐ ์ณ๋ง์์ต๋๋ค.
ํ์๋ ์ฌ์ค PWA
๋ฅผ ๊ตฌํํด๋ณธ ๊ฒฝํ์ด ์๋ค. ๊ตฌ๊ธ์์ ์ ๊ณตํด์ฃผ๋ ๋ฐ๋ชจ๋ ๋ช๋ฒ ๋๋ ค๋ณธ ์ ์ด ์์ง๋ง ๋๋จธ์ง๋ ๊ทธ๋ฅ ๋ค๋ฅธ ๊ต์๋ถ๋ค์ ๋ธ๋ก๊ทธ๋ฅผ ๋ณด๊ณ ์ค...๋๋ ํด๋ณด๊ณ ์ถ๊ตฐ
์ ๋๋ก๋ง ์๊ฐํ์๋ค.
๊ทผ๋ฐ ๋ค๋ฅธ ์ฌ๋๋ค์ด ๊ตฌํํ ๊ฑธ ๋ณด๋ฉด ๋ญ ์ฝ๋๊ฐ ๋ณต์กํ ๊ฒ๋ ์๋๊ณ ์๋น์ค ์์ปค(Service Worker)
๋ ์๋ ์๋ฆฌ๊ฐ ๊ทธ๋ ๊ฒ ์์ํ ๋๋์ ์๋๊ธฐ์ ์ฌ์ค ์๋ณด๊ณ ์์๋ค.
์๋ ํ์์ ๊ณํ
์๋ ํ์๋ PWA
์๊ฒ ์ณ๋ง๊ธฐ ์ ๊น์ง ๊ทธ๋ด์ธํ ๊ณํ์ด ์์๋ค. ๋ฌผ๋ก ํ์๋ ์์ธํ ๊ณต๋ถ๋ฅผ ํ๊ณ ๋ค๋น๋ ํ์
์ด ์๋๋ผ ๊ทธ๋ฅ ๋์ถฉ ์์๋ณธ ๋ค์์ ๋๋จธ์ง๋ ์ง์ ๋ง์๊ฐ๋ฉด์ ๋ฐฐ์ฐ๋ ํ์
์ด๋ผ ๋ ๊ทธ๋ฌ๋ ๊ฒ๋ ์๋ค. ์ด์จ๋ ์ด์ ์ ์ฌ์ ๋จน๊ณ ์์ ํ์๊ฐ ์ฌ๋ฌด์ค์ ์์์ ๊ฐ๋งํ ์๊ฐ์ ํด๋ณธ ๊ฒฐ๊ณผ, โPWA๋ ํด๋ณผ๊นโฆ? Manifest ๋ฃ๊ณ ์๋น์ค ์์ปค ๋ถํ๋ฉด ๋ญ ๋๋จธ์ง๋ ๋ฌธ์๋ณด๊ณ ํด๋ ๋์ถฉ ๋ ๊ฑฐ ๊ฐ์๋ฐโฆ?โ๋ก ๊ฒฐ๋ก ์ด ๋์๋ค.
๊ทธ๋๋ PWA
์ ๋ชจ๋ ๊ธฐ๋ฅ์ ๋ค ๊ตฌํํ๋ ๊ฒ์ ํด๊ทผ ์๊ฐ์ธ 19์
๊น์ง๋ ํ๋ค ๊ฒ ๊ฐ์์ ์์
์ ๋ํ ์ค์ฝํ๋ฅผ ์ก์๋ค.
- ๊ธฐ๋ฅ์ ์ ๋ถ ๊ตฌํํ๋ ๊ฑด ํ๋ค ๊ฒ ๊ฐ์ผ๋ ์ผ๋จ ๊ธฐ๋ฐ์ ์ก์๋๋๋ค๊ณ ์๊ฐํ์.
- ์๋น์ค ์์ปค ์ค์น.
- Manifest.json ์ถ๊ฐ. ์ด๊ฑด ๋จ๋ค ๋คํ๋ ๊ฑฐ๋๊น ์ฐ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํด์ผํจ.
- Pusher SDK์ PushManager๋ฅผ ์ฌ์ฉํด์ ๋ธ๋ผ์ฐ์ ๊ฐ ๊บผ์ ธ์๋๋ผ๋ ์ฌ์ฉ์์๊ฒ ํธ์ ๋ฉ์ธ์ง๋ฅผ ๋ณด์ฌ์ฃผ์.
- ์๊ฐ ๋๋ฉด ๋ชจ๋ฐ์ผ์์ ํ์คํฌ๋ฆฐ์ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ถ๊ฐํ๋ ๊ฒ๊น์ง ํด๋ณด๊ณ ์ถ๋ค.(์ฐ์ ์์ ๋ฎ์)
- ์คํ๋ผ์ธ ์บ์ฑ์ ๋ค์ ์๊ฐ์โฆ(๋๋ฆ ์ค์ฝํ ์กฐ์ )
๋ฌธ์๋ฐ ํ๋ก ๊ณํ๋ฌ
์ ์ด์ ๊ณํ์ด ์กํ์ผ๋ PO์ ํ
์คํฐ์๊ฒ โํ๋ก์ ํธ ํ
์คํธ ํ์๋ ๋์ ์ ๋ ์ ๋ง์ ๋์ดํฐ์์ ๋๋ค ์ค๊ฒ ์ต๋๋คโ๋ผ๊ณ ํ์(๋ผ๊ณ ์ฐ๊ณ ํต๋ณด๋ผ๊ณ ์ฝ๋๋ค.)๋ฅผ ํ ๋ค ๋ง์คํฐ์์ ๋ธ๋์น๋ฅผ ํ๋ ๋๋ค. ๋ธ๋์น ์ด๋ฆ๋ ํ์์ ์์ง๊ฐ ๋๋ณด์ด๋ feature/service-worker-web-push
๋ก ๋ฑ ์ง์ด๋๊ณ 15์ 30๋ถ
์ฏค ๋ถํฐ ์์
์ ์์ํ๋ค. ๊ทธ๋๋ ์ด์ ๋๋ฉด ํ 4~5์๊ฐ
์์ ์ถฉ๋ถํ ๊ฐ๋ฅํ๊ฒ ๋ค ์ถ์๋๋ฐโฆ

๊ฒฐ๊ณผ์ ์ผ๋ก ์ ์ค์์ ๋ฌ์ฑํ ์ ๋๋ก ๋ฌ์ฑํ ๋ชฉํ๋ 1, 2, 3
๋ฒ ๋ฟ์ด๋ค. 4
๋ฒ ๋ชฉํ์ธ ํธ์ ๋ฉ์์ง์ ๊ฒฝ์ฐ, Background ๋ฉ์ธ์ง์ ์ฝ์ง๋ง ํ๋ค๊ฐ ์๊ฐ์ด ๋๋ฌด ๋ง์ด ๊ฐ์ ์คํจํ๊ณ Notification API
๋ฅผ ์ฌ์ฉํ์ฌ Foreground์์๋ง ๋
ธ์ถ๋๋๋ก ๊ตฌํํ๋ค.
์ฒ์์๋ ์จ๊ณ ๋ชจ๋ฐ์ผ ์ฑ์์ ์ด๋ฏธ ์ฌ์ฉํ๊ณ ์๋ FCM(Firebase Cloud Messaging)
์ ์ฌ์ฉํ๋ ค๊ณ ํ๋๋ฐ ๊ทธ๋ฌ๋ฉด ์น ํด๋ผ๋ฆฌ์ธํธ์ ํธ์ ์ฑ๋์ด ์ด์ํ๋๊ธฐ ๋๋ฌธ์ ์ผ๋จ ํ
์คํธ๋ ํด๋ณผ ๊ฒธ FCM
์์ด ์๋น์ค ์์ปค์ PushManager
๋ง ์ฌ์ฉํด์ ๊ตฌํํ๋ ค๊ณ ํ๋ค.
์ด์ ํ์๊ฐ ์ด๊ฒ๋ค์ ์์ ํ๋ฉด์ ์ด๋ค ๋ฌธ์ ์ ๋ด์ฐฉํ๊ณ , ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง ํ๋ฒ ์ค๋ช ํด๋ณด๊ฒ ๋ค.
์ 1 ๊ด๋ฌธ, Manifest.json
์ ์ผ ์ฒ์ ํ ์ผ์ manifest.json
์ ์์ฑํ๋ ๊ฒ์ด๋ค. ์ด๊ฑด ๊ทธ๋ฅ ๋ง ๊ทธ๋๋ก ์์ฑํ๋ฉด ๋๋ค. ๋ํ manifest.json
์ ์ด์ฐจํผ ์ ์ ์ธ ํ์ผ์ด๊ธฐ๋ ํ๊ณ ์
๋ฐ์ดํธ๋ ์ฆ์ง ์์ ํ์ผ์ด๊ธฐ ๋๋ฌธ์ ๊ตณ์ด Express
์์ ์๋ตํ์ง ์์๋ ๋๋ค. ๊ทธ๋์ ํ๋ก์ ํธ ๋ด๋ถ์ static
๋๋ ํ ๋ฆฌ์ manifest.json
์ ์์ฑํ๊ณ nginx
๊ฐ ๋ฐ๋ก ์๋ตํด์ฃผ๋ ๋ฐฉ์์ผ๋ก ์์ฑํ๋ค.
// static/manifest.json
{
"name": "์จ๊ณ ",
"short_name": "์จ๊ณ ",
"icons": [{
"src": "https://d1hhkexwnh74v.cloudfront.net/app_icons/1x.png",
"type": "image/png",
"sizes": "48x48"
}, {
"src": "https://d1hhkexwnh74v.cloudfront.net/app_icons/2x.png",
"type": "image/png",
"sizes": "64x64"
}, {
"src": "https://d1hhkexwnh74v.cloudfront.net/app_icons/3x.png",
"type": "image/png",
"sizes": "128x128"
}, {
"src": "https://d1hhkexwnh74v.cloudfront.net/app_icons/3x.png",
"type": "image/png",
"sizes": "144x144"
}],
"start_url": "/?pwa=true",
"display": "fullscreen",
"background_color": "#FFFFFF",
"theme_color": "#00C7AE",
}
manifest.json
์ ๋ํ ์์ธํ ๋ด์ฉ์ Google Web Developer ๋ฌธ์์ The Web App Manifest๋ฅผ ์ฐธ๊ณ ํด์ ์์ฑํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ ์์ด์ฝ์ ์ง๊ธ ์จ๊ณ ์ฑ์์ ์ฌ์ฉํ๊ณ ์๋ ์์ด์ฝ๋ค์ด๋ค. ๊ตณ์ด ๋ชจ๋ฐ์ผ ์ฑ๊ณผ ๋ค๋ฅธ ์์ด์ฝ์ ์ฌ์ฉํ ์ด์ ๋ ์๊ณ , ๋์์ด๋ ๋ถ๋ค๋ ๋ฐ๋น ์ ๋ฉํ๋๊ฐ ์ํฉ์ด๊ธฐ ๋๋ฌธ์ ํ์์ ๊ธฐ์ ์ ์ธ ์์ฌ ๋๋ฌธ์ ์์ด์ฝ์ ๋ง๋ค์ด ๋ฌ๋ผ๊ณ ํ๊ธฐ์ ๋๋ฌด ๋ฏธ์ํ๋ค.
๋ํ ์ฑ๊ณผ ์น์ด ๊ฐ์ ์์ด์ฝ์ ์ฌ์ฉํด์ผ ๋ธ๋๋ฉ ์ธก๋ฉด์์๋ ์ข์ ๊ฑฐ๋ผ ์๊ฐํด์ ๋ชจ๋ฐ์ผ ์ฑ ๋ ํ์งํ ๋ฆฌ๋ฅผ ํด๋ก ๋ฐ์์ ๋ชฐ๋ ํ์ณ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ ์ด๋ฏธ์ง๋ค์ ๊ตณ์ด ํ๋ก์ ํธ ๋ด์ ์ ์ฅํ ํ์๊ฐ ์์ผ๋ฏ๋ก ํ์ฌ์์ ์ฌ์ฉํ๋ S3
๋ฒํท์ ์
๋ก๋ํ๊ณ CloudFront
๋ก ๋๋ฆฌ๋ฒ๋ฆฌํ๋ค.
์์์ ์ค๋ช
ํ๋ฏ์ด ํ์๋ manifest.json
์ ๋ํ ์์ฒญ์ Express
๊ฐ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ์๋๋ผ ์๋ฒ ์์ง์ธ nginx
๊ฐ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์ ํํ๋๋ฐ, ์ด๋ ๊ฒ nginx
๊ฐ ์๋ฒ ์ดํ๋ฆฌ์ผ์ด์
๊น์ง ์์ฒญ์ ํ ์คํด์ฃผ์ง ์๊ณ ๊ทธ๋ฅ ์์์ ์๋นํ๋๋ก ํ๊ณ ์ถ๋ค๋ฉด ๊ฐ๋จํ ์ค์ ์ ์ถ๊ฐํ๋ฉด ๋๋ค.
server {
...
location ~ ^/static {
root /your/project/location;
}
...
}
์ด๋ฐ ์์ผ๋ก ์ค์ ํ๋ฉด ๋ฐ๋ก ํ๋ก์ ํธ ๋ด์ static
๋๋ ํ ๋ฆฌ์ ์ ๊ทผํ ์ ์๋ค. โ/static
์ผ๋ก ์์ํ๋ ์์ฒญ์ด ๋ค์ด์ค๋ฉด /your/project/location
๊ฒฝ๋ก์์ ๋๊ฐ ์์์ ์ฐพ์์ค~โ๋ผ๋ ์๋ฏธ์ด๋ค. ํ์ง๋ง ๋ก์ปฌ์์ ๊ฐ๋ฐ ์๋ฒ๋ฅผ ์ฌ์ฉํ ๋์๋ nginx๋ฅผ ์ฌ์ฉํ์ง ์๊ณ NodeJS๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ๋ก ๊ฐ๋ฐ์ฉ ์๋ฒ๋ฅผ ๋์ฐ๋ฏ๋ก ๋ก์ปฌ ํ๊ฒฝ์์๋ Express
๊ฐ ์ง์ ํ์ผ์ ์๋นํ ์ ์๋๋ก ํด์ค์ผ ํ๋ค.
if (process.env.NODE_ENV === 'local') {
app.use('/static', serve('./static'));
}
์ด์ ๋ธ๋ผ์ฐ์ ์๊ฒ ๋์ Manifest ํ์ผ์ด ์ฌ๊ธฐ ์์ผ๋ ๊ฐ์ ธ๊ฐ์์ค
๋ผ๊ณ ์๋ ค์ค ์ ์๋ link
ํ๊ทธ๋ฅผ ํ๋ ๋ฃ์ด์ฃผ๋ฉด ๋์ด๋ค.
// constants/meta.constant.js
export default {
// ...
link: [{
rel: 'manifest',
href: '/static/manifest.json',
}],
// ...
};
<link rel="manifest" href="/static/manifest.json" data-vue-meta="true">
ํ์๋ vue-meta
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์ด๋ ๊ฒ Object
ํ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ๋ ๋๋ง ๋ <head>
๋ด๋ถ์ ์์์ ๋ฃ์ด์ค๋ค. ๊ทธ ํ ๋ธ๋ผ์ฐ์ ๊ฐ manifest.json
์ ์ ๋๋ก ๊ฐ์ ธ๊ฐ๋์ง๋ ํฌ๋กฌ ๊ฐ๋ฐ์ ๋๊ตฌ์ Application > Manifest
์์ ํ์ธํ ์ ์๋ค.

์, ์ฌ๊ธฐ๊น์ง ํ๋๋ฐ ๊ฑฐ์ 10๋ถ
์ ๋ ๊ฑธ๋ ธ๋ ๊ฒ ๊ฐ๋ค. ์์ง๊น์ง๋ ํ์์ ๊ณํ๋๋ก ์ํํ ํ๋ฌ๊ฐ๊ณ ์์๋ค.
ํ์ง๋ง ๋ฌธ์ ๋ ์ด์ ๋ถํฐ ์๊ธฐ๊ธฐ ์์ํ๋คโฆ
์ 2 ๊ด๋ฌธ, Service Worker
๊ธฐ๋ถ ์ข๊ฒ ์ปคํผ ํ์ ๋๋ฆฌ๊ณ ์์ ์ด์ ์๋น์ค ์์ปค๋ฅผ ์ ์ฉํ๋ ์์ ์ ์์ํ๋ค. ์๋น์ค ์์ปค๋ ๋ธ๋ผ์ฐ์ ์ ์ค์นํ๊ณ ๋๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋ ํ๋ก์ธ์ค๋ก, ์น ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฉ์ธ ๋ก์ง๊ณผ๋ ์ ํ ๋ณ๊ฐ๋ก ์๋ํ๋ค.
์๋น์ค ์์ปค์ ์น์ฑ์ ๋ฉ์ธ ๋ก์ง์ ์๋ก ๋ฉ์ธ์ง
๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ผ๋ก ์๋ํ๋ค. postMessage
๋ก ๋ณด๋ด๋ฉด onMessage
๋ก ๋ฐ๋ ๋ฐฉ์์ธ๋ฐ, WebView
๋ Chrome Extension
๊ณผ ๊ฐ์ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ ์ฐ๋ฆฌ์๊ฒ ์ต์ํ ๋ฐฉ์์ด๋ค.
๋จ, ์์ง๊น์ง ๋ชจ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ navigator
์ ์ญ ๊ฐ์ฒด ๋ด๋ถ์ serviceWorker
๊ฐ์ฒด๊ฐ ์กด์ฌํ๋ ์ง ํ์ธํ ํ ์ด๊ธฐํ๋ฅผ ์งํํด์ผํ๋ค.
if ('serviceWorker' in navigator) { /* ... */ }
๋ญ ์ด ์ ๋ ์ฏค์ด์ผ ํ์๊ฐ์ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ค์๊ฒ๋ ๊ต์ฅํ ์ต์ํ ์ํฉ์ด๊ธฐ ๋๋ฌธ์ ๊ทธ๋ฅ ์๊ณ ๋๊ธธ ์ ์์ง๋ง ์ด๊ฒ๋ณด๋ค ๋ ๊ท์ฐฎ์ ๊ฒ ์์๋ค. ๋ฐ๋ก ์๋น์ค ์์ปค์ ๊ฐ๋ฐํ๊ฒฝ ์ธํ ์ด๋ค.
๊ฐ๋ฐํ๊ฒฝ ์ธํ
์๋น์ค ์์ปค๋ ์ผ๋ฐ์ ์ธ ์น ์ดํ๋ฆฌ์ผ์ด์ ๋ณด๋ค ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ทผํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณด์์ด ๊ต์ฅํ ์ค์ํ๋ค. ๊ทธ๋์ ์๋น์ค ์์ปค๋ ์ ํ์ ์ธ ํ๊ฒฝ์์๋ง ์๋ํ ์ ์๋๋ก ๋ง๋ค์ด์ก๋๋ฐ ๊ทธ ์กฐ๊ฑด์ ๋ฑ ๋๊ฐ์ง์ด๋ค.
- ์น ์ดํ๋ฆฌ์ผ์ด์
์ ํธ์คํธ๊ฐ
localhost
์ด๊ฑฐ๋ HTTPS
ํ๋กํ ์ฝ์ ์ฌ์ฉํ๊ณ ์์ ๊ฒ
๋ถํํ๋ ํ์๋ ์ด ๋๊ฐ์ง ๋ชจ๋ ๋ค ํด๋น์ด ์๋๋ค. ํ์์ ํ์ฌ๋ ๋ก์ปฌ์์ http://local.soomgo.com
์ด๋ผ๋ ์ค๋ฆฌ์ง์ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์๋น์ค ์์ปค ๊ฐ์ฒด๊ฐ ํ์ฑํ ๋์ง ์๋๋ค.
๊ทธ๋ผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ 2๊ฐ์ง๋ก ์ค์ด๋ ๋ค. ๋ด๊ฐ localhost
๋ก ์ ์ํ๊ฑฐ๋ ๋ก์ปฌ์ HTTPS
๋ฅผ ๋ถํ๊ฑฐ๋.
์ฌ์ค ํ์๋ ์ด๊ฑธ ๋์ณ์ โ์ ์๋น์ค ์์ปค ์ด๋๊ฐ์ด? ์ ์๋ผ? ์ฃฝ์๋?โ๋ก ์ปดํจํฐ๋ ์ค๋์ดํ๋๋ผ ํ 30๋ถ ๋ ๋ ค๋จน์๋ค. ๊ณต์ ๋ฌธ์์ ๋ฒ์ ์ด ํ๊ตญ์ด๋ก ์ ํ์๋ ๋ด์ฉ์ด๋ฏ๋ก ํญ์ ๊ณต์ ๋ฌธ์๋ฅผ ๊ผผ๊ผผํ ์ฝ์โฆ
๊ฐ๋ฐ ์๋ฒ๋ฅผ HTTPS๋ก ๋์ฐ์!
๊ทผ๋ฐ ๋ ํ์๊ฐ localhost
๋ก ๋ง์ถ๊ธฐ์๋ ์ ์ง ์ปดํจํฐ ๋ฐ์์๊ฒ ์ง๋ ๊ธฐ๋ถ์ด๋ผ ๊ทธ๋ฅ ๊ฐ๋ฐ ํ๊ฒฝ์ HTTPS
๋ฅผ ๋ถํ๊ธฐ๋ก ํ๋ค.
Express
๋ ๋ด์ฅ ๊ฐ์ฒด๋ก https
๊ฐ์ฒด๋ฅผ ์ง์ํด์ฃผ๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๋จํ๊ฒ ์
์
ํ ์ ์๋ค. ๋จผ์ HTTPS
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ SSH ํค์์ด ํ์ํ๋ฏ๋ก openssl
์ ์ฌ์ฉํ์ฌ ํค๋ฅผ ๋ง๋ค์ด ์ฃผ์๋ค.
$ openssl genrsa 1024 > private.pem # ๋น๊ณต๊ฐํค ์์ฑ
...
$ openssl req -x509 -new -key private.pem > public.pem # ๊ณต๊ฐํค ์์ฑ
๊ทธ ๋ค์ ์ด ํค๋ฅผ ํ๋ก์ ํธ์ ์ ๋นํ ๊ณณ์ ์ ์ฅํ ๋ค์ ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๋๋๋ฐ ์ฌ๊ธฐ์ ์ฃผ์ ์ฌํญ.
> ์์ฑํ sshํค๋ ๋ก์ปฌ ๊ฐ๋ฐํ๊ฒฝ์์๋ง ์ฌ์ฉ๋์ด์ผํฉ๋๋ค.
> ์ด ๋๋ ํ ๋ฆฌ ๋ด์ *.pem ํ์ผ์ gitignore์ ์ถ๊ฐ๋์ด์์ต๋๋ค./keys/README.md
์ ๋ ๋ฆฌ๋ชจํธ ์ ์ฅ์์ SSH ํค๋ฅผ ์ ๋ก๋ํ์ง ๋ง์. ๋ณด์ ํฐ์ง๋ค. ๋ฌผ๋ก ์ด ํค๋ก ๋ญ๊ฐ ์ค์ง์ ์ธ ์ธ์ฆ์ ํ๋ ๊ฒ์ด ์๋๋ผ ๋ก์ปฌ ๊ฐ๋ฐ ์๋ฒ์์๋ง ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋ญ๊ฐ ์ค์ํ ๊ฑธ ํธ๋ฆด ์ผ๋ ค๋ ์์ง๋ง ๊ทธ๋๋ ์ต๊ด์ด ์ค์ํ๋ค. SSH ํค๋ ํญํ ๋ค๋ฃจ๋ฏ์ด ๋ค๋ฃจ์.
๋์ ํ์๋ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค์ด ์ฝ๊ฒ HTTPS
ํ๊ฒฝ์ ์ธํ
ํ ์ ์๋๋ก ํ๋ก์ ํธ ๋ด์ ํค๋ฅผ ์ ์ฅํ๋ ๋๋ ํ ๋ฆฌ ๋ด๋ถ์ README.md
๋ฅผ ์์ฑํด๋๊ณ ์ด ํค๊ฐ ์ด๋ค ์ฉ๋๋ก ์ฌ์ฉ๋ ๊ฒ์ธ์ง ์ฃผ์ ์ฌํญ์ ๋ฌด์์ธ์ง ์์ธํ๊ฒ ์ ์ด๋์๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ๋ฐํ ๋๋ HTTPS
๊ฐ ์๋๋ผ HTTP
๋ก ์๋ฒ๋ฅผ ๋์ธ ์ผ๋ ์๊ธธ ์ ์์ผ๋ฏ๋ก ๊ฐ๋ฐ ์๋ฒ๋ฅผ ๋์ธ ๋ ์ต์
์ ์ฌ์ฉํ์ฌ ํ๋กํ ์ฝ์ ์ ํํ ์ ์๋๋ก ํด์ฃผ์๋ค.
// package.json
{
"scripts": {
"serve": "cross-env NODE_ENV=local node server",
"serve:https": "cross-env NODE_ENV=local node server --https",
}
}
// server.js
const express = require('express');
const https = require('https');
// node์ ์ต์
๋ค์ ๋ฐฐ์ด ํํ๋ก process.argv์ ๋ด๊ฒจ์๋ค.
const useHttps = process.argv.some(val => val === '--https');
const app = express();
// ...
if (isLocal) {
let localApp = app;
if (useHttps) {
localApp = https.createServer({
// ์๊น ์์ฑํ ํค๋ ์ฌ๊ธฐ์ ์ฌ์ฉํ๋ค!
key: fs.readFileSync('./keys/private.pem'),
cert: fs.readFileSync('./keys/public.pem'),
}, app);
}
localApp.listen(port, host, () => {
debug(`${isHttps ? 'https://' : 'http://'}${host}:${port}๋ก ๊ฐ์ฆ์!!!!`);
});
}
// ...
์ด๋ฐ ์์ผ๋ก ์์ฑํด๋์ผ๋ฉด ๊ฐ๋ฐ ์๋ฒ๋ฅผ ์ฌ๋ฆด ๋ ์ํ๋ ํ๋กํ ์ฝ์ ์์ ์์ฌ๋ก ๋ณ๊ฒฝํ ์ ์๊ธฐ ๋๋ฌธ์, ํน์ ๋ด๊ฐ ๊ฐ๋ฐ ์๋ฒ๋ฅผ HTTPS
๋ก ๋ฐ๊ฟ๋์ ๋ค๋ฅธ ๊ฐ๋ฐ์ ์ปดํจํฐ์์ ๊ฐ๋ฐ ์๋ฒ๊ฐ ์ ์ฌ๋ผ๊ฐ๋ ์ผ๋จ ์๊ฐ์ ๋ฒ์ด๋๊ณ ๋ฒ๊ทธ๋ฅผ ์์ ํ ์ ์๋ค.
์๋ ์ง์ง ๊ณ ์๋ ๋ง๊ธฐ ์ ์ ๋ง์ ๊ณณ์ ์์ํด์ ๋ฏธ๋ฆฌ ํ์ ์ฃผ๊ณ ์๋ ๋ฒ์ด๋ค.(๋ง์ด ๋ง์๋ณธ 1์ธ)
๊ฐ๋ฐ ์๋ฒ๋ฅผ HTTPS
๋ก ๋์ฐ๋ฉด ์ด์ navigator
๊ฐ์ฒด ๋ด๋ถ์ ์ด์๊ฒ ๋ค์ด๊ฐ์๋ serviceWorker
๊ฐ์ฒด๋ฅผ ํ์ธํ ์ ์๋ค.
> navigator.serviceWorker
< ServiceWorkerContainer {ready: Promise, controller: null, oncontrollerchange: null, onmessage: null}
HTTPS๋ฅผ ์ฐ์ง ์๊ณ ๋ฌธ์ ํํผํ๊ธฐ
์ฌ์ค ํ์๋ ์ฒ์์๋ ๋ก์ปฌ์ HTTPS
๋ถํ๊ธฐ๊ฐ ๊ท์ฐฎ์์ ๋ฆฌ์์นํ๋ค๊ฐ ์ฐพ์ ์์์ด์ธ๋ฐ, ๋ก์ปฌ ์๋ฒ์์๋ HTTPS
๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ ์๋น์ค ์์ปค๋ฅผ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค.
chrome://flags/#unsafely-treat-insecure-origin-as-secure์ ๋ค์ด๊ฐ์ ํด๋น ๊ธฐ๋ฅ์ ํ์ฑํํ๊ณ ํ ์คํธ ํ๋์ ์ํ๋ ํธ์คํธ๋ฅผ ์ ๋ ฅํด๋์ผ๋ฉด ํด๋น ํธ์คํธ๋ ์์ ํ๋ค๊ณ ํ๋จํ์ฌ ์๋น์ค ์์ปค๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค๋ค.

์ด๋ ๊ฒ ๋ฑ๋กํ ๋ค ํ๋จ์ Relaunch Now
๋ฒํผ์ ํด๋ฆญํ๋ฉด ํฌ๋กฌ์ด ์ฌ์์๋๋ฉด์ ์ค์ ์ด ๋ณ๊ฒฝ๋๋ค. ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ์ค์ค๋ก ๋ณด์ ์ทจ์ฝ์ ์ ๋ง๋ค์ด ๋ด๋ ๊ฒ์ด๊ธฐ ์ฐ์ฐํด์ ๊ฒฐ๊ตญ ์ฌ์ฉํ์ง๋ ์์๋ค.
Service Worker ์์ฑ
์ด์ ๊ฐ๋ฐํ๊ฒฝ ์ธํ ์ด ๋ค ๋๋ฌ๋ค๋ฉด ์ด์ ๋ถํฐ๋ ๋ฑํ ๊ท์ฐฎ์ ๊ฑด ์๋ค. ๊ทธ๋ฅ ๋ฌธ์๋ณด๋ฉด์ ์ญ์ญ ์์ฑํ๋ฉด ๋๋ค. ์ผ๋จ ์๋น์ค ์์ปค์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ ์ ์ ์๋ํ๋ ์ง ๋ถํฐ ํ์ธํด์ผ ํ๋ฏ๋ก ์๋ฌด ๋ด์ฉ์ด ์๋ ์๋น์ค ์์ปค๋ถํฐ ๋ง๋ค์๋ค.
๊ทผ๋ฐ ์ด๊ฒ๋ manifest.json
์ฒ๋ผ ๊ทธ๋ฅ ์น ์ดํ๋ฆฌ์ผ์ด์
๊ณผ ์์ ๋ถ๋ฆฌ๋ static/service-worker.js
๋ก ๋ฐ๋ก ์์ฑํ๋ฉด ๋ง๋ค ๋๋ ํธํ๊ธด ํ๋ฐ, ๋์ค์ ์๋น์ค ์์ปค์์ ์น ์ดํ๋ฆฌ์ผ์ด์
์์ ์ฌ์ฉํ๊ณ ์๋ ๋ชจ๋์ด๋ ๋ฐ์ดํฐ์ ์ ๊ทผํ๊ณ ์ถ์ ๋ ๊ต์ฅํ ์ ๋งคํด์ง ๊ฒ ๊ฐ์์ ๊ทธ๋ฅ Webpack
์ ์ฌ์ฉํด์ ๊ฐ์ด ๋น๋ํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
์ผ๋จ service-worker.js ํ์ผ์ ๋ง๋ค์!
Webpack
์ผ๋ก ๋น๋ํ๋ค๊ณ ํด๋ ์ด ์น๊ตฌ๊ฐ ๋ฌด์์ ์ ๋ฅผ ์ฐฝ์กฐํ๋ ์น๊ตฌ๋ ์๋๊ธฐ ๋๋ฌธ์ ๋น์ฐํ ์์ค ํ์ผ์ ์์ด์ผ ํ๋ค. ์ผ๋จ ์๋น์ค ์์ปค๊ฐ ์๋ํ๋ ๊ฒ์ ๋ณด๋ ๊ฒ์ด ๋ชฉ์ ์ด๋ฏ๋ก ์ฌํํ๊ฒ ์์ฑํด์ฃผ์๋ค.
// src/service-worker.js
self.addEventListener('message', event => {
console.log('์ ์ชฝ ํ
์ด๋ธ์์ ๋ณด๋ด์ ๊ฒ๋๋ค -> ', event);
});
Service Worker Webpack Plugin ์ฌ์ฉํ๊ธฐ
ServiceWorkerWebpackPlugin
์ ๊ตฌ๊ธ์ Service Worker Webpack
์ ๊ฒ์ํ๋ฉด ๊ฐ์ฅ ์๋จ์ ๋์ค๋ ํ๋ฌ๊ทธ์ธ์ด๋ค. ๊นํ๋ธ ๋ ํ์งํ ๋ฆฌ์ ๊ฐ์ ๋ค๋ค ๊ตฌ๊ฒฝ ํ๋ฒ ํด๋ณด์.
README
๋ฅผ ์ฝ์ด๋ณด๋ ์ฌ์ฉ๋ฒ์ด ์ด๊ฐ๋จ ๊ทธ ์์ฒด๋ค. ๊ทธ๋ฅ Webpack
์ค์ ์ ๋ฃ์ด์ฃผ๋ฉด ๋์ด๋ค.
// build/webpack.client.config.js
module.exports = merge(baseConfig, {
plugins: [
// ...
new ServiceWorkerWebpackPlugin({
entry: path.resolve(__dirname, '../src/service-worker.js'),
}),
// ...
]
})
์๋น์ค ์์ปค ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ฐ์ด์ฃผ๊ณ ServiceWorkerWebpackPlugin
๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ์ด๊ธฐ ์ธ์๋ก ๋๊ฒจ์ฃผ๋ฉด Webpack
์ค์ ์ output
์ ์ ์๋ ๊ฒฝ๋ก์ sw.js
ํ์ผ์ ์์ฑํด์ค๋ค. ๋ฌผ๋ก ์ด ํ์ผ ์ด๋ฆ์ ๋ณ๊ฒฝํ ์ ์์ผ๋ ๋ชจ๋ ๊ฐ์์ ์ทจํฅ๋๋ก ์ ์ ์ ๋ฌ๋ฟ ๋ด์ ์ด์ ์ด๋ฆ์ ์ง์ด์ฃผ์. ํ์๋ ๊ท์ฐฎ์ผ๋๊น ๊ทธ๋ฅ sw
๋ก ๊ฐ๊ธฐ๋ก ํ๋ค.

๊ทผ๋ฐ ์ด ๋ฐฉ๋ฒ์ ๋จ์ ์ด ์๋๋ฐ, ์ผ๋ฐ์ ์ธ ์๋น์ค ์์ปค๋ณด๋ค ํ์ผ์ ํฌ๊ธฐ๊ฐ ์ปค์ง๋ค๋ ๊ฒ์ด๋ค. ์๋ฌด๋๋ Webpack
์ผ๋ก ๋น๋ํ๋ค๋ณด๋ ์๋น์ค ์์ปค์ ์ธ๋ถ์ ์๋ ๋ชจ๋์ด๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์ด์ค๊ธฐ ๋๋ฌธ์ด๋ค.
๋ญ ์ด๊ฑด ์ ์ด์ ํ์๊ฐ ์กฐ๊ธ ํธํ๊ฒ ์ฐ๋ ค๊ณ ์ ํํ ์ํฉ์ด๋ ์ด์ฉ ์ ์๊ธดํ๋ค. ์ด์จ๋ ์ด๋ฐ ๋จ์ ์ด ์์ผ๋, ์๋น์ค ์์ปค์ ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ์ ํ์๋ ๋
์๋ถ๋ค์ ์ง์ ์์ฑํ์๋ ๊ฒ ๋ ์ข์ ์๋ ์๋ค.
์ด์ ์๋น์ค ์์ปค์ ๋ณธ์ฒด๋ฅผ ๋ง๋ค์์ผ๋ ์ด๊ฑธ ์น ์ดํ๋ฆฌ์ผ์ด์
์ด ์ด๊ธฐํ๋ ๋ ๋ธ๋ผ์ฐ์ ์ ๋ ์๋น์ค ์์ปค ๊ฐ์ง๊ณ ์์ด!
๋ผ๊ณ ์๋ ค์ฃผ๋ ์ผ๋ง ๋จ์๋ค.
Service Worker ์ค์น
์๋น์ค ์์ปค๋ฅผ ์ค์นํ๋ ๋ฐฉ๋ฒ์ ๊ฐ๋จํ๋ค. ์ด ๋ธ๋ผ์ฐ์ ์ serviceWorker
๊ฐ์ฒด๊ฐ ์ง์๋๋ ์ง ํ์ธํ ํ ์ค์นํ๋ฉด ๋๋ค.
์ผ๋ฐ์ ์ธ ์๋น์ค ์์ปค์ ์ค์น ๋ฐฉ๋ฒ์ ๊ตฌ๊ธ์ ์๋น์ค ์์ปค:์๊ฐ ๋ฌธ์๋ฅผ ๋ณด๊ณ ๋ฐ๋ผํ๋ฉด ๋๋ค. ํ์๋ ServiceWorkerWebpackPlugin
์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํด๋น ํ๋ฌ๊ทธ์ธ์ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ฌ ์์ฑํ๋ค.
์ด์ฐจํผ ์ด๋ค ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ์ค์น ์์ฒด๋ ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์๋ค.
// settings/service-worker.setting.js
export default () => {
const isSupported = process.browser && 'serviceWorker' in navigator;
if (!isSupported) {
return;
}
console.log('์๋น์ค ์์ปค๊ฐ ์ง์๋๋ ๋ธ๋ผ์ฐ์ ์
๋๋ค.');
const runtime = require('serviceworker-webpack-plugin/lib/runtime');
runtime.register().then(res => {
console.log('์๋น์ค ์์ปค ์ค์น ์ฑ๊ณต ->', res);
}).catch(e => {
console.log('์๋น์ค ์์ปค ์ค์น ์คํจ ใ
ใ
-> ', e);
});
}
isSupported
๋ณ์์ process.browser
๊ฐ์ ๊ฒ์ฌํ๋ ์ด์ ๋, ์จ๊ณ ์ ์ดํ๋ฆฌ์ผ์ด์
์ Next.js
๋ Nuxt.js
์ฒ๋ผ ์ ๋๋ฒ์
SSR ํ๊ฒฝ์์ ์คํ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ ๋๋ฒ์
SSR์ด ๋ญ์ง ๊ถ๊ธํ์ ๋ถ์ ์ด์ ์ ์์ฑํ ํฌ์คํ
์ธ Universal Server Side Rendering์ด๋?์ ํ๋ฒ ์ฝ์ด๋ณด์.
ํด๋น ๋ชจ๋์ ๋ฌผ๋ก ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์๋ง ํธ์ถ๋์ง๋ง ๊ทธ๋๋ ํน์ ๋ชจ๋ฅด๋ ์ ๋งํ๋ฉด ํ์ฌ ์คํ ์ปจํ
์คํธ๊ฐ ํด๋ผ์ด์ธํธ์ธ์ง ์๋ฒ์ธ์ง๋ ์ฒดํฌํด์ฃผ๋ ๊ฒ์ด ์ข๋ค.
์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง ์ฌ์ดํด ๋ ๋ธ๋ผ์ฐ์ API์ธ navigator
์ ์ ๊ทผํ๋ ค๊ณ ํ๋ฉด ๋น์ฐํ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์๋น์ค ์์ปค์ ์ค์น๊ฐ ์ฑ๊ณตํ๋ค๋ฉด chrome://inspect/#service-workers ๋๋ chrome://serviceworker-internals์ ์ฌ๋ฌ๋ถ์ ์๋น์ค ์์ปค๊ฐ ๋ ธ์ถ๋ ๊ฒ์ด๋ค. ์๋น์ค ์์ปค๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ค ๊ต์ฅํ ์ฌํํ ํธ์ด๋ค.
๊ทผ๋ฐ ์ฌ์ค ํ์๊ฐ ๊ฐ์ฅ ์๊ฐ์ ๋ง์ด ์ก์๋จน์ ๋ถ๋ถ์ด ๋ฐ๋ก ์ด ์๋น์ค ์์ปค์๋๋ฐ, ๊ทธ ์ด์ ๋ ์๋น์ค ์์ปค ๊ณต์ ๋ฌธ์์ ์ ํ์๋ค.
์ค์น ์คํจ ์๋ฆผ ๊ธฐ๋ฅ ๋ถ์กฑ
์๋น์ค ์์ปค๊ฐ ๋ฑ๋ก๋๋๋ผ๋ chrome://inspect/#service-workers ๋๋ chrome://serviceworker-internals์ ํ์๋์ง ์๋ ๊ฒฝ์ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฑฐ๋
event.waitUntil()
์ ๊ฑฐ๋ถ๋ ํ๋ผ๋ฏธ์ค๋ฅผ ์ ๋ฌํ๊ธฐ ๋๋ฌธ์ ์ค์นํ์ง ๋ชปํ์ ์ ์์ต๋๋ค.์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด chrome://serviceworker-internals๋ก ์ด๋ํ์ฌ โOpen DevTools window and pause JavaScript execution on service worker startup for debuggingโ์ ์ ํํ๊ณ ์ค์น ์ด๋ฒคํธ์ ์์ ์์น์ ๋๋ฒ๊ฑฐ ๋ฌธ์ ์ถ๊ฐํฉ๋๋ค. ์ด ์ต์ ์ ํ์ธํ ์ ์๋ ์์ธ ์ ์ผ์ ์ค์ง์ ํจ๊ป ์ฌ์ฉํ๋ฉด ๋ฌธ์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.
Matt Gaunt contributor to WebFundamentals
์๋ ์ด๊ฒ ๋๋ฒ๊น
์ด ์ง์ง ํ๋ค๋ค. ์๋น์ค ์์ปค์ ์ค์น๊ฐ ์คํจํ์ผ๋ฉด ์ด๋์ ์๋ฌ๊ฐ ๋ฌ๋์ง, ์ ๋ฌ๋์ง ๋ณด์ฌ์ค์ผ ํ๋๋ฐ Uncaught DomException
ํ๋๋ง ๋ธ๋ ๋ณด์ฌ์ฃผ๊ณ ๋๋ธ๋ค. ๊ทธ๋์ ๋ญ๊ฐ ์๋ชป๋์๋์ง ํ๋ํ๋ ์ฝ๋ ๋ผ์ธ์ debugger
์ฐ๊ณ ์ถ๋ฆฌํด๊ฐ๋ฉด์ ์์ ํด์ผํ๋๋ฐ ์ด๊ฒ ์ง์ง ํ๋ค๋ค. ๊ทผ๋ฐ ์ด๊ฑธ ๋ ๊ณต์ ๋ฌธ์์ ๋๋ฒ๊น
ํ๋ค๋ค๊ณ ๋ฒ์ ์ด ์ ์ด๋์ ๊ดํ ๋ ํ๋ ๊ฑฐ ๊ฐ๋ค.

์ 3 ๊ด๋ฌธ, PushManager
์ด์ ์๋น์ค ์์ปค๋ ์ค์นํ์ผ๋ ์๋น์ค ์์ปค์ PushManager
๋ง ์ฐ๋ํด์ฃผ๋ฉด ๋ชจ๋ ๊ฒ์ด ๋๋๋ค! ๋ผ๊ณ ์๊ฐํ์ง๋ง ์ด ๋๋ ๋ณต๋ณ์ด์๋ค. PushManager
๋ํ ์์ง ํ์ค์ด ์๋๋ผ์ ๋ชจ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋๋ ๊ธฐ๋ฅ์ด ์๋๊ธฐ ๋๋ฌธ์ ์๋น์ค ์์ปค ์ค์น ์ PushManager
์ ์กด์ฌ ์ฌ๋ถ๋ ํจ๊ป ๊ฒ์ฌํด์ฃผ์ด์ผํ๋ค.
// settings/service-worker.setting.js
const isSupported = process.browser && 'serviceWorker' in navigator && 'PushManager' in window;
// ...
์กฐ๊ฑด์ ๊ฐ๋ ์ฑ์ด ์กฐ๊ธ ๋จ์ด์ง ๊ฒ์ด ๋ง์์ ์๋ค์ง๋ง ์ผ๋จ์ ๊ตฌ๋ ํ ์คํธ๋ฅผ ํ๋ ๊ฒ์ด๋ฏ๋ก ๊ทธ๋ฅ ๋์ด๊ฐ๋ค. ๋ฑ ์ฌ๊ธฐ๊น์ง ํ๊ณ ๋์ ๋ค์ ์คํ ์ ๋ดค๋๋โฆ
์ฌ์์ ์์
์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ ํค ๊ฐ์ ธ์ค๊ธฐ
์ด ์ฝ๋๋ฉ์ผ๋ก ์์ ํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ ํค๋ฅผ ๋ช ๊ฐ ์์ฑํ ํ์๊ฐ ์๋๋ฐ, ๋์ฐ๋ฏธ ์ฌ์ดํธ์ธ https://web-push-codelab.glitch.me/์์ ์์ฑํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ๊ณต๊ฐ ํค ์๊ณผ ๋น๊ณต๊ฐ ํค ์์ ์์ฑํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ดscripts/main.js
๋ก ๊ณต๊ฐ ํค๋ฅผ ๋ณต์ฌํ์ฌ<Your Public Key>
๊ฐ์ ๋ฐ๊พธ์ธ์.
const applicationServerPublicKey = '<Your Public Key>';
์ฐธ๊ณ : ์ ๋๋ก ๋น๊ณต๊ฐ ํค๋ฅผ ์น ์ฑ์ ๋๋ฉด ์ ๋ฉ๋๋ค!
Matt Gaunt ์น ์ฑ์ ํธ์ ์๋ฆผ ์ถ๊ฐ
์โฆ? SSH ํค๊ฐ ํ์ํ๋ค๊ณ โฆ? ์์ ๋ชปํ๊ธด ํ์ง๋ง HTTPS
ํ๊ฒฝ์์ ์๋ฒ์ ํด๋ผ์ด์ธํธ ํต์ ์ฑ๋์ด ํ๋ ๋ ์๊ธฐ๋ ๊ฒ์ด๋ฏ๋ก SSH ํค๊ฐ ํ์ํ ๊ฑด ๋ง๋ ๊ฒ ๊ฐ์์ ๋น ๋ฅด๊ฒ ์ธ์ ํ๋ค.
Google์ ๊ณต์ ๋ฌธ์์์ PushManager๋ก ํธ์ ์ฑ๋์ ๊ตฌ๋ ํ๋ ์์ ๋ฅผ ์ดํด๋ดค๋๋ ํ์คํ SSH ํค๊ฐ ํ์ํ๊ธด ํ๋ค.
function subscribeUser() {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
// ...
}
์ ์์์ ๋ชปํ์ง๋ง ์ด ์ ๋๋ ๊ด์ฐฎ๋ค. ์ด์ฐจํผ Pusher
๋ผ๋ ํธ์ ์๋ฃจ์
์ ์ด๋ฏธ ์ฌ์ฉํ๊ณ ์๊ณ ์ด๊ฒ๋ ๊ฒฐ๊ตญ ๊ฐ์ ์๋ฆฌ๋ก ์๋ํ๊ธฐ ๋๋ฌธ์ ๋ด๋ถ์ ์ผ๋ก๋ SSH ํค์์ ์ฌ์ฉํ ์ธ์ฆ์ ์ฌ์ฉํ์ ๊ฒ์ด๋ค.
๋น ๋ฅธ ์์
๊ทธ๋ ๊ฒ Pusher
์์ ์ฌ์ฉ๋๋ SSH ํค๊ฐ ์ด๋ ์๋์ง ์ฐพ๊ธฐ๋ฅผ ์ด์ธ 20๋ถโฆ ๊ฒฐ๊ตญ ๋ชป ์ฐพ์๋ค.
Pusher
๋ด๋ถ์ ์ผ๋ก ์๋ฒ์์ ์ฌ์ฉํ๋ secret
๊ฐ๊ณผ ํด๋ผ์ด์ธํธ์์ ์ฌ์ฉํ๋ key
๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆ์ ํ๋๋ฐ, ์ด๊ฑด SSH ํค์์ด ์๋๋ผ ๊ทธ๋ฅ ์์์ ๋ฌธ์์ด์ด์๋ค. ์ด์ฐจํผ SSH ํค๋ฅผ ๋ง๋ ๋ค๊ณ ํด๋ ์น ํธ์์ ๋ํ ์ปจํธ๋กค์ ๋ฐฑ์๋๊ฐ ๊ฐ์ง๊ณ ์์ผ๋ฏ๋ก ํ์ ํผ์ ์ด๊ฒ์ ๊ฒ ๊ฑด๋๋ฆฌ๋ฉด์ ํ
์คํธ ํด๋ณด๊ธฐ์๋ ์กฐ๊ธ ๋ฌด๋ฆฌ๊ฐ ์๋ค. ๊ฒ๋ค๊ฐ ํด๊ทผ ์๊ฐ์ ์ด๋ฏธ ํ์ฐธ ์ง๋ฌ๊ธฐ ๋๋ฌธ์ ๋ฐฑ์๋ ๋ถ๋ค์ ํด๊ทผํ์
จ๋ค.(์ฌ์ค ๊ธ์์ผ ์ ๋
์ ์ด๋ฐ ๊ฑฐ ํ๊ณ ์๋ ์ฌ๋์ด ์ด์ํ๊ฑฐ๋ค.)
์ด๋ ์ด๋ฏธ ํ์์ ๋ฉํ์ ์กฐ๊ธ ๋๊ฐ์์๊ธฐ ๋๋ฌธ์ ์์์ผ์ ์ถ๊ทผํด์ ๋ชจ๋ฐ์ผ ์ฑ์์๋ ์ด๋ป๊ฒ Pusher
์ FCM
์ ์ฐ๋ํด์ ์ฌ์ฉํ๊ณ ์๋ ์ง ๋ฌผ์ด๋ณธ ํ ์งํํด์ผ๊ฒ ๋ค๊ณ ๊ฒฐ๋ก ์ ๋ด๋ฆฌ๊ณ ๋ฐฉํฅ์ ๋ฐ๊ฟจ๋ค.
์ปค๋ฐ ๋ก๊ทธ๋ฅผ ๋ณด๋ ์ด๋ ์๊ฐ์ด ๋๋ต 21์ 40๋ถ ์ฏคโฆ ์๋ ๊ทธ๋๋ ํด๊ทผ์ ํด์ผํ๋๊นโฆ ์ง์๋ ๊ฐ์ผ์งโฆ

์ 4 ๊ด๋ฌธ, Notification API
๊ทธ๋์ ๋ฐ๊พผ ๋ฐฉํฅ์ โBackground ํธ์ ๋ฉ์ธ์ง๋ ํฌ๊ธฐํ๊ณ Foreground ํธ์ ๋ฉ์ธ์ง๋ผ๋ ์ ๋๋ก ๋ฐ๊ฒ ํ์โ์๋ค.
์ด๋ ๊ฒ ๋๋ฉด ํ์๊ฐ ์ฒ์ ์๊ฐํ๋ โ๋ธ๋ผ์ฐ์ ๊ฐ ๊บผ์ ธ์๋๋ผ๋ ํธ์ ๋ฉ์ธ์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์ถ๋คโ๋ ๋ฌ์ฑํ์ง ๋ชปํ์ง๋ง ๋ธ๋ผ์ฐ์ ๊ฐ ์ผ์ ธ์๊ณ soomgo.com
์ ์ ์๋์ด ์๋ค๋ฉด ์ฐฝ์ ๋ด๋ ค๋๋ ๋ค๋ฅธ ์ผ์ ํ๊ณ ์๋ ์ฌ์ฉ์์๊ฒ ํธ์ ๋ฉ์ธ์ง๋ ๋ณด์ฌ์ค ์ ์์ผ๋ฏ๋ก ์ด๋ ์ ๋ ๋ชฉ์ ๋ฌ์ฑ์ ๋๋ค.
๋ฐฉํฅ์ ๋ฐ๊พธ๊ณ ๋๋๊น ๊ธฐ์กด์ ์น ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํ๋์ด์๋ ํธ์ ๋ก์ง์ ๋ ธํฐํผ์ผ์ด์ ์ ๋ณด์ฌ์ฃผ๋ ์ฝ๋๋ง ์ถ๊ฐํ๋ฉด ๋๋๋ ๊ฐ๋จํ ์ผ์ด ๋์๋ค. ์ด์ฐจํผ Pusher ์๋ฃจ์ ์ ์ฌ์ฉํ์ฌ ์ธ์ฆ, ์ด๋ฒคํธ ๊ตฌ๋ ๋ฑ์ ๋ก์ง์ ์์ ์ ์ฑํ ๊ธฐ๋ฅ ๊ฐ๋ฐํ ๋ ๋ค ๋ง๋ค์ด ๋จ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ธฐ์กด ๊ธฐ๋ฅ์ Notification ๋ผ์๋ฃ๊ธฐ
์์ ์ ์ฑํ
๊ธฐ๋ฅ์ ๊ฐ๋ฐํ ๋ Pusher SDK
๋ฅผ ํ๋ฒ ๋ํํ ํฌํผ ํด๋์ค๋ ๋ง๋ค์ด ๋จ์๊ธฐ ๋๋ฌธ์ ๋๋ฆ ๊ตฌ์กฐ๋ ํํํ๋ค. ์ด์ ์ฌ๊ธฐ์ ๋ฉ์๋๋ง ๋ช๊ฐ ์ถ๊ฐํ๊ณ ์น ํธ์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ ์๋ฆผ๋ง ๋ณด์ฌ์ฃผ๋ฉด ๋๋ค.
์ฐ์ ์ด ๋ธ๋ผ์ฐ์ ๊ฐ Notification API
๋ฅผ ์ง์ํ๋ ์ง ํ์ธํ๋ ๋ฉ์๋๊ฐ ํ์ํ๋ค.
// src/helpers/Pusher.js
isSupportNotification () {
return process.browser && window && 'Notification' in window;
}
๊ทธ ๋ค์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ
์ ๋ํ ํ๊ฐ๋ฅผ ๋ฐ๋ ๋ฉ์๋๋ฅผ ์์ฑํ๋ค. Notification
์ ๋ด๋ถ์ permission
์์ฑ์ ๊ฐ์ง๊ณ ์๊ณ ์ด ์์ฑ์ granted
, denied
, default
๋ก ๋๋์ด ์ง๋ค.
// src/helpers/Pusher.js
getNotificationPermission () {
if (!this.isSupportNotification()) {
this.isAllowNotification = false;
return Promise.reject(new Error('not_supported'));
}
if (Notification.permission === 'granted') {
this.isAllowNotification = true;
return Promise.resolve();
}
else if (Notification.permission !== 'denied' || Notification.permission === 'default') {
return Notification.requestPermission().then(result => {
if (result === 'granted') {
this.isAllowNotification = true;
}
});
}
}
granted
๋ ์ฌ์ฉ์๊ฐ ์ด๋ฏธ ์๋ฆผ์ ํ์ฉํ ์ํ, denied
๋ ๊ฑฐ๋ถํ ์ํ, default
๋ ์์ง ์๋ฆผ์ ๋ํ ํผ๋ฏธ์
์ ์ค์ง๋ง์ง ์ฌ์ฉ์๊ฐ ๊ฒฐ์ ์ ํ์ง ์์ ์ํ์ด๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ํผ๋ฏธ์
์ด default
์ํ์ผ ๋ Notification.requestPermission
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ์๋ฆผ ๋
ธ์ถ์ ๋ํ ํ๊ฐ๋ฅผ ๋ฐ์์ผ ํ๋ค.
์ด์ ์ค์ ๋ก ์๋ฆผ์ฐฝ์ ๋์์ค ๋ฉ์๋๋ฅผ ์์ฑํด๋ณด์. Notification API
์์ฒด๊ฐ ์๋ ์ฌํํ๋ค๋ณด๋ ๊ทธ๋ฅ ์ด๋ ต์ง ์๋ค.
createForegroundNotification (title, { body, icon, link }) {
const notification = new Notification(title, {
body,
icon: icon || `${AssetsCloudFrontHost}/app_icons/1x.png`,
});
notification.onshow = () => {
setTimeout(() => notification.close(), 5000);
};
notification.onerror = e => {
console.error(e);
};
notification.onclick = event => {
event.preventDefault();
if (link) {
window.open(link, '_blank');
}
};
}
new
ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ Notification
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด ๊ทธ ์ฆ์ OSX
๋ ํ๋ฉด ์ฐ์ธก ์๋จ์, Windows
๋ ์ฐ์ธก ํ๋จ์ ์๋ฆผ ๋ฉ์ธ์ง๊ฐ ๋
ธ์ถ๋๋ค. ๊ทธ ๋ค์ ์์ฑํ Notification
๊ฐ์ฒด์ onshow
, onclick
๋ฑ์ ์ด๋ฒคํธ ๋ฆฌ์ค๋์ ํธ๋ค๋ฌ๋ฅผ ๋ฑ๋กํด์ฃผ๋ฉด ๋๋ค.
๋ฉ์๋ ๋ช
์ Background
๋ฉ์ธ์ง์ ์คํํ์ง ๋ชปํ ํ์์ ์ฌํ์ ๋ด์ createForegroundNotification
์ผ๋ก ๊ฒฐ์ ํ๋ค. ๊ตณ์ด Foreground
๋ฅผ ๊ฐ์กฐํ ์ด์ ๋ ์ธ์ ๊ฐ createBackgroundNotification
๋ฉ์๋๋ฅผ ๋ง๋ค๊ฒ ๋ค๋ ํ์์ ์ผ๋ง์ ๋ด์๋ค.
์ด์ ํ์ํ ๋ชจ๋ ๊ฒ์ ๋ง๋ค์์ผ๋ Pusher SDK
์์ Web Socket์ ํตํด ํธ์๋ฅผ ๋ณด๋ผ ๋๋ง๋ค ์๋ฆผ์ด ์๋ํ๋๋ก ์ฐ๊ฒฐ๋ง ํด์ฃผ๋ฉด ๋๋ค.
async subscribeNotification () {
if (!this.isSupportNotification()) {
return;
}
await this.getNotificationPermission();
if (!this.isAllowNotification) {
return;
}
const channel = await this.getPrivateUserChannel();
channel.bind('message', response => {
if (response.sender.id === this.myUserId) {
return;
}
const targetChatRoute = !!response.sender.provider ? 'chats' : 'pro/chats';
this.createForegroundNotification(`${response.sender.name}๋์ด ๋ฉ์ธ์ง๋ฅผ ๋ณด๋์ด์.`, {
body: response.message,
icon: response.sender.profile_image,
link: `${location.origin}/${targetChatRoute}/${response.chat.id}`,
});
});
}
Pusher SDK
๋ ํธ์ ์ฑ๋์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๋ฐ์ธ๋ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ค๋ค. message
์ด๋ฒคํธ๋ ์ฌ์ฉ์๊ฐ ์ฑํ
๋ฉ์ธ์ง๋ฅผ ๋ฐ์์ ๋ ํธ์ถ๋๋ ์ด๋ฒคํธ์ด๋ค. ๋จ ์๊ธฐ ์์ ์ด ๋ณด๋ธ ๋ฉ์ธ์ง์๋ ์ฌ๊ณผ์์ด ์ด๋ฒคํธ๊ฐ ํธ์ถ๋๋ฏ๋ก response.sender.id === this.myUserId
์กฐ๊ฑด์ ํตํด ์์ ์ด ๋ณด๋ธ ๋ฉ์ธ์ง์๋ ์๋ฆผ์ ๋ณด์ฌ์ฃผ์ง ์๋๋ก ์ฒ๋ฆฌํ์๋ค.
๊ทธ ๋ค์์ ์ด์ ์ฌ์ฉ์๊ฐ ์์ ์๋ฆผ ๋ฉ์ธ์ง๋ง ๋ณด๊ณ ๋ ์ด๋ค ์ํฉ์ด ๋ฒ์ด์ง๋ ๊ฒ์ธ์ง ์ฝ๊ฒ ์ ์ ์๋๋ก OOO๋์ด ๋ฉ์ธ์ง๋ฅผ ๋ณด๋์ด์
๋ผ๋ ํ์์ ์ ๋ชฉ๊ณผ ๋ฉ์ธ์ง์ ๋ด์ฉ, ์๋๋ฐฉ์ ํ๋กํ ์ฌ์ง์ ์ฌ์ฉํ์ฌ Notification
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด ๋์ด๋ค.

์ด์จ๋ ์ด๋ ๊ฒ ํด์ ๋ธ๋ผ์ฐ์ ์ soomgo.com
์ด ์ด๋ ค์๋ค๋ฉด ์ฌ์ฉ์๋ค์ ๋ค๋ฅธ ์ผ์ ํ๋ค๊ฐ ๊ณ์ ํ์ด์ง๋ฅผ ํ์ธํ๊ฑฐ๋ ํธ๋ํฐ์ ํ์ธํ ํ์์์ด ๋ฐ์คํฌํ ๋ด์์ ์๋ก์ด ์ฑํ
๋ฉ์ธ์ง๋ฅผ ๋ฐ๋ก ํ์ธํ ์ ์๊ฒ ๋์๋ค.
์์ง๋ Background
์ํ์์ ํธ์ ๋ฉ์ธ์ง๋ฅผ ๋ฐ๋ก ๋ณด์ฌ์ฃผ์ง ๋ชปํ๋ค๋ ๊ฒ ์์ฝ๊ธด ํ๋ค. ํ์ง๋ง ์ด ์ ๋๋ง ํด๋ ์ฌ์ฉ์๋ค ์
์ฅ์์๋ ๊ฝค๋ ํธํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค.
๋ง์น๋ฉฐ
์ฌ์ค ์ฒ์ ๋ชฉํํ๋ ๊ฑธ ๋ค ์ด๋ฃจ์ง ๋ชปํด์ ์ฐ์ฐํ์ง๋ง ๋๋ฌด ํผ๊ณคํ๊ธฐ ๋๋ฌธ์ ๋ค์์ ๊ธฐ์ฝํ๊ธฐ๋ก ํ๋ค. ์ด์ ์์์ผ์ ์ถ๊ทผํด์ PO
ํํ
์ด๊ฑธ ๋ณด์ฌ์ฃผ๊ณ ํน์ ๋ญ ์ถ๊ฐํ๊ณ ์ถ์ ๊ฑฐ ์๋์ง ๋ฌผ์ด๋ณด๊ณ ๋ช๊ฐ์ง ํ
์คํธ๋ฅผ ์ข ํด๋ณธ ํ ๋ฐฐํฌํ ์์ ์ด๋ค.
์๋น์ค ์์ปค์ fetch
์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ๋ฉด Add to Homescreen
๊ธฐ๋ฅ๋ ์ฌ์ฉํ ์ ์์ง๋ง ์ฌ์ค ์จ๊ณ ์ ํ๋ก ํธ์๋ ์ฑํฐ ๊ณต์ ์
์ฅ์ ์ฌ์ฉ์๋ค์ด ๋ชจ๋ฐ์ผ ์น๋ณด๋ค๋ ๋ชจ๋ฐ์ผ ์ฑ์ ๋ง์ด ์ฌ์ฉํ์ผ๋ฉด ํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ด๊ฑด ํ ๊น๋ง๊น ๊ณ ๋ฏผ ์ค์ด๋ค.(์ธ์ฑ ๋ธ๋ผ์ฐ์ ํฌ๋ก์ค๋ธ๋ผ์ฐ์ง ํ๊ธฐ ์ซโฆ)
์ผ๋จ ์ฒ์ ์ค์ฝํ๋ฅผ ๋๋ฌด ํฌ๊ฒ ์ก์ ๊ฒ ๊ฐ๊ธฐ๋ ํ๋ค. ํ๋ํ๋ ์ข ์์ธํ ์์๋ณด๊ณ ์์ ์ ํ์ผ๋ฉด ์ข์์ ๊ฒ ๊ฐ์๋ฐ ์์์ผ๋ถํฐ๋ ๋ฐ๋ก ๋ ํ๋ ํ๋ก์ ํธ ์์ ์ ๋ค์ ํด์ผํด์ ๋ง์์ด ๊ธํ๋ ๊ฒ๋ ์๋ค. ๊ทธ๋ฆฌ๊ณ ํ์ฌ์ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ๋ถ์กฑํ๊ธฐ ๋๋ฌธ์ ์ด๋ฐ ๊ธฐ์ ์ ์ธ ๊ธฐ๋ฅ์ ๋ถํ๋ ๊ฑด ์ฐ์ ์์๊ฐ ๋ฎ์ ํธ์ด๋ผ์ โ์ง๊ธ ์๋๋ฉด ์์ผ๋ก ์ธ์ ํ ์ ์์ ์ง ๋ชจ๋ฅธ๋คโ๋ผ๋ ๋ง์๋ ์ปธ๋ ๊ฒ ๊ฐ๋ค.
๋ง๊ฐ์ ์ด์ฉํด, ํ์์ ํจ๊ป ์ผํด์ฃผ์ค ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ๋ถ์ ๋ชจ์ ๋ค๋ JD๋ฅผ ๋ฟ๋ฆฌ๋ฉด์ ํฌ์คํ ์ ๋ง๋ฌด๋ฆฌ ํ๊ฒ ๋ค. PWAย ์ธ์๋ ๋ค๋ฅธ ํ๊ณ ์ถ์ ๊ฑด ๋ง์๋ฐ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ๋ชจ์๋ผ์ ๋ชปํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ฒฝ๋ ฅ ์ฌํ์ ์๊ด์์ด ๊ทธ๋ฅ ์ฌ๋ฐ๋ ๊ฑฐ ์ข์ํ์๋ ๋ถ์ด๋ฉด ๋๋ค.(ํ๊ณ ์ถ์ ๊ฐ๋ฐ ๋ค ํ์ค ์ ์๋๋ก ์ด ํ๋ชธ ๋ถ์ด๋ผ ๋ณดํํ๊ฒ ์ต๋๋ค.)
์ด์์ผ๋ก PWA ํ๋ฃจ ๋ง์ ๋์ ํ๊ธฐ ํฌ์คํ ์ ๋ง์น๋ค.
- JavaScript
- ์๋ฐ์คํฌ๋ฆฝํธ
- Web Push
- Web Socket
- socket.io
- Notification API
- Vue
- PWA
- Progressive Web Application
๊ด๋ จ ํฌ์คํ ๋ณด๋ฌ๊ฐ๊ธฐ
![[JS ํ๋กํ ํ์
] ํ๋กํ ํ์
์ ์ฌ์ฉํ์ฌ ์์ํ๊ธฐ [JS ํ๋กํ ํ์
] ํ๋กํ ํ์
์ ์ฌ์ฉํ์ฌ ์์ํ๊ธฐ](/static/ffc88c93a54f1c5d1783f61478e57588/4e277/thumbnail.png)
[JS ํ๋กํ ํ์ ] ํ๋กํ ํ์ ์ ์ฌ์ฉํ์ฌ ์์ํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ/์๋ฐ์คํฌ๋ฆฝํธ![[JS ํ๋กํ ํ์
] ์๋ฐ์คํฌ๋ฆฝํธ์ ํ๋กํ ํ์
ํ์ด๋ณด๊ธฐ [JS ํ๋กํ ํ์
] ์๋ฐ์คํฌ๋ฆฝํธ์ ํ๋กํ ํ์
ํ์ด๋ณด๊ธฐ](/static/c4c3f4bd82adfc1f6422180eedbd4fb0/4e277/thumbnail.png)
[JS ํ๋กํ ํ์ ] ์๋ฐ์คํฌ๋ฆฝํธ์ ํ๋กํ ํ์ ํ์ด๋ณด๊ธฐ
ํ๋ก๊ทธ๋๋ฐ/์๋ฐ์คํฌ๋ฆฝํธ
์ต์ ๊ฐ๊ณผ ์ต๋ ๊ฐ์ ๋น ๋ฅด๊ฒ ์ฐพ์ ์ ์๊ฒ ๋์์ฃผ๋ ํ(Heap)
ํ๋ก๊ทธ๋๋ฐ/์๊ณ ๋ฆฌ์ฆ![[JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์ค๋์ค ์ดํํฐ๋ก ๋๋ง์ ์๋ฆฌ ๋ง๋ค๊ธฐ [JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์ค๋์ค ์ดํํฐ๋ก ๋๋ง์ ์๋ฆฌ ๋ง๋ค๊ธฐ](/static/026a9fe9c894f201ec1e45217221447c/163a5/thumbnail.jpg)
[JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์ค๋์ค ์ดํํฐ๋ก ๋๋ง์ ์๋ฆฌ ๋ง๋ค๊ธฐ
ํ๋ก๊ทธ๋๋ฐ/์ค๋์ค![[JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์๋ฆฌ์ ํ๋ฆ์ ํ์
ํ์ [JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์๋ฆฌ์ ํ๋ฆ์ ํ์
ํ์](/static/026a9fe9c894f201ec1e45217221447c/163a5/thumbnail.jpg)
[JavaScript ์ค๋์ค ์ดํํฐ ๋ง๋ค๊ธฐ] ์๋ฆฌ์ ํ๋ฆ์ ํ์ ํ์
ํ๋ก๊ทธ๋๋ฐ/์ค๋์ค