(해당 포스팅은 <The Net Ninja>
채널의 Next.js Tutorial의 강의노트 입니다. 링크를 통해 강의를 보실 수 있습니다.)
NextJS는 Server Side Rendering과 Static Site Generation을 지원하는 프레임워크이다. 다시 말해 Pre-render되는 리액트 웹사이트를 만들기 위한 프레임워크라고 할 수 있다.
Client Side Rendering 방식을 따르는 리액트의 경우, 기본 html 파일만을 서버로 부터 내려받고, js 스크립트를 통해 DOM을 구성해 다시 html에 그려준다. 브라우저에서 렌더링을 실행한다는 것이다! 그러나 React를 사용하다보면 때때로 우리는 이런 생각을 하게 된다. "나는 굳이 사용자와의 실시간성을 보장할 필요가 없는데도 CSR을 사용해야 하는 걸까?", "많은 양의 비디오나 이미지들은 컴퓨팅파워가 브라우저에 비해 뛰어난 서버에서 미리 처리해 html 파일을 내려주면 좋을텐데!" 하는 고민들 말이다.
NextJS와 함께 라면 html이 미리 렌더링된 상태로 브라우저로 내려오게 된다.
이러한 Pre-rendering은 퍼포먼스를 개선과 SEO 최적화에 유리하다.
npx create-next-app [프로젝트 이름]
create-react-app
과 유사하게 위와 같은 명령어로 프로젝트를 시작해준다.
위와 같은 파일 구조를 확인할 수 있다!
(디테일한 부분에는 차이가 있을 수 있습니다.)
NextJS에서는 페이지 폴더에 생성되는 파일이 각각 하나의 페이지를 구성하게 된다.
이 때,_app.js
는 루트 컴포넌트와 같다고 할 수 있을 것이다.
the file name and location of each component is tied to the route for that particular page
예를 들어, pages 폴더 내에 About.js
라는 파일을 생성하면, /about
으로 해당 페이지가 라우팅된다.
만일 /ninja/test
와 같이 라우팅을 하고 싶다면, pages/ninja/test.js
와 같이 폴더 및 파일 구조를 구성해주면 된다. 더 나아가 /ninja/test
와 동시에 /ninja
를 사용하고 싶다면 해당 ninja 폴더 내부에 index.js
파일을 생성해주고 내용을 작성해주면 된다!
모쪼록 pages 폴더에 들어있는 파일 및 폴더 구조에 따라서 라우팅이 자동으로 생성된다는 점을 기억하면 되겠다!
여러 페이지에 재사용되는 컴포넌트를 구성해주고 싶다면, 리액트에서와 동일하게 사용해주면 된다!
루트 디렉토리에 components
와 같은 이름의 폴더를 만들어주고 해당 폴더 내부에 필요한 컴포넌트들을 작성해준다.
예를 들어 각 페이지에 삽입되는 Nav
바와 Footer
를 생성해줄 수 있을 것이다.
const Footer = () => {
return <footer>Copyright 2021 Ninja List</footer>;
};
export default Footer;
우리는 기존에 react-router
의 <Link>
를 활용했던 것처럼, NextJS가 제공해주는 Link
를 활용해, 페이지 간의 이동을 시도할 것이다.
import Link from "next/link";
const Navbar = () => {
return (
<nav>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/about">
<a>About</a>
</Link>
<Link href="/ninjas">
<a>Ninja Listing</a>
</Link>
</nav>
);
};
export default Navbar;
분명 NextJS는 SSR 방식이라고 했던 것 같은데 왜 페이지 이동 시에 네트워크 탭에 새로운 html 파일을 받아오는 내역이 보이지 않는가...!?
위에서 Navbar
와 Footer
를 생성해주었다.
우리는 이 두 컴포넌트를 일일이 각 페이지에 삽입해주는 것보다 더 효율적인 방안을 갖고 있다.
바로 Layout
컴포넌트를 만들어주고 이것을 _app.js에 추가해주는 것이다!
방식은 다음과 같다.
// Layout.js
import Footer from "./Footer";
import Navbar from "./Navbar";
const Layout = ({ children }) => {
return (
<div className="content">
<Navbar />
{children}
<Footer />
</div>
);
};
export default Layout;
import Layout from "../components/Layout";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;
Custom App과 관련한 NextJS 공식문서를 살펴보면 조금 더 디테일하게 이 원리를 이해해볼 수 있다.
먼저 MyApp이 프롭스로 넘겨받고 있는 Component
는 활성화된 페이지를 의미한다. 만일 현재 유저가 머무른 페이지가 Home
이라면 위 코드에 작성된 Component
역시 Homepage
가 되는 것이다. 이때 해당 컴포넌트로 내려준 Props는 페이지에서 전부 내려받을 수 있다.
pageProps
의 경우 공식문서는 이를 "데이터 페칭 메소드에 의해 페이지에 미리 로드된 초기 props"라고 소개한다. 만일 초기에 설정해준 프롭이 없다면 빈 객체를 내려받게 된다.
만일 직접 NextJS로 프로젝트를 진행해야 한다면, 스타일링 라이브러리를 쓰겠지만!
우선 기본적인 CSS를 어떻게 NextJS에서 다루는지 알아보도록 하자.
우선 위와 같이 styles
폴더가 구성된다.
각 파일 내부에는 기존 바닐라 자바스크립트, 혹은 리액트를 다룰 때와 동일하게 적용해줄 스타일에 해당되는 CSS 내용을 작성해준다.
이를 사용하는데 있어 약간의 차이가 있는데!
위와 같이 각 컴포넌트, 혹은 페이지에서 임포트해서 클래스 네임을 지정해준다!
각 컴포넌트/페이지 별 스타일 시트를 만들어준다고 생각하면 좋을 것 같다.
게다가 전체 페이지 기준으로 보았을 때 클래스명이 중복되는 것을 방지하기 위해서 실제 브라우저 콘솔에서 엘리먼트의 클래스네임을 확인하면, 스타일드컴포넌트를 사용했을 때와 같이 임의의 코드가 클래스네임 뒤에 더 붙어있는 것을 확인할 수 있다.
모쪼록 style을 적용해줄 때는 컴포넌트.module.css
라는 이름으로 파일을 생성해주고 내부에 CSS파일을 작서앟ㄴ 후에 해당 파일을 컴포넌트에서 임포트해서 클래스네임을 변수로 지정해준다는 것만 기억하면 될 듯 싶다.
pages
폴더 내부에 404.js
파일을 만들어준다.
이때, 만일 자동으로 리다이렉트를 해주고 싶다면, useRouter를 활용하자!
import Link from "next/link";
import { useEffect } from "react";
import { useRouter } from "next/router";
const NotFount = () => {
const router = useRouter();
useEffect(() => {
setTimeout(() => {
// router.go(1);
router.push("/");
}, 3000);
}, []);
return (
<div className="not-found">
<h1>Ooooops...</h1>
<h2>That page cannot be found.</h2>
<p>
Go back to the{" "}
<Link href="/">
<a>Homepage</a>
</Link>
</p>
</div>
);
};
export default NotFount;
router
의 go
메서드를 활용하면 넘겨준 인자(1/-1)에 따라, 앞으로 가기와 뒤로 가기가 구현되고, push
메서드를 활용하면 특정 페이지로 이동시킬 수 있다.
정적데이터들을 활용할 때, 예를 들어 이미지 파일을 사용할 때는 NextJS가 제공하는 Image 태그를 활용하면 좋다. 자동으로 레이지 로딩을 적용해준다.
import Image from "next/image";
<Image src="/logo.png" width={128} height={77} />
위와 같이 적용해주면 된다!
또한, Meta 태그를 활용하면 메타데이터를 쉽게 다룰 수 있고 탭바에 출력되는 내용을 수정해줄 때도 유용하다.
import Head from "next/head";
<Head>
<title>Ninja List | About</title>
<meta name="keywords" content="ninjas" />
</Head>
위와 같이 활용할 수 있다! 페이지 이동시마다 타이틀이 변경된다.
NextJS에서는 useEffect와 같은 훅을 사용해 데이터를 페칭하지 않는다.
NextJS는 프리렌더링을 하며, 그렇기 때문에 데이터 페칭을 위해서 NextJS가 제공하는 특별한 함수를 사용하도록 한다.
getStaticProps를 사용할 것이다.
getStaticProps는 정적생성 방식으로 빌드 시에 데이터를 가져와 한 번 빌드되고 나면 정적으로 움직이지 않는다. 기본적으로 빌드시에 처음 데이터를 가져오는 것이라고 생각하면 좋다.
(getServerSideProps를 활용하면 페이지가 요청될 때마다 데이터가 재요청된다. 페이지를 이동할 때마다 새로 데이터를 불러오기 때문에 속도는 느려지지만 동적인 구성이 가능해진다.)
import styles from "../../styles/Ninjas.module.css";
import Link from "next/link";
export const getStaticProps = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
return {
props: { ninjas: data },
};
};
const Ninjas = ({ ninjas }) => {
return (
<div>
<h1>All Ninjas</h1>
{ninjas.map((ninja) => (
<Link href={"/ninjas/" + ninja.id} key={ninja.id}>
<a className={styles.single}>
<h3>{ninja.name}</h3>
</a>
</Link>
))}
</div>
);
};
export default Ninjas;
데이터 페칭과 관련한 더 자세한 부분은 공식문서를 참고하자!
페이지가 동적 경로를 갖고 있다면 우리는 아래와 같은 방법을 사용한다.
[id]
와 같이 대괄호를 사용해 페이지를 구성해주도록 한다.
그리고 파일 내부에서는 getStaticPaths를 사용한다.
만일 우리가 동적 경로를 사용하는 페이지에서 getStaticPaths를 사용하면 명시해놓은 모든 경로를 NextJS가 정적으로 사전 렌더한다.
export const getStaticPaths = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch("https://jsonplaceholder.typicode.com/users/" + id);
const data = await res.json();
return {
props: { ninja: data },
};
};
const Details = ({ ninja }) => {
return (
<div>
<h1>{ninja.name}</h1>
<p>{ninja.email}</p>
<p>{ninja.website}</p>
<p>{ninja.address.city}</p>
</div>
);
};
export default Details;
실제로 이렇게 작성한 후에 npm run build
를 통해 빌드해주고 나서,
.next
내부에서 빌드된 결과를 살펴보면!
이와 같이 확인할 수 있다!
이럴 경우 만약 미리 예측하지 못하는 검색어 같은 것들은 어떻게 프리렌더링을 하지?
라는 고민이 들긴하지만, 아마 그런 부분은 CSR로 구현하겠거니...
그리고 이런식으로 프리렌더링 해줘야 하는 html이 엄~~청 많은 경우도...