Nextjs ๋ถ€์ˆ˜๊ธฐ ๐Ÿคœ๐Ÿป

์ œ์ด๋ฐยท2021๋…„ 10์›” 31์ผ
3
post-thumbnail

Nextjs ์‹œ์ž‘ํ•˜๊ธฐ

1. ์ตœ์‹  node ๋‹ค์šด๋กœ๋“œ

2. npx create-next-app app_name

npx create-next-app app` ์˜ ์—ญํ• 

  1. ์ปดํŒŒ์ผ๊ณผ ๋ฒˆ๋“ค๋ง์ด ์ž๋™์œผ๋กœ ๋œ๋‹ค(webpack ๊ณผ babel)
    nextjs 12๋ฒ„์ „ ๋ถ€ํ„ฐ๋Š” SWC ๋ผ๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    (TMI swc๋Š” ํ•œ๊ตญ์ธ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐœ๋ฐœํ–ˆ๋‹คํ•จ.. ๐Ÿ™€respect)
  2. ์ž๋™ ๋ฆฌํ”„๋ ˆ์‰ฌ ๊ธฐ๋Šฅ์œผ๋กœ ์ˆ˜์ •ํ•˜๋ฉด ํ™”๋ฉด์— ๋ฐ”๋กœ ๋ฐ˜์˜๋œ๋‹ค.
  3. ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์ด ์ง€์›๋œ๋‹ค.
  4. ์Šคํƒœํ‹ฑ ํŒŒ์ผ์„ ์ง€์›ํ•œ๋‹ค.

TIP๐Ÿ”ฅ ์‚ฌ์ดํŠธ๊ฐ€ ssrํŽ˜์ด์ง€์ธ์ง€ csr์ธ์ง€ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐฉ๋ฒ•

  1. ์†Œ์Šค์ฝ”๋“œ๋ณด๊ธฐ๋กœ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ์ฝ”๋“œ๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๋Š”์ง€ ๋ณด๊ณ  ๊ตฌ๋ถ„
  2. ๊ฐœ๋ฐœ์ž ๋„๊ตฌ network์ฐฝ์„ ํ†ตํ•ด ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํŽ˜์ด์ง€๊ฐ€ ssr ์ด๋ผ๋ฉด Network ์ƒ๋‹จ ๋ชฉ๋กํŒŒ์ผ์„ ์—ด์—ˆ์„๋•Œ index ํŒŒ์ผ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋ฐ˜๋Œ€๋กœ csr ํŽ˜์ด์ง€ ๋ผ๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฝ๊ณ ๋ฌธ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ดˆ๊ธฐ์„ธํŒ…์‹œ BUG ๋ฐœ์ƒ ๐Ÿ™€

nextjs๊ฐ€ ๋ฒ„์ „ 12๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ๋˜๋ฉด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

failed to load SWC binary, ...

BUG

์ด์œ 

Next.js now uses Rust-based compiler SWC to compile JavaScript/TypeScript.

ํ•ด๊ฒฐ โœ…

  1. babelrc ์„ค์ •ํ•ด์ฃผ๊ธฐ!
{
  "presets": ["next/babel"]
}

Nextjs ๊ธฐ๋ณธ ํŒŒ์ผ ์ดํ•ดํ•˜๊ธฐ

_app.js

  1. ํŽ˜์ด์ง€ ์ „ํ™˜์‹œ ๋ ˆ์ด์•„์›ƒ์„ ์œ ์ง€ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ํŽ˜์ด์ง€ ์ „ํ™˜์‹œ ์ƒํƒœ๊ฐ’์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  3. componentDidCatch๋ฅผ ์ด์šฉํ•ด์„œ ์ปค์Šคํ…€ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ์ถ”๊ฐ€์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์ด์ง€๋กœ ์ฃผ์ž…์‹œ์ผœ ์ฃผ๋Š”๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค
  5. ๊ธ€๋กœ๋ฒŒ CSS ๋ฅผ ์ด๊ณณ์— ์„ ์–ธํ•œ๋‹ค.

_document.js (์ปค์Šคํ…€์„ ์œ„ํ•ด ์ง์ ‘ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ๋‹ค.)

next ์—์„œ ์ œ๊ณตํ•˜๋Š” document์„ ์ ์šฉํ•˜๋ฉด ๋งˆํฌ์—…์„ ์ปค์Šคํ…€ ํ•  ์ˆ˜์žˆ๋‹ค.

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

Dynamic Routes

Nextjs ์—์„œ๋Š” ๋™์ ๋ผ์šฐํŒ… ์‚ฌ์šฉํ•˜๊ธฐ

pages/view/[id].js

import { useRouter } from "next/router";

export default function Post() {
  const router = useRouter();
  const { id } = router.query;
  
  return (
  	<div>Post</div>
  );
}

next/link ์—์„œ ๊ฐ€์ ธ์˜จ Link๋Š” a ํƒœ๊ทธ์™€ ํ•จ๊ป˜ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜์—ฌ id๊ฐ’์œผ๋กœ ๋™์  ๋ผ์šฐํŒ…์„ ์ ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

import Link from "next/link";

export default function ItemList({ list }) {
  return (
    <>
        {list.map((item) => {
          return (
            <Link key={item.id} href={`/view/${item.id}`}>
              <a>
                    <div className="image">
                      <img src={item.image_link} alt={item.name} />
                    </div>
              </a>
            </Link>
          );
        })}
    </>
  );
}

์‚ฌ์ „ ๋ Œ๋”๋ง(Pre-rendering)

Nextjs ๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์ „ ๋ Œ๋”๋ง(htmlํŒŒ์ผ์„ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์คŒ)์„ ํ•ฉ๋‹ˆ๋‹ค.

์žฅ์ 

  1. ๋” ์ข‹์€ ํผํฌ๋จผ์Šค๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  2. ๊ฒ€์ƒ‰์—”์ง„์˜ ์ตœ์ ํ™”(SEO)

Pre-rendering ์˜ ๋‘๊ฐ€์ง€ ํ˜•ํƒœ

  1. ์ •์ ์ƒ์„ฑ
  2. Server Side Rendering (SSR, Dymanic Rendering)

๋‘˜์˜ ์ฐจ์ด์ ์€ ์–ธ์ œ html ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋Š”๊ฐ€?

์ •์ ์ƒ์„ฑ

  • ํ”„๋กœ์ ํŠธ๊ฐ€ ๋นŒ๋“œํ•˜๋Š” ์‹œ์ ์— htmlํŒŒ์ผ ์ƒ์„ฑ
  • ๋ชจ๋“  ์š”์ฒญ์— ์žฌ์‚ฌ์šฉ(๋ชจ๋“  ํŒŒ์ผ์„ ํ•œ๋ฒˆ์— ๋งŒ๋“ค์–ด ๋‘” ํ›„ ํ˜ธ์ถœ์ด ๋“ค์–ด์˜ฌ ๋•Œ ๋งˆ๋‹ค ์žฌ์‚ฌ์šฉ)
  • ํผํฌ๋จผ์Šค ์ด์œ ๋กœ, Nextjs๋Š” ์ •์  ์ƒ์„ฑ์„ ๊ถŒ๊ณ ํ•จ
  • ์ •์  ์ƒ์„ฑ๋œ ํŽ˜์ด์ง€๋“ค์€ CDN์— ์บ์‹œ
  • getStaticProps / getStaticPaths

์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์€ ๋งค ์š”์ฒญ๋งˆ๋‹ค html์„ ์ƒ์„ฑ

  • ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ ์œ ์ง€
  • getServerSideProps
    _- ์š”์ฒญํšŸ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚จ์— ๋”ฐ๋ผ ํผํฌ๋จผ์Šค๊ฐ€ ์กฐ๊ธˆ ๋–จ์–ด์งˆ ์ˆ˜๋Š” ์žˆ๋‹ค.

์ •์ ์ƒ์„ฑ, ์„œ๋ฒ„์‚ฌ์ด๋“œ ์ฐจ์ด์ 

์ •์ ์ƒ์„ฑ๊ณผ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์˜ ์ฐจ์ด๋Š” ์–ธ์ œ HTMLํŒŒ์ผ์„ ๋งŒ๋“œ๋Š”์ง€
1. ์ •์ ์ƒ์„ฑ์€ ๋นŒ๋“œ์‹œ html์„ ์ƒ์„ฑ, ์ดํ›„ ์š”์ฒญํ• ๋•Œ๋งˆ๋‹ค ์ด๋ฏธ ์ €์žฅ๋˜์–ด ์žˆ๋Š” Html ํŒŒ์ผ์„ ๋ณด์—ฌ์คŒ (์ •์ ์ƒ์„ฑ)
2. ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๊ฐ™์€ ๊ฒฝ์šฐ ์š”์ฒญ์„ ํ•œ ํ›„ html์„ ๋งŒ๋“ค์–ด ๋ณด์—ฌ์ค€๋‹ค.(๋™์ ์ƒ์„ฑ)

๋ชฉ์ ์— ์•Œ๋งž๊ฒŒ ์‚ฌ์šฉ ๋  ์ˆ˜ ์žˆ๋‹ค.

์ •์ ์ƒ์„ฑ, ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋” ์‚ฌ์šฉ ๊ตฌ๋ถ„

  1. User๊ฐ€ ์‚ฌ์šฉํ•˜๊ธฐ์ „์— ๋ฏธ๋ฆฌ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด๋„ ์ƒ๊ด€์—†์œผ๋ฉด ์ •์ ์ƒ์„ฑ
    ex)๋งˆ์ผ€ํŒ…ํŽ˜์ด์ง€, ๋„์›€๋ง , ์ œํ’ˆ๋ฆฌ์ŠคํŠธ...
  2. ์š”์ฒญ์ด ์žˆ์„๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ ๋ Œ๋”๋˜์–ด์•ผ ํ•œ๋‹ค๋ฉด ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง
    ex)ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ ์œ ์ง€, ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€, ๋ถ„์„์ฐจํŠธ ...

No Pre-rendering (REACT vs NEXTjs)

๊ธฐ๋ณธ react(csr)์™€ ๊ฐ™์ด pre-rendering๋œ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ JSload์ „ ์•„๋ฌด๋Ÿฐ ์ฝ”๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค.

nextjs์™€ ๊ฐ™์ด pre-rendering ๋œ ํŽ˜์ด์ง€ ๊ฐ™์€ ๊ฒฝ์šฐ JSload์ „ ์ด๋ฏธ html์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค (head, meta etc...)

์ •์ ์ƒ์„ฑ, ์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋” ์ฝ”๋“œ์— ์ง์ ‘ ์ ์šฉํ•ด๋ณด๊ธฐ

์ •์ ์ƒ์„ฑ ์ ์šฉํ•˜๊ธฐ

๊ธฐ์กด์ฝ”๋“œ

export default function Home() {
  const [list, setList] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  // ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์ด๋ผ next_public_ ๋ฅผ ๋ถ™์ž„
  const API_URL = process.env.NEXT_PUBLIC_API_URL;

  function getDate() {
    axios.get(API_URL).then((res) => {
      setList(res.data);
      setIsLoading(false);
    });
  }

  useEffect(() => {
    getDate();
  }, []);

  return (
    <div className={styles.container}>
      <Head>
        <title>HOME | ํ”„๋ก ํŠธ๋ชฝํ‚ค</title>
        <meta name="description" content="ํ”„๋ก ํŠธ๋ชฝํ‚ค ํ™ˆ ์ž…๋‹ˆ๋‹ค."></meta>
      </Head>
      {isLoading && (
        <div style={{ padding: "300px 0" }} className="ui segment">
          <div className="ui active inverted dimmer">
            <div className="ui text loader">Loading</div>
          </div>
          <p></p>
        </div>
      )}
      {!isLoading && (
        <>
          <Header as="h3" style={{ paddingTop: 40 }}>
            ๋ฒ ์ŠคํŠธ์ƒํ’ˆ
          </Header>
          <Divider />
          <ItemList list={list.slice(0, 6)} />
          <Header as="h3" style={{ paddingTop: 40 }}>
            ์‹ ์ƒํ’ˆ
          </Header>
          <Divider />
          <ItemList list={list} />
        </>
      )}
    </div>
  );
}

์ •์ ์ƒ์„ฑ ์ฝ”๋“œ ์ ์šฉ

export default function Home({ list }) {
  // ํŽ˜์ด์ง€๊ฐ€ ๋ฐ”๋กœ ๋‚˜ํƒ€๋‚˜๊ธฐ ๋•Œ๋ฌธ์— loading ํ™”๋ฉด์ด ํ•„์š”ํ•˜์ง€ ์•Š์Œ
  return (
    <div className={styles.container}>
      <Head>
        <title>HOME | ํ”„๋ก ํŠธ๋ชฝํ‚ค</title>
        <meta name="description" content="ํ”„๋ก ํŠธ๋ชฝํ‚ค ํ™ˆ ์ž…๋‹ˆ๋‹ค."></meta>
      </Head>
      <>
        <Header as="h3" style={{ paddingTop: 40 }}>
          ๋ฒ ์ŠคํŠธ์ƒํ’ˆ
        </Header>
        <Divider />
        <ItemList list={list.slice(0, 6)} />
        <Header as="h3" style={{ paddingTop: 40 }}>
          ์‹ ์ƒํ’ˆ
        </Header>
        <Divider />
        <ItemList list={list} />
      </>
    </div>
  );
}

export async function getStaticProps() {
  // ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— NEXT_PUBLIC ์„ ๋ถ™ํž ํ•„์š” ์—†์Œ
  const apiUrl = process.env.apiUrl;
  const res = await axios.get(apiUrl);
  const data = res.data;

  return {
    props: {
      list: data,
      name: process.env.name,
    },
  };
}

์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์ ์šฉํ•˜๊ธฐ

๊ธฐ์กด์ฝ”๋“œ

export default function Post() {
  const router = useRouter();
  const [item, setItem] = useState();
  const [isLoading, setIsLoading] = useState(true);

  const { id } = router.query;
  const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;

  function getDate() {
    axios.get(API_URL).then((res) => {
      setItem(res.data);
      setIsLoading(false);
    });
  }

  useEffect(() => {
    if (id && id > 0) {
      getDate();
    }
  }, [id]);

  return (
    <>
      {isLoading ? (
        <div style={{ padding: "300px 0" }} className="ui segment">
          <div className="ui active inverted dimmer">
            <div className="ui text loader">Loading</div>
          </div>
          <p></p>
        </div>
      ) : (
        <Item item={item} />
      )}
    </>
  );
}

์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋” ์ ์šฉ

export default function Post({ item }) {
  return (
    <>
      <Head>
        <title>{item.name}</title>
        <meta name="description" content={item.description}></meta>
      </Head>
      {item && <Item item={item} />}
    </>
  );
}

export async function getServerSideProps(context) {
  const id = context.params.id;
  const apiUrl = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
  const res = await axios.get(apiUrl);
  const data = res.data;

  return {
    props: {
      item: data,
    },
  };
}

๋™์ ๋ผ์šฐํŒ… ์ •์ ์ƒ์„ฑ ์ ์šฉํ•˜๊ธฐ

๊ธฐ์กด์ฝ”๋“œ

export default function Post({ item, name }) {
  return (
    <>
      <Head>
        <title>{item.name}</title>
        <meta name="description" content={item.description}></meta>
      </Head>
      {name} ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค.
      {item && <Item item={item} />}
    </>
  );
}

export async function getServerSideProps(context) {
  const id = context.params.id;
  const apiUrl = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
  const res = await axios.get(apiUrl);
  const data = res.data;

  return {
    props: {
      item: data,
      name: process.env.name,
    },
  };
}

๋™์ ๋ผ์šฐํŠธ ํŒŒ์ผ์— ์ •์ ์ƒ์„ฑ ์ ์šฉ

const Post = ({ item, name }) => {
  return (
    <>
      {item && (
        <>
          <Head>
            <title>{item.name}</title>
            <meta name="description" content={item.description}></meta>
          </Head>
          {name} ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค.
          <Item item={item} />
        </>
      )}
    </>
  );
};

export default Post;

export async function getStaticPaths() {
  return {
    paths: [
      { params: { id: "740" } },
      { params: { id: "730" } },
      { params: { id: "729" } },
    ],
    // false ์„ค์ •์‹œ ๋Œ€์‘๋˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€๋Š” ๋ชจ๋‘ 404Error ์ฒ˜๋ฆฌ
    // true ์„ค์ •์‹œ ๋ชจ๋“  ๋ผ์šฐํŠธํŽ˜์ด์ง€์˜ ์ •์ ํŒŒ์ผ์ด ์ƒ์„ฑ๋จ (์ดˆ๊ธฐ๋นŒ๋“œ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆผ)
    fallback: true,
  };
}

export async function getStaticProps(context) {
  const id = context.params.id;
  const apiUrl = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
  const res = await axios.get(apiUrl);
  const data = res.data;

  return {
    props: {
      item: data,
      name: process.env.name,
    },
  };
}

ํ”„๋กœ๋•์…˜์œผ๋กœ ์‹คํ–‰ํ–ˆ์„๋•Œ ๋นŒ๋“œํŒŒ์ผ์„ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์  htmlํŒŒ์ผ์ด ์ƒ์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฑธ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Reference

https://nextjs.org/docs
์ฝ”๋”ฉ์•™๋งˆ๋‹˜์˜ ์œ ํŠœ๋ธŒ ๊ฐ•์ขŒ

profile
๋ชจ๋ฅด๋Š”๊ฒƒ์€ ๊ทธ๋•Œ๊ทธ๋•Œ ๊ธฐ๋กํ•˜๊ธฐ

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