NextJS 시작하기 - 노마드코더 정리

dev_hee·2022년 3월 28일
2

NextJS

목록 보기
1/2
post-thumbnail

NextJS 시작하기

✏️ Next App 설치

npx create-next-app@latest
npm run dev
  • node 버전이 14 이하면 next app이 동작하지 않음.
    • M1 노트북의 경우엔 node_modules를 다시설치해야할 수도 있음...

✏️ 프레임워크 VS 라이브러리

라이브러리

  • 개발자가 라이브러리를 불러와서 사용함
  • 개발자가 사용하고 싶을 때 라이브러리를 사용하면 됨
  • 폴더 구조, 언제 어떤 파일을 불러올지 개발자가 결정함
  • 즉, 자유도가 높음

프레임워크

  • 프레임워크가 개발자의 코드를 불러옴
  • 개발자가 적잘한 위치에 코드를 작성하면 코드를 불러와서 동작하게 함
  • 특정한 규칙을 따라서 특정한 것을 해야함
  • Pages 안에서 뭔가 만드는 것만 할 수 있음.
  • 코드를 어떤 곳에 넣으면, 프레임워크가 그 코드를 불러와 사용함!
  • 즉, 자유도가 낮음

✏️ /pages

  • 리액트 컴포넌트를 pages 폴더에 넣어두기만 하면, 파일의 이름의 url를 만들어 라우팅을 해줌
  • 컴포넌트의 이름은 상관없이, 파일의 이름이 url이 됨.
  • default로 export해야함.
  • 404 page도 제공됨. 이는 커스터마이징 할 수 있음.
  • 파일명이 jsx일 필요 없음. React 를 import 할 필요 없음.

✏️ Static Pre Rendering

SPA (only React, CSR)

  • 브라우저가 UI를 그리는 모든 작업 JS를 통해서 함.
  • 따라서 맨 처음에는 비어있는 html 을 받게됨. 이후에 브라우저가 자바스크립트를 로드한 후 실행해서 화면을 그리게 됨.
    • 만약 브라우저에서 자바스크립트를 활성화 하지 않았을 때 noscript 가 있었다면 아래만 보여지게 됨

      <noscript>자바스크립트가 활성화 되지 않았습니다.</noscript>
  • 브라우저가 자바스크립트를 통해서 UI를 그려내는 작업은 비용이 크고 오래걸리는 작업임.
    • 3G 같이 느린 네트워크에서는 사용자는 처음에 빈 화면을 보게 됨

Static Pre Rendering

  • 프론트 서버에서 react js 를 실행해서 html 을 완성한 후 보내기 때문에 빈화면이 아닌 채워진 화면을 보게됨.
    • 즉 페이지가 초기상태로 Pre Render 되어서 전달됨

High Dration

  • react js 가 클라이언트에서 동작하는 것을 말함
  • JS 가 브라우저에서 실행되며 동적으로 UI를 그려낼 수 있음
  • 브라우저가 자바스크립트를 비활성화하면 초기 html은 그려져 있지만, 동적으로 동작하지는 않음

✏️ Routing

NextJS 에서 라우팅할 때는 Link 를 사용해야 함. 그래야 CSR를 할 수 있음.

// components/NavBar.js
import Link from "next/link";

export default function NavBar() {
  return (
    <nav>
      <Link href="/"> 
        <a>HOME</a>
      </Link>
      <Link href="/about">
        <a>ABOUT</a>
      </Link>
    </nav>
  );
}
  • aLink 로 감싸주는 이유는 style 이나 class 처럼 다른 어트리뷰트를 사용할 수 없기 때문임.

    • Linkhref 만을 위한 컴포넌트임. 그 외의 것은 a 에게 전달하면 됨.
  • href 에 객체를 전달하면 pathname 과 query를 지정할 수 있음.

<Link
  href={{
   pathname: `/movies/${movie.id}`,
	 query: {
		 title: movie.title,
	 },
  }}
	as={`/movies/${movie.id}`}
>

useRouter Hook

  • next js에 있는 훅인 useRouter
    • location 정보를 얻을 수 있음.
    • pathname 으로 현재 url을 알 수 잇음
import Link from "next/link";
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";

export default function NavBar() {
  const router = useRouter();
  console.log(router);
  return (
    <nav className="nav">
      <Link href="/">
        <a style={{ color: router.pathname === "/" ? "red" : "black" }}>HOME</a>
      </Link>
      <Link href="/about">
        <a style={{ color: router.pathname === "/about" ? "red" : "black" }}>
          ABOUT
        </a>
      </Link>
    </nav>
  );
}
  • console 결과

  • 렌더링 결과

✏️ CSS Modules

  • 클래스 이름에 해쉬값을 더해서 네이밍 스페이스를 모듈 안으로 한정 지음.
.nav {
  display: flex;
  justify-content: space-between;
  background-color: tomato;
}
  • styles.nav 로 css 파일에 있는 클래스 이름을 가져다가 사용하면 됨
import Link from "next/link";
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";

export default function NavBar() {
  const router = useRouter();
  console.log(router);
  return (
    <nav className={styles.nav}>
      <Link href="/">
        <a>HOME</a>
      </Link>
      <Link href="/about">
        <a>ABOUT</a>
      </Link>
    </nav>
  • 만약 className 에 여러개의 클래스를 넣고싶다면, 띄어쓰기를 포함한 문자열로 작성해야함.
    				<a
              className={`${styles.link} ${
                router.pathname === "/" ? styles.active : ""
              }`}
            >
              HOME
            </a>
    
    // ....
    
    				<a
              className={[
                styles.link,
                router.pathname === "/about" ? styles.active : "",
              ].join("")}
            >
              ABOUT
            </a>

✏️ Styles JSX

<style jsx>

  • props 로 받은 변수도 사용할 수 있음. color: ${props.color}
  • 컴포넌트 단위로 네이밍 스페이스가 지정됨 active 처럼 클래스를 사용하더라도 네이밍 스페이스가 한정됨
import Link from "next/link";
import { useRouter } from "next/router";

export default function NavBar() {
  const router = useRouter();
  console.log(router);
  return (
    <nav>
      <Link href="/">
        <a>HOME</a>
      </Link>
      <Link href="/about">
        <a>ABOUT</a>
      </Link>
      <style jsx>{`
        nav {
          background-color: tomato;
        }
        a {
          text-decoration: none;
        }
      `}</style>
    </nav>
  );
}

✏️ Custom App (_app.js)

  • NavBar 이나 글로벌 스타일 처럼 모든 페이지에서 공통적으로 사용하는 것들을 _app.js 파일에서 한번에 적용할 수 있음
  • NextJS 가 _app.js 를 불러와서 실행할 것임
    • about 페이지를 렌더링 하기 위해서 about 컴포넌트를 _app.js에서 내보내진 컴포넌트의 props({ Component, pageProps })로 보내준 다음 합쳐진 것을 그려줌

글로벌 스타일링

  • <style jsx global> 로 글로벌 스타일을 적용할 수 있음.
// pages/_app.js
import NavBar f플
  • globals.css 처럼 css 파일은 index.js 에서 import 할 수 없음. 하지만 _app.js 에서는 가능하다.
// pages/_app.js
import "../styles/globals.css";

✏️ Layout 컴포넌트

  • _app.js 에 넣을 것이 많기 때문에, _app 의 크기가 커지는 것을 보통 지양함
  • 그래서 Layout 컴포넌트를 만들어서 _app.js 에서 사용하도록 한다. (Layout 자체를 app에서 구현 안한다는 말)
// components/Layout
import NavBar from "./NavBar";

export default function Layout({ children }) {
  return (
    <>
      <NavBar />
      {children}
    </>
  );
}
// pages/_app
import Layout from "../components/Layout";
import "../styles/globals.css";

export default function App({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

✏️ Head

  • 리액트에서는 react-helmet 으로 구현했던 head 를 간단히 Head 컴포넌트를 사용해서 구현 가능
// components/Seo.js
import Head from "next/head";
import { useRouter } from "next/router";
import { TITLE } from "./const";

export default function Seo() {
  const { pathname } = useRouter();
  return (
    <Head>
      <title>{TITLE[pathname]} | Next</title>
    </Head>
  );
}
export const TITLE = {
  "/": "Home",
  "/about": "About",
};
  • 라우팅 정보에 대해서 상수 객체로 관리하면 편함

→ 근데 개발을 진행하다보니, TITLE 객체를 계속 추가해야하고, 영화 title 과같이 완전 램덤인 값들을 처리할려고 하다보니 404 페이지 띄우기 애매해지는 문제가 발생함. 그냥 그때그때 SEO 컴포넌트를 추가하는게 나은거 같음.

✏️ Image

public 파일

  • public에 있는 파일들을 쉽게 import할 수 있다.
  • public이 / 경로가 되어서, /photo.png 이렇게 경로를 지정하면 된다.

Image

https://velog.io/@pyo-sh/React-NextJS-에서-이미지-import-하기

  1. Next.js의 Image 태그는 최신 웹용으로 확장된 next/imageHTML img요소이다.
  2. 브라우저에서 지원하는 경우 JPEG보다 약 30 % 작은 WebP와 같은 최신 이미지 형식으로 이미지를 자동으로 제공한다.(필요에 따라 이미지 최적화)
  3. 뷰포트를 스크롤하는 동안 특정 임계 값에 도달 한 경우에만 페이지 내부의 이미지를 지연로드한다.
  4. 동적으로 사용할 다양한 및 사용자 정의 해상도에 대해 다른 이미지 크기를 지정할 수 있다.
  5. 사진의 품질을 75 %로 설정된 낮은 임계 값으로 자동 변경한다(각 호출에 대해 변경 가능)

✏️ Fetching Data

The Movie Database (TMDB)

  • API 키
785c2b2480182aa560d1a17dd4391c02
  • document

API Docs

API로 데이터 받아오기

	useEffect(() => {
    (async () => {
      const { results } = (
        await fetch(`${BASE_URL}/3/movie/popular?api_key=${API_KEY}`)
      ).json();
      setMovies(results);
      console.log(results);
    })();
  }, []);

✏️ Redirect and Rewrite

API key 를 숨겨야 하는 이유

  • API 요청할 때, 네트워크에서 API key 가 쉽게 노출된다.

Redirect

API키를 숨기지 않는 방법

유저를 리다이렉트하며 url이 변경된다.

  • next.config.js
    • redirects 함수는 비동기적으로 동작하므로 async를 작성한다.

    • redirecting에 대한 객체를 배열에 담아 반환한다.

      /** @type {import('next').NextConfig} */
      const nextConfig = {
        reactStrictMode: true,
        async redirects() {
          return [
            {
              source: "/old-blog/:path*", // 사용자기 여기로 접근하면 
              destination: "/new-blog/:path*", // 여기로 redirect 시켜준다.
              permanent: false,
            },
          ];
        },
      };
      
      module.exports = nextConfig;

Rewirtes

API를 숨기는 방법

유저를 리다이렉트 하지만 url이 변경되지 않음.

  • url 프록시 역할을 하며 destination 경로를 masking 하여 사용자가 사이트에서 위치를 변경하지 않은 것 처럼 보이게함
  • 아래처럼 요청 url이 변경되지 않음!
/** @type {import('next').NextConfig} */

const API_KEY = "785c2b2480182aa560d1a17dd4391c02"; // process.env.API_KEY

const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: "/api/movies",
        destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
      },
    ];
  },
};

module.exports = nextConfig;
  • .env 에 API_KEY를 저장하면 git 에 API 키가 올라가는 것을 막을 수 있음. (.gitignore.env 를 추가해야함)
    // .env
    API_KEY=785c2b2480182aa560d1a17dd4391c02

✏️ Server Side Rendering

getServerSideProps

import { useEffect, useState } from "react";
import Seo from "../components/Seo";

// SSR - getServerSideProps
export async function getServerSideProps() {
  const { results } = await (
    await fetch(`http://localhost:3000/api/movies`)
  ).json();
  return {
    props: {
      results,
    },
  };
}

export default function Home({ results }) {
  return (
    <div className="container">
      <Seo title="Home" />
      {!results && <h4>Loading...</h4>}
      {results?.map((movie) => (
        <div className="movie" key={movie.id}>
          <img src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
          <h4>{movie.original_title}</h4>
        </div>
      ))}
    </div>
  );
}
  • HTML의 head 안에 서버에서 미리 받아놓은 API 응답 정보들을 담아둠. 이걸 컴포넌트는 props 로 사용할 수 있음!

✏️ Dynamic Routes

/movies/all

/movies/:id

대괄호를 사용해서 다이나믹 라우팅을 해줄 수 있음.

  • useRouter 를 통해서 query 의 id를 찾을 수 있음.

✏️ Router Hook

Link를 사용해서 라우팅

			{results?.map((movie) => (
        <div className="movie" key={movie.id}>
          <img src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
          <Link href={`/movies/${movie.id}`}>
            <a>
              <h4>{movie.original_title}</h4>
            </a>
          </Link>
        </div>
      ))}

router.push

onclick 되었을 때 url 를 변경하도록 push 함수를 호출할 수 있음

	const router = useRouter();
  const onClick = (id) => {
    router.push(`/movies/${id}`);
  };

// ...

		<div onClick={() => onClick(movie.id)} className="movie" key={movie.id}>
				// ...
  • push 함수의 매개변수로 넘겨주는 객체에서 query 를 사용해 end point를 직접 작성해줄 수 있음
	const router = useRouter();
  const onClick = (id) => {
    router.push({
      pathname: `/movies/${id}`,
      query: {
        id,
        title: "hello",
      },
    });
  };
  • 변경된 url

pathname?queryKey=queryvalue

  • query 부분을 as 를 사용해서 숨길 수 있음.
	const router = useRouter();
  const onClick = (id) => {
    router.push(
      {
        pathname: `/movies/${id}`,
        query: {
          id,
          title: "hello",
        },
      },
      `/movies/${id}`
    );
  };
  • 유저가 보는 url 에는 query가 없음
  • 하지만 useRouter 를 사용하면 query를 꺼내올 수 있다! 이것도 Link 에서 동일하게 사용 가능하다.

  • next.config.js 에 api 요청 url을 /api/movies/:id로 우회하도록 추가
/** @type {import('next').NextConfig} */

const API_KEY = process.env.API_KEY;

const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: "/api/movies",
        destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
      },
      {
        source: "/api/movies/:id",
        destination: `https://api.themoviedb.org/3/movie/:id?api_key=${API_KEY}`,
      },
    ];
  },
};

module.exports = nextConfig;
  • /api/movies/:id 로 요청을 보내면 정상적으로 백엔드 api에서 받은 응답을 확인할 수 있음

✏️ Catch All URL

[...]

  • [...] 를 통해서 movies 아래 모든 end point 들을 여기로 라우팅 할 수 있음

  • Link 의 href 를 아래와 같이 수정한 다음
    <Link href={`/movies/${movie.original_title}/${movie.id}`}>
  • useRouter 를 사용해 params 배열을 배열 디스트럭처링 하면 사용할 수 있다.
import { useRouter } from "next/router";

export default function Detail() {
  const router = useRouter();
  const [title, id] = router.query.params;
  return (
    <div>
      <h2>{title || "loading..."}</h2>
    </div>
  );
}
// url = "/movies/title/id"

queryparams 는 배열임

  • 하지만 서버에서는 아직 params 가 배열이 아니므로, 시크릿 모드로 캐시가 모두 사라진 경우에 query가 담긴 url 로 접근하려고 하면 오류가 발생한다.
    • 따라서 [] 를 아래처럼 추가해주어야 에러가 나지 않음

    • 하지만 이건 CSR 만 해준 것임

      const router = useRouter();
      const [title, id] = router.query.params || [];

getServerSideProps

  • 첫번째 인수 ctx 에는 queryparams 정보들이 담겨져 있음
  • 이걸 사용해서 pre-rendering 해주면 됨
import { useRouter } from "next/router";

export default function Detail() {
  const router = useRouter();
  const [title, id] = router.query.params || [];
  return (
    <div>
      <h2>{title || "loading..."}</h2>
    </div>
  );
}
// url = "/movies/title/id"
export async function getServerSideProps(ctx) {
  console.log(ctx); // query, params 정보들

  return {
    props: {},
  };
}

  • pre-rendering
export default function Detail({ params }) {
  const [title, id] = params || [];
  return (
    <div>
      <h2>{title || "loading..."}</h2>
    </div>
  );
}
// url = "/movies/title/id"
export async function getServerSideProps({ params: { params } }) {
  return {
    props: { params },
  };
}

  • SEO (title)
import Head from "next/head";
import { useRouter } from "next/router";
import { TITLE } from "./const";

export default function Seo() {
  const { pathname, query } = useRouter();
  return (
    <Head>
      <title>
        {TITLE[pathname] ? TITLE[pathname] : query.params[0]} | Next
      </title>
    </Head>
  );
}

✏️ 404 Page

폴더구조는 아래와 같다.

export default function NotFound() {
  return "404 페이지 입니다";
}
profile
🎨그림을 좋아하는 FE 개발자👩🏻‍💻

0개의 댓글