๐Ÿšค ์„ฑ์žฅ์ผ์ง€ 7.0

์ฑ… ํ–‰๋ณตํ•œ ์ด๊ธฐ์ฃผ์˜์ž(์›จ์ธ ๋‹ค์ด์–ด)์˜ ๋‚ด์šฉ์— ์ž๊ทน๋ฐ›์•„ ์‹œ์ž‘ํ•˜๋Š” ์†Œ๋ฐ•ํ•œ ์„ฑ์žฅ๊ธฐ๋ก

์‚ด์•„์žˆ๋Š” ๊ฝƒ๊ณผ ์ฃฝ์€ ๊ฝƒ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ๋ณ„ํ•˜๋Š”๊ฐ€?
์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์‚ด์•„ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
์ƒ๋ช…์˜ ์œ ์ผํ•œ ์ฆ๊ฑฐ๋Š” ์„ฑ์žฅ์ด๋‹ค!

โš› (7.0)<์™„์ „ ๊ฐœํŽธ> ํŒŒ์ธ๋งŒ ํ•™์Šต๋ฒ•์„ ์•Œ๊ฒŒ ๋œ๋งŒํผ, ์„ฑ์žฅ์ผ์ง€๋Š” ์ •๋ง ๊ทธ ๋‚ ์˜ ํ‚ค์›Œ๋“œ ์ค‘์‹ฌ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋„๋ก ํ•œ๋‹ค.

Next.js

๊ธฐ์กด์— React๋กœ ์ง„ํ–‰ํ–ˆ๋˜ ๋ฒ ์ŠคํŠธ ๋ผ๋นˆ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ Next.js๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ์‚ฌ์‹ค ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ฒ ์ŠคํŠธ ๋ผ๋นˆ์Šค๋Š” Next๋กœ ์ง„ํ–‰ํ•˜๊ณ  ์‹ถ์—ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์„œ๋น„์Šค ์ œ๊ณต ๋ฐ ์œ ์ง€ ๋ณด์ˆ˜์— ์ดˆ์ ์„ ๋งž์ถ”๊ณ  ์‹œ์ž‘ํ•œ ํ”„๋กœ์ ํŠธ๋ผ์„œ SEO์— ๊ฐ•์ ์„ ์ฃผ๊ณ  ์‹ถ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•˜์ง€๋งŒ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ์ „์— ๊ธ‰ํ•˜๊ฒŒ Next๋ฅผ ๋ฐฐ์šฐ๋ฉด์„œ ์ง„ํ–‰ํ•˜๊ธฐ์—๋Š” ์ฃผ์–ด์ง„ 10์ผ์ด๋ผ๋Š” ์‹œ๊ฐ„ ๋™์•ˆ MVP ์กฐ์ฐจ ์™„์„ฑํ•˜์ง€ ๋ชปํ•  ๊ฑฐ ๊ฐ™์•„์„œ React๋กœ ์ง„ํ–‰ํ–ˆ์—ˆ๋‹ค. MVP ๊ฐœ๋ฐœ์ด ๋๋‚˜๊ณ  ๋งˆ์นจ ์‹œ๊ฐ„์ด ๋‚˜์„œ Next๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

PWA ์ ์šฉ๊ณผ Open Graph

Next๋„ React์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ PWA๋ฅผ ์ง€์›ํ•œ๋‹ค. ๊ทธ ๋ฐฉ๋ฒ• ์ž์ฒด๊ฐ€ ํฌ๊ฒŒ ์–ด๋ ต์ง€ ์•Š์€๋ฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. next-pwa ํŒจํ‚ค์ง€ ์„ค์น˜
npm i next-pwa
  1. next.config.js ๋‚ด์šฉ ์ˆ˜์ •
const withPWA = require('next-pwa')({
  dest: 'public',
  // disable: process.env.NODE_ENV === 'development',
});

module.exports = withPWA({
  // next.js config
});

๊ฐœ์ธ์ ์œผ๋กœ ์œ„์˜ config์—์„œ disable: process.env.NODE_ENV === 'development'์€ ์„ค์ •ํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด npm run dev๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค pwa ๋นŒ๋“œ ํŒŒ์ผ์ด ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.(commitํ•  ๋•Œ๋งˆ๋‹ค ๋นŒ๋“œ ํŒŒ์ผ์ด ์ƒˆ๋กœ ์ƒ์„ฑ๋ผ์„œ ๊ต‰์žฅํžˆ ๋ถˆํŽธํ•˜๋‹ค.)

์ถ”๊ฐ€๋กœ ์œ„์˜ disable ์„ค์ •์„ ํ•˜์ง€ ์•Š๊ณ  ์ผ๋‹จ npm run build๋ฅผ ํ†ตํ•ด์„œ public์— workbox-*.js์™€ sw.js(service-worker)๊ฐ€ ์ƒ์„ฑ๋˜๋Š”์ง€ ํ™•์ธํ•˜์ž.

  1. public์— manifest.json ์ƒ์„ฑ

manifest.json์„ ์„ค์ •ํ•˜์—ฌ ์›น ์•ฑ์„ ์„ค์น˜ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์•„์ด์ฝ˜, ์ด๋ฆ„, ์„ค๋ช… ๋“ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

{
  "name": "Best Robbins",
  "short_name": "Best Robbins",
  "description": "Best Robbins",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    }
    // ... ์ค‘๋žต
  ],
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
  1. head์— meta ํƒœ๊ทธ ์ถ”๊ฐ€
  • ์ผ๋ฐ˜์ ์ธ html ํ˜น์€ next 12 ๋ฒ„์ „์—์„œ๋Š” next/head๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Head ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— <meta /> ํ˜•์‹์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ•˜์ง€๋งŒ next 13 ๋ฒ„์ „์—์„œ๋Š” head ์ถ”๊ฐ€ ๋ฐฉ๋ฒ•์ด ๋ณ€๊ฒฝ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€์žฅ ์ตœ์ƒ๋‹จ์˜ layout.tsx ํ˜น์€ page.tsx์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.
export const metadata: Metadata = {
  metadataBase: new URL('https://best-robbins-fe.vercel.app/'),
  title: {
    default: '๋ฒ ์ŠคํŠธ๋ผ๋นˆ์Šค',
    template: '%s | ๋ฒ ์ŠคํŠธ๋ผ๋นˆ์Šค',
  },
  description: '์ตœ๊ณ ์˜ ์•„์ด์Šคํฌ๋ฆผ ์กฐํ•ฉ์„ ์ถ”์ฒœ๋ฐ›์•„๋ณด์„ธ์š”!',
  manifest: '/manifest.json',
  openGraph: {
    type: 'website',
    title: '๋ฒ ์ŠคํŠธ๋ผ๋นˆ์Šค',
    description: '์ตœ๊ณ ์˜ ์•„์ด์Šคํฌ๋ฆผ ์กฐํ•ฉ์„ ์ถ”์ฒœ๋ฐ›์•„๋ณด์„ธ์š”!',
    images: 'https://kr.object.ncloudstorage.com/best-robbins/logo/logo_string.svg',
    locale: 'ko_KR',
    url: 'https://best-robbins-fe.vercel.app/',
    siteName: '๋ฒ ์ŠคํŠธ๋ผ๋นˆ์Šค',
  },
};

์—ฌ๊ธฐ์„œ openGraph๋Š” ์šฐ๋ฆฌ๊ฐ€ ํ”ํžˆ og:image๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”ํƒ€ ํƒœ๊ทธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. Open Graph๋Š” ํŽ˜์ด์Šค๋ถ์—์„œ ๋งŒ๋“  ๋ฉ”ํƒ€ ํƒœ๊ทธ๋กœ, ์›น ํŽ˜์ด์ง€๊ฐ€ ์†Œ์…œ ๋ฏธ๋””์–ด์— ๊ณต์œ ๋  ๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋กœ ๋ณด์—ฌ์ง€๋Š” ์ •๋ณด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์†Œ์…œ ๋ฏธ๋””์–ด์—์„œ ๊ณต์œ ๋  ๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ์ž˜ ๋‚˜์˜ค๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ๋์ด ๋‚ฌ์œผ๋ฉด ๋นŒ๋“œ ํ›„, npm run start๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  localhost:3000์— ์ ‘์†ํ•ด๋ณด์ž. ๊ทธ๋ฆฌ๊ณ  ๋ผ์ดํŠธํ•˜์šฐ์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉด PWA๊ฐ€ ์ ์šฉ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋กœ์ปฌ ํฐํŠธ ์ ์šฉํ•˜๊ธฐ

next์—์„œ ์ œ๊ณตํ•˜๋Š” next/font/google์„ ์‚ฌ์šฉํ•ด์„œ ๊ตฌ๊ธ€ ํฐํŠธ๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋กœ์ปฌ ํฐํŠธ๋ฅผ ์ ์šฉํ•  ๋•Œ๋Š” next/font/local์„ ์‚ฌ์šฉํ•œ๋‹ค.

๋จผ์ € ์›ํ•˜๋Š” ํฐํŠธ๋ฅผ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๋Š”๋‹ค. ๋‚˜๋Š” Pretendard๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. public/fonts์— ํฐํŠธ ํŒŒ์ผ์„ ๋„ฃ๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ์ „์—ญ์— ํฐํŠธ๊ฐ€ ์ ์šฉ๋œ๋‹ค.

import './globals.css';
import type { Metadata } from 'next';
import localFont from 'next/font/local';

export const Pretendard = localFont({
  src: '../../public/fonts/PretendardVariable.woff2',
});

// ... ์ค‘๋žต

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body className={Pretendard.className}>{children}</body>
    </html>
  );
}

๐Ÿ“ ํšŒ๊ณ 

ํ™•์‹คํžˆ ๋ฐฉํ–ฅ์„ ์ •ํ•˜๊ณ  ๋‹ค์‹œ ์ฝ”๋”ฉ์„ ํ•˜๋‹ˆ๊นŒ ๋„ˆ๋ฌด ์žฌ๋ฏธ์žˆ๋‹ค..ใ… ใ…  ๊ฐœ๋ฐœ ์ž์ฒด์—์„œ ์˜ค๋Š” ์ŠคํŠธ๋ ˆ์Šค๋„ ์žˆ์ง€๋งŒ ๊ฐœ๋ฐœํ•˜์ง€ ์•Š์„ ๋•Œ ์˜ค๋Š” ์ŠคํŠธ๋ ˆ์Šค๊ฐ€ ๋” ํฌ๊ณ  ํž˜๋“  ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ณดํ†ต์€ ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ์ƒˆ๋กœ์šด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ์žฌ๋ฏธ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ์˜ค๋Š˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋„ ๊ทธ๋ ‡๊ณ  ๋ฆฌํŒฉํ† ๋ง๋„ ๊ทธ๋ ‡๊ณ  ์˜คํžˆ๋ ค ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ณ  ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒŒ ๋” ์žฌ๋ฏธ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ์•ž์œผ๋กœ๋Š” ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๊ธฐ์กด์— ๊ฐœ๋ฐœํ–ˆ๋˜ ์„œ๋น„์Šค๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์— ๋” ์ง‘์ค‘ํ•ด์„œ ๊ฐœ๋ฐœํ•  ์˜ˆ์ •์ด๋‹ค.

์ฐธ๊ณ