Migration: CRA To Next.js (feat. React v18)

NaHCoH
  • #React
  • #CRA
  • #SSR
  • #NextJS
  • #migration
  • #SEO

์•ˆ๋…•ํ•˜์„ธ์š”. ์›๋”์›” ํ”„๋ก ํŠธ์—”๋“œํŒ€ ํ™ฉํ˜ธ์ฐฌ์ž…๋‹ˆ๋‹ค.
๊ธฐ์กด ์›๋”์›” ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋กœ์ ํŠธ๋Š” CSR(CRA) ๋ฐฉ์‹์˜ ์›น ํŽ˜์ด์ง€์˜€์Šต๋‹ˆ๋‹ค.
์ตœ์ดˆ ์ƒ์„ฑ ์‹œ์—๋Š” ๋น ๋ฅธ ๊ฐœ๋ฐœ ์ง„ํ–‰์„ ์œ„ํ•ด์„œ, ๊ทธ ํ›„์—๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์˜ ๊ธฐํšŒ ๋น„์šฉ์ด ๋‚ฎ์•„์„œ ๋“ฑ์˜ ์ด์œ ๋กœ SSR(Next.js) ๋„์ž…์„ ๋ฏธ๋ฃจ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๊ทธ๋Ÿฐ ํ”„๋ก ํŠธ์—”๋“œ ์›๋”์›” ์›น ํ”„๋กœ์ ํŠธ์— ์™œ Next.js๋ฅผ ๋„์ž…ํ–ˆ๊ณ , ๋ฌด์—‡์ด ๋‹ฌ๋ผ์กŒ๋Š”์ง€ ์ด์•ผ๊ธฐํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

Why?

์ž, ๊ทธ๋Ÿผ ์—ฌํƒœ๊ป CSR๋กœ ๋ฐฉ์น˜(?)ํ•ด๋†“์€ ํ”„๋กœ์ ํŠธ์— ๊ฐ‘์ž๊ธฐ ์™œ Next.js ๋„์ž…์„ ์‹œ๋„ํ•˜๊ฒŒ ๋˜์—ˆ์„๊นŒ์š”?

์ฒซ ๋ฒˆ์งธ๋กœ๋Š”, ์šฐ์„  ๊ฐ„๋‹จํ•œ SEO ์ฒ˜๋ฆฌ์ž…๋‹ˆ๋‹ค.
๋ฐ‘์—์„œ ์„ค๋ช…ํ•  ์˜ˆ์ •์ด์ง€๋งŒ, CSR์—์„œ์˜ ๋ณต์žกํ•œ SEO์ฒ˜๋ฆฌ๋ฅผ Next.js์—์„œ๋Š” ๊ฐ„๋‹จํžˆ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์ด์œ ๋Š” ๋ฐ”๋กœ React 18๋ฒ„์ „์˜ ์ •์‹ ์ถœ์‹œ๋กœ ์ธํ•œ SSR์—์„œ์˜ Suspense ์ง€์›์ด์—ˆ์Šต๋‹ˆ๋‹ค.
SSR์—์„œ์˜ Suspense๋ฅผ ์ •์‹ ์ง€์›ํ•˜๊ฒŒ ๋จ์œผ๋กœ์จ, SSR์ด ๊ฐ–๋Š” ๋ช‡๊ฐ€์ง€ ๋ฌธ์ œ์ ๋“ค์ด ํ•ด์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋Š” Next.js๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ง„ํ–‰ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•œ ์ด์œ ์˜€๊ณ , ์ด๋ฒˆ ๊ธฐํšŒ์— Next.js ์ ์šฉ๊ณผ ํ•จ๊ป˜ React18 ์—…๋ฐ์ดํŠธ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋ญ๊ฐ€ ๋‹ฌ๋ผ์ง€๊ธด ํ–ˆ๋‚˜?

1. SEO ์ฒ˜๋ฆฌ

CSR ๊ธฐ๋ฐ˜์˜ ์›น์€ ๊ธฐ๋ณธ์ ์œผ๋กœ SEO ์ฒ˜๋ฆฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. CRA๋กœ ๋งŒ๋“  ํ”„๋กœ์ ํŠธ์—์„œ ์›น ํฌ๋กค๋Ÿฌ๊ฐ€ body ํƒœ๊ทธ๋‚ด๋ถ€์—์„œ ์ฐพ์•„๋ณผ์ˆ˜ ์žˆ๋Š”๊ฑดโ€ฆ

<div id="root"></div>

์‹ค์ œ ๋‚ด์šฉ๋“ค์ด ๋ Œ๋”๋ง๋  root ๋…ธ๋“œ ํ•œ์ค„ ๋ฟ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” AWS CloudFront Lambda@Egde๋ฅผ ํ†ตํ•ด์„œ SEO ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํŽ˜์ด์ง€๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์ƒ์„ฑ๋˜๋ฉด Lambda@Edge ์ฝ”๋“œ์— ํ•ด๋‹น ํŽ˜์ด์ง€์— ๋Œ€ํ•œ SEO ์ฒ˜๋ฆฌ๋ฅผ ์ถ”๊ฐ€/์‚ญ์ œํ•˜๊ณ  ๋ณ€๊ฒฝ๋œ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ CloudFront์— ๋ฐฐํฌํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

let html = await new S3({signatureVersion: 'v4'})
    .getObject({
        Bucket: stageVariable.Bucket,
        Key: 'index.html'
    })
    .promise()
    .then(value => value.Body.toString());

// html head meta ์ˆ˜์ •

response.body = html;

return response;

ํ•˜์ง€๋งŒ Next.js๋ฅผ ๋„์ž…ํ•œ ํ›„์—๋Š” ๊ฐ ํŽ˜์ด์ง€์—์„œ getStaticProps ํ˜น์€ getServerSideProps์—์„œ SEO ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ฐ’์„ ์ •์˜ํ•˜๊ณ , ํ•ด๋‹น ๊ฐ’์„ ํŽ˜์ด์ง€์˜ props๋กœ ๋„˜๊ธฐ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ณ„๋„ ๋ฐฐํฌ ์—†์ด๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export const getServerSideProps = async ({query}) => {
    const response = await getClass({classId: Number(query.id)});

    if (!response) {
        return {
            redirect: {
                permanent: false,
                destination: Path.Class
            }
        };
    }

    return {
        props: {
            meta: {
                title: response.title
            }
        }
    };
};

<title>{props.meta.title}</title>;

์ด์ฒ˜๋Ÿผ ์„ค์ •ํ•œ ๋‚ด์šฉ์ด ์ •์ƒ์ ์œผ๋กœ html์— ํฌํ•จ๋œ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ํด๋” ๊ตฌ์กฐ ๋ณ€๊ฒฝ

๊ธฐ์กด ํ”„๋กœ์ ํŠธ์˜ ํด๋” ๊ตฌ์กฐ๋Š” ๊ฐ ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ ํด๋”๊ฐ€ ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค.

src/pages/Home
 - index.tsx
 - styles.ts
 - types.ts
 - components
   - HomeBanner
     - index.tsx
   - HomeContents
     - index.tsx
   - ...

ํ•˜์ง€๋งŒ Next.js์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” pages ํด๋”์˜ ํ•˜์œ„ ํด๋” ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ผ์šฐํŒ…์„ ํ•˜๋Š” ํ˜•์‹์œผ๋กœ, ๊ธฐ์กด์˜ ํด๋” ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.
์ด๋กœ ์ธํ•ด ํด๋” ๊ตฌ์กฐ ์ž์ฒด๋ฅผ ๋ณ€๊ฒฝํ•˜๊ฒŒ ๋˜์—ˆ๊ณ  ๊ธฐ์กด์˜ ํŽ˜์ด์ง€๋ณ„๋กœ ๋‚ด๋ถ€ ์š”์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ์•„๋‹Œ ๊ฐ ์š”์†Œ ๋‚ด๋ถ€์—์„œ ํŽ˜์ด์ง€ ๋ณ„๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

src/
 - pages/home
 - components/home
 - ...

3. react-router์—์„œ next/router๋กœ์˜ ๋ณ€๊ฒฝ

CRA ๊ธฐ๋ฐ˜์˜ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” history ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ react-router ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Next.js์—์„œ๋Š” ์ž์ฒด์ ์œผ๋กœ next/router๋ฅผ ์ง€์›ํ•˜์—ฌ, next/router๋กœ์˜ ์ˆ˜์ • ์ž‘์—…์„ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋‘˜ ์‚ฌ์ด์— ๋ฌธ๋ฒ•๊ณผ ๊ธฐ์กด์— ์ง€์›ํ•˜๋˜ ๊ธฐ๋Šฅ ์ค‘, ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ์ด ๋ช‡๊ฐœ ์žˆ์—ˆ๊ณ 
ํŠนํžˆ history ๊ฐ์ฒด์—์„œ์˜ ๋ช‡๋ช‡ ๋ณ€์ˆ˜ ๋ฐ ๋ฉ”์†Œ๋“œ๋“ค์€ next/router์—์„œ ์‚ฌ์šฉ๋ฒ•์ด ๋‹ค๋ฅด๊ฑฐ๋‚˜ ์—†๋Š” ๊ธฐ๋Šฅ์ด์–ด์„œ
next/router์˜ ์ ์ ˆํ•œ ๊ธฐ๋Šฅ๋“ค๋กœ ๋Œ€์ฒดํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

// v1
history.push('/class');
history.push('/class', {param: 'value'});
history.state; // {param: value}
if (history.action === 'pop');

// v2
router.push('/class');
router.push({pathname: '/class', query: {param: 'value'}});
history.query; // {param: 'value'}
router.beforePopState(() => {});

4. Suspense with SSR

๊ธฐ์กด์˜ SSR์€ ํด๋ผ์ด์–ธํŠธ์—์„œ HTML์„ ๋ฐ›์•„์™€ ๋ Œ๋”๋ง์„ ์ง„ํ–‰ํ•˜๊ณ , ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์ด ๊ธฐ๋Šฅ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•ด ๋ณด์ผ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฌธ์ œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„๋•Œ๋งŒ ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ๋ฐ์ดํ„ฐ ํŒจ์นญ์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์„๋•Œ, fallbackUI๋ฅผ ๋…ธ์ถœํ•˜๊ณ  ์‹ถ์„๋•Œ, ๋ฐ์ดํ„ฐ์˜ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํŒŒ์•…ํ•˜์—ฌ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ด๋Š” ๋ฐ์ดํ„ฐ, Javascript, ํ•˜์ด๋“œ๋ ˆ์ด์…˜์˜ ๊ณผ์ •์„ ๋ชจ๋‘ ๊ธฐ๋‹ค๋ฆฐ ํ›„์—์•ผ ๋น„๋กœ์†Œ ์ƒํ˜ธ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ด์Šˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. Container, Component1, Component2 ๋ Œ๋”๋ง
  2. Container, Component1, Component2 ํ•˜์ด๋“œ๋ ˆ์ด์…˜
  3. ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅ
//index.tsx
const data = fetch('url');

return (
  <Container>
    <Component1 />
    {data && <Component2 props={data} />}
  </Container>
)

ํ•˜์ง€๋งŒ React18์—์„œ SSR์— Suspense๋ฅผ ์ง€์›ํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์œ„ ๋ฌธ์ œ์ ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  1. Container, Component1, Component2 fallbackUI ๋ Œ๋”๋ง
  2. Container, Component1 hydration => Container, Component ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅ
    +) Component2 ๋ Œ๋”๋ง, Component2 ํ•˜์ด๋“œ๋ ˆ์ด์…˜
// Component2.tsx
const data = useSWR('url', {suspense: true, revalidateOnMount: true});

// index.tsx
return (
  <Container>
    <Component1 />
    <Suspense fallback={<Loading />}>
      <Component2 />
    </Suspense>
  </Container>
)

๊ธฐ์กด์— rendering, ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ๋ชจ๋‘ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ๋‚˜์„œ์•ผ User ์ƒํ˜ธ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ–ˆ๋˜๊ฒƒ๊ณผ ๋‹ฌ๋ฆฌ
2๋ฒˆ ์‹œ์ ์— ์‚ฌ์šฉ์ž๊ฐ€ Component2 ์˜์—ญ์— ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด 2๋ฒˆ ๊ณผ์ •์„ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  Component2์˜ ๋ Œ๋”๋ง, ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ์šฐ์„ ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
๋˜ํ•œ Component2์˜ ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์–ด๋„, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ƒํ˜ธ์ž‘์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ด ์™ธ์—๋„ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ window ๊ฐ์ฒด์˜ ๋ถ€์žฌ๋กœ ์ธํ•œ ์ด์Šˆ ์ฒ˜๋ฆฌ, Layout ์ปดํฌ๋„ŒํŠธ ๊ด€๋ฆฌ ๋“ฑ ์ˆ˜๋งŽ์€ ์ด์Šˆ ํ•ด๊ฒฐ ๋ฐ ๋ณ€๊ฒฝ์ด ์žˆ์—ˆ์ง€๋งŒ ๊ฐ€์žฅ ํฌ๊ฒŒ ๋‹ฌ๋ผ์ง„ ๋ถ€๋ถ„์€ ์ด๋ ‡๊ฒŒ ๋„ค๊ฐ€์ง€๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋‚จ์€์ผ์€โ€ฆ

์ด๋ ‡๊ฒŒ CRA ๊ธฐ๋ฐ˜ CSR ํ”„๋กœ์ ํŠธ(v1)์—์„œ Next.js ๊ธฐ๋ฐ˜์˜ SSR ํ”„๋กœ์ ํŠธ(v2)๋กœ์˜ ์›๋”์›” ํ”„๋ก ํŠธ์—”๋“œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค!!!
ํ•˜์ง€๋งŒ ์•„์ง ๋ถ€์กฑํ•œ ๋ถ€๋ถ„, ์ˆ˜์ •ํ•ด์•ผํ•  ๋ถ€๋ถ„์ด ์‚ฐ๋”๋ฏธ์ž…๋‹ˆ๋‹ค.
์›๋”์›” ์›น์€ ๊พธ์ค€ํžˆ ์—…๋ฐ์ดํŠธ ๋˜๊ณ  ์žˆ๊ณ , ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ ์—…๋ฐ์ดํŠธ๋˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ์š”์†Œ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด์—, ํŒ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋…ผ์˜ํ•˜์—ฌ ์ตœ์†Œํ•œ์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์˜ ์ฝ”๋“œ์ธ ์ƒํƒœ๋กœ ๋ฐฐํฌ ํ›„, v2์—์„œ ์ถ”๊ฐ€ ๊ฐœ๋ฐœ์˜ ์ง„ํ–‰ ๋ฐ ๊พธ์ค€ํ•œ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ v2 ๋ฒ„์ „์˜ ์›๋”์›” ์›น์—์„œ๋Š” ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์˜ ์ถ”๊ฐ€ ๊ฐœ๋ฐœ๊ณผ ํ•จ๊ป˜
๋”์šฑ SSR ๋‹ค์šด, ๋”์šฑ React๋‹ค์šด ์ฝ”๋“œ๋กœ์˜ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ๊ธ€์—์„œ๋Š” v2 migration ์ง„ํ–‰ ์‹œ, AWS S3 (CSR) ์—์„œ AWS Serverless (SSR) ๋กœ์˜ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ์™€
AWS Serverless์—์„œ Vercel๋กœ์˜ ๋ฐฐํฌ ํ”Œ๋žซํผ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ๋‹ค๋ฃจ์–ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋งŽ์€ ๊ด€์‹ฌ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค!
๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ฐธ์กฐ

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ