๐Ÿš€ ์ถ”๊ฐ€๊ตฌํ˜„: PWA ๊ตฌํ˜„ํ•˜๊ธฐ

Yeonnยท2025๋…„ 3์›” 11์ผ
0

๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

๋ชฉ๋ก ๋ณด๊ธฐ
7/10
post-thumbnail

โ“ ์บ ํ•‘ํ•‘์˜ ์‚ฌ์šฉ์„ฑ ๋†’์ด๊ธฐ

์บ ํ•‘์กฑ์„ ์œ„ํ•œ ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋“ค๊ณผ ์บ ํ•‘ํ•‘ ์˜ ์ฐจ๋ณ„์ ์€ ๋ฌด์—‡์ผ๊นŒ? ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์•˜๋‹ค.

  • ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ๊ธฐ๋ฐ˜ ์œผ๋กœ ์ฃผ๋ณ€์˜ ์บ ํ•‘์žฅ์„ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ๊ธฐ๋ฐ˜ ์œผ๋กœ ์ฃผ๋ณ€ ์‚ฌ์šฉ์ž์™€ ์†Œํ†ต ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ์ž์˜ ์œ„์น˜๋ฅผ ๊ธฐ๋ฐ˜ ์œผ๋กœ ํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ณ ,
์ฃผ๋ณ€ ์‚ฌ์šฉ์ž์™€ ์†Œํ†ต์„ ์›ํ™œํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์˜คํ”„๋ผ์ธ์‹œ์—๋„ ๊ธฐ๋Šฅ ์œ ์ง€ ๊ฐ€ ๊ฐ€๋Šฅํ•ด์•ผ ํ–ˆ๋‹ค.

์ด๋Ÿฐ ์š”๊ตฌ ์‚ฌํ•ญ๋“ค์„ ์ถฉ์กฑ์‹œํ‚ค๊ธฐ ์œ„ํ•ด PWA( Progressive Web App ) ๊ธฐ์ˆ ์„ ๋„์ž…ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.


โ—๏ธ PWA ( Progressive Web App )

PWA๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ๊ณผ ์œ ์‚ฌํ•œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ์›น ์•ฑ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์ด ์žˆ๋‹ค.

๐Ÿ“ PWA( Progressive Web App ) ํŠน์ง•

  • ์˜คํ”„๋ผ์ธ ์ง€์›( ๋„คํŠธ์›Œํฌ๊ฐ€ ์—†๋Š” ํ™˜๊ฒฝ์—์„œ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์œ ์ง€ ๊ฐ€๋Šฅ )
  • ๋ชจ๋ฐ”์ผ ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€ ๊ฐ€๋Šฅ( ๋ณ„๋„์˜ ์•ฑ ์„ค์น˜ ์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅ )
  • ๊ธฐ์กด ์›น ๋ณด๋‹ค ๋น ๋ฅธ ๋กœ๋”ฉ ์†๋„

์œ„์™€ ๊ฐ™์€ PWA์˜ ํŠน์ง•์€ ์บ ํ•‘ํ•‘์ด ๋ชจ๋ฐ”์ผ์—์„œ ์‹คํ–‰ํ•  ๋•Œ ์ฆ‰์‹œ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ณ ,
์˜คํ”„๋ผ์ธ์—์„œ ์ฑ„ํŒ… ๋“ฑ์— ๋Œ€ํ•œ ํ‘ธ์‹œ ์•Œ๋ฆผ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ๋Š” ๋ฐ์— ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.


๐Ÿš€ PWA ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“ manifest.json ์„ค์ •

์•ฑ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์•ฑ ์ด๋ฆ„, ์•„์ด์ฝ˜, ์‹œ์ž‘ URL ๋“ฑ์„ ์„ค์ •ํ•ด ์ค€๋‹ค.

// src/app/manifest.json
{
  "name": "์บ ํ•‘ํ•‘",
  "short_name": "์บ ํ•‘ํ•‘",
  // PWA ์•ฑ ์•„์ด์ฝ˜ ์„ค์ •( ๋‹ค์–‘ํ•œ ํ•ด์ƒ๋„ ์ง€์› )
  "icons": [
    {
      "sizes": "128x128",
      "src": "/images/maskable_icon_x128.png",
      "type": "image/png"
    },
	{
      "sizes": "192x192",
      "src": "/images/maskable_icon_x192.png",
      "type": "image/png"
    },
    {
      "sizes": "512x512",
      "src": "/images/maskable_icon_x512.png",
      "type": "image/png"
    }
  ],
  // ํŠน์ • ํŽ˜์ด์ง€๋กœ ๋ฐ”๋กœ ์ด๋™ํ•˜๋Š” ๋‹จ์ถ• ์„ค์ •
  "shortcuts": [
    {
      "name": "list",
      "url": "/list"
    },
	// ...codes
  ],
  // ์•ฑ์ด ์‹คํ–‰๋  ๋•Œ ์‹œ์ž‘๋˜๋Š” url
  "start_url": "/",
  // standalone์„ ์„ค์ •ํ•˜์—ฌ ๋„ค์ดํ‹ฐ๋ธŒ ์•ฑ์ฒ˜๋Ÿผ ์‹คํ–‰๋˜๋„๋ก ๊ตฌ์„ฑ
  "display": "standalone",
}

๐Ÿ“ next-pwa ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ

์˜คํ”„๋ผ์ธ ํ™˜๊ฒฝ์—์„œ๋„ ์•ฑ์ด ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„œ๋น„์Šค ์›Œ์ปค๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.
์ด๋ฅผ ์œ„ํ•ด์„œ next-pwa ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ–ˆ๋‹ค.

npm install @ducanh2912/next-pwa
// next.config.mjs
const nextConfigFunction = async (phase) => {
  if (phase === PHASE_DEVELOPMENT_SERVER || phase === PHASE_PRODUCTION_BUILD) {
    const withPWAPlugin = (await import('@ducanh2912/next-pwa')).default({
      // PWA ๊ด€๋ จ ํŒŒ์ผ public ํด๋”์— ์ €์žฅ
      dest: 'public',
      // ์บ์‹œํ•  ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ
      maximumFileSizeToCacheInBytes: 15 * 1024 * 1024
    });
    return withPWAPlugin(nextConfig);
  }
  return nextConfig;
};

๐Ÿ“ PWA ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ

์‚ฌ์šฉ์ž๊ฐ€ PWA๋กœ ๋ฐฉ๋ฌธํ–ˆ๋Š” ์ง€, ์ผ๋ฐ˜ ์›น์œผ๋กœ ๋ฐฉ๋ฌธํ–ˆ๋Š” ์ง€๋ฅผ ์ฒดํฌํ•ด์•ผ ํ•œ๋‹ค.
์ด๋ฅผ ์œ„ํ•ด display-mode๋ฅผ ์ฒดํฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

export const isPwa = () => {
  if (typeof window !== 'undefined') {
    return window.matchMedia('(display-mode: standalone)').matches;
  }
  return false;
};

window.matchMedia('(display-mode: standalone)').matches๋ฅผ ํ†ตํ•ด
PWA๋กœ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ true ๋ฐ˜ํ™˜, ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

PWA์—์„œ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ ์„ค์น˜ ๋ฒ„ํŠผ์„ ์ˆจ๊ธด๋‹ค.

๐Ÿ“ PWA ์„ค์น˜ ์•ˆ๋‚ดํ•˜๊ธฐ

PWA๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€ํ•ด์•ผ ์„ค์น˜ ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด์— ๋Œ€ํ•œ ์•ˆ๋‚ด๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.
์ด ์•ˆ๋‚ด๋ฅผ ์œ„ํ•ด beforeinstallprompt ์ด๋ฒคํŠธ๋ฅผ ํ™œ์šฉํ–ˆ๋‹ค.

// custom Hook: usePwaPrompt.ts
import { usePwaStore } from '@/stores/pwaState';
import { useEffect } from 'react';
import { toast } from 'react-toastify';

export const usePwaPrompt = () => {
  const {
    deferredPrompt,
    setDeferredPrompt,
    isPwaOpen,
    setIsPwaOpen,
    setClicked,
  } = usePwaStore();

  useEffect(() => {
    const handleBeforeInstallPrompt = (e: BeforeInstallPromptEvent) => {
      // ์ž๋™ ์‹คํ–‰ ๋ฐฉ์ง€
      e.preventDefault();
      setDeferredPrompt(e);
      setIsPwaOpen(false);
    };

    window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);

    return () => {
      window.removeEventListener(
        'beforeinstallprompt',
        handleBeforeInstallPrompt
      );
    };
  }, []);

  // ์„ค์น˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์‹คํ–‰๋  ํ•จ์ˆ˜
  const installPwa = async () => {
    // ์„ค์น˜ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๊ฒฝ์šฐ ์„ค์น˜ ์•ˆ๋‚ด ๋ชจ๋‹ฌ์„ ๋„์šด๋‹ค.
    setClicked('install');

    // iOS๋Š” beforeinstallprompt ์ด๋ฒคํŠธ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    // iOS๋Š” 'pwa-unsupported' ์ƒํƒœ์˜ ๋ชจ๋‹ฌ์„ ๋„์›Œ 'ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€' ๊ธฐ๋Šฅ์„ ์•ˆ๋‚ดํ•œ๋‹ค.
    if (!deferredPrompt) {
      setIsPwaOpen(true, 'pwa-unsupported'); // iOS: ํ™ˆ ํ™”๋ฉด ์ถ”๊ฐ€ ์•ˆ๋‚ด ๋ชจ๋‹ฌ ํ‘œ์‹œ
    } else {
      setIsPwaOpen(true, 'pwa-supported'); // Android: PWA ์„ค์น˜ ํ™•์ธ ๋ฐ ์„ค์น˜ ํ”„๋กœ์„ธ์Šค ์ง„ํ–‰
    }
  };

  // ์„ค์น˜ ์•ˆ๋‚ด ๋ชจ๋‹ฌ์—์„œ ์•ฑ ์„ค์น˜ ์•ˆ๋‚ด๋ฅผ ํ•˜๊ณ  ์„ค์น˜/์ทจ์†Œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  // ์„ค์น˜๋ฅผ ์„ ํƒํ•  ๊ฒฝ์šฐ PWA ์„ค์น˜๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค. toast ์•Œ๋ฆผ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฒฐ๊ณผ๋ฅผ ์•ˆ๋‚ดํ•œ๋‹ค.
  const handleInstall = async () => {
    if (!deferredPrompt) return;
    setIsPwaOpen(false);

    deferredPrompt.prompt();
    deferredPrompt.userChoice.then((result) => {
      if (result.outcome === 'accepted') {
        toast.success('PWA๊ฐ€ ์„ค์น˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽ‰');
      } else {
        toast.error('์„ค์น˜๊ฐ€ ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
      }
      setDeferredPrompt(null);
    });
  };

  const handleClose = async () => {
    setIsPwaOpen(false);
  };

  return {
    deferredPrompt,
    setDeferredPrompt,
    isPwaOpen,
    installPwa,
    handleInstall,
    handleClose,
  };
};

โœจ ๊ตฌํ˜„ ๊ฒฐ๊ณผ

๐Ÿ“ iOS

๐Ÿ“ Android

0๊ฐœ์˜ ๋Œ“๊ธ€