next.js의 파일 기반 라우팅은 pages 폴더에만 이루어집니다. 기존 리액트 라우팅 작업을 위해, pages 폴더와 components 폴더를 형식적으로 분류하였다면, next.js 에서는 명확히 구분해주어야 할 이유가 생긴 것이죠.
필요한 컴포넌트는 모두 components 폴더에 분할하면서, pages에서는 라우팅 작업을 중점적으로 진행합니다. 각 경로에 필요한 컴포넌트를 페이지에 import 해줍니다. next.js에서 페이지 작업은 이런 식으로 이루어집니다.
기존 react-router의 <Link>
의 작업은 next/Link 가 대신합니다. 변화한 점은 props가 to 에서 href로 변경됩니다.
기존 react-router의 동적 경로 설정작업인 useParams는 next/router의 useRouter가 대체합니다. useRouter의 동적경로는 useRouter.query.[pathName]에 있습니다.
import { useRouter } from "next/router";
// our-domain.com/news
function DetailPage() {
const router = useRouter();
const newsId = router.query.newsId;
// send a request to the backendAPI
// to fetch the news item with newsId
return <h1>The DetailPage Page</h1>;
}
export default DetailPage;
next.js 에서는 상시 고정이 필요한 컴포넌트를 손쉽게 렌더링 하는 것이 가능합니다. 최상위 루트 컴포넌트를 상시 고정 컴포넌트가 감싸게 되면 이후 모든 페이지에서 상시 고정 컴포넌트가 계속 등장하게 됩니다. 최상위 루트 컴포넌트는 _app.js
안에 존재합니다.
import "../styles/globals.css";
import Layout from "../components/layout/Layout";
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;
useEffect() 는 의존성 항목이 변경되는 경우 컴포넌트를 재평가하여 다시 렌더링 하는 함수입니다. 함수형 컴포넌트에서 sideEffect를 최대한 제어하기 위해 설계되었으며, 주로 필요한 비동기 플로우를 처리할 때 많이 쓰이곤 합니다.
pre-rendering은 이러한 컴포넌트를 재평가하는 기능에서 취약점이 나타나게 됩니다.
pre-rendering은 이름처럼 초기에 미리 HTML 구조를 받아 경로를 탐색하며 최적화된 정보를 구할 수 있게 됩니다. 그렇기 때문에 useEffect()를 통해 한번 더 재평가 된 컴포넌트에 대해서도 이전에 useEffect() 가 적용되지 않은 초기의 HTML 구조를 탐색하게 되는 단점이 발생하게 됩니다. 따라서 pre-rendering에도 재평가된 컴포넌트의 HTML 구조가 반영되도록, 미세한 조정이 필요합니다.
static generation은 페이지의 build하는 시점에서 HTML 구조를 생성합니다. 만약 페이지가 외부 데이터에 의존하고 있는 경우, getStaticProps()를 통해 데이터를 완전히 받은 이후, HTML 구조가 생성되도록 하는 흐름을 제어할 수 있게 됩니다. 또한 렌더링 시 useEffect()를 통해 컴포넌트를 재평가하여 두번, 세번 렌더링 할 필요도 없기 때문에 성능적인 면에서도 매우 우수한 기능입니다.
import MeetupList from "../components/meetups/MeetupList";
const DUMMY_MEETUPS = [
{
id: "m1",
title: "A First Meetup",
image:
"https://img.khan.co.kr/news/2020/10/16/2020101601001687000138341.jpg",
address: "Some address 5, 12345 Some City",
description: "This is a first meetup!",
},
{
id: "m2",
title: "A Second Meetup",
image:
"http://digitalchosun.dizzo.com/site/data/img_dir/2022/06/10/2022061080123_0.jpg",
address: "Some address 1, 54321 Some City",
description: "This is a Second meetup!",
},
];
function HomePage(props) {
return <MeetupList meetups={props.meetups} />;
}
export async function getStaticProps() {
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS,
},
};
}
export default HomePage;
하지만 이 기능도 완벽하진 않습니다. 이렇게 하면 단순히 초기 데이터만 HTML 구조에 반영될 뿐, 새롭게 변경되거나 추가되는 데이터까지는 pre-rendering을 통해 알 수가 없습니다. 이러한 문제를 해결하기 위해next.js에서는 revaildate라는 속성을 제공해주고 있습니다. revalidate는 숫자 값을 받는데, 해당 숫자 초 씩 요청 시 서버에서 페이지를 다시 생성하도록 합니다.
import MeetupList from "../components/meetups/MeetupList";
const DUMMY_MEETUPS = [
{
id: "m1",
title: "A First Meetup",
image:
"https://img.khan.co.kr/news/2020/10/16/2020101601001687000138341.jpg",
address: "Some address 5, 12345 Some City",
description: "This is a first meetup!",
},
{
id: "m2",
title: "A Second Meetup",
image:
"http://digitalchosun.dizzo.com/site/data/img_dir/2022/06/10/2022061080123_0.jpg",
address: "Some address 1, 54321 Some City",
description: "This is a Second meetup!",
},
];
function HomePage(props) {
return <MeetupList meetups={props.meetups} />;
}
export async function getStaticProps() {
// fetch data from an API
return {
props: {
meetups: DUMMY_MEETUPS,
},
revalidate : 10
};
}
export default HomePage;
위에서 getStateProps()를 통한 외부 데이터를 반영한 HTML 구조를 생성하는 방법을 알았고, 매 시간마다 업데이트 되는 데이터가 반영된 페이지를 반복하여 생성하는 방법도 알게 되었습니다. 이번에는 데이터 변경 빈도가 매우 높아, 매 요청마다 페이지를 새롭게 생성해내야 하는 경우를 알아보겠습니다.
getServerSideProps()는 getStaticProps()와는 다르게 매 요청마다 페이지르 새롭게 생성합니다.
next.js에서는 되도록 getStaticProps()를 사용하는 것을 권장합니다. 퍼포먼스 상의 이유가 크기 때문입니다. getServerSideProps()는 매 요청마다 페이지를 재생성하기 때문에, 매 요청마다 페이지가 생성되기까지 기다려야 하지만, getStaticProps() 한번 빌드 되면 변경이 있을 때까지, 별도의 설정없이 CDN에 의해 캐싱되어, 매 요청마다 페이지를 생성하는거 보다 훨씬 빠르게 렌더링하는 것이 가능하기 때문입니다.
동적 라우팅을 사용되는 페이지에서, 외부 데이터에 의존하는 경우, 정적으로 렌더링 할 경로를 next.js에게 미리 알려주는데 쓰이는 함수입니다. 해당 함수는 paths 객체를 리턴하는데, 중요한 점은 해당 paths가 라우팅 될 수 있는 모든 경로 값을 전부 객체 안에 가지고 있어야 한다는 것입니다.
next.js는 오로지 path 값에 존재하는 값만을 렌더링 해줍니다. getStaticPath() 에서는 fallback 프로퍼티를 통해 paths가 가진 객체 외의 경로에 대한 처리를 다룰 수 있는데 fallback이 false 인 경우에는, 404 에러를 렌더링 하며, true인 경우에는 paths 객체 데이터 외의 요청도 허가해줍니다.
fallback은 paths 값과 함께 반드시 지정하여 리턴해주어야 합니다!!
import MeetupDetail from "../../components/meetups/MeetupDetail";
function MeetupDetails() {
// const router = useRouter();
return (
<MeetupDetail
id="m1"
title="A First Meetup"
image="https://img.khan.co.kr/news/2020/10/16/2020101601001687000138341.jpg"
address="Some Street 5, Some City"
description="This is a first meetup"
/>
);
}
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
meetupId: "m1",
},
},
{
params: {
meetupId: "m2",
},
},
],
};
}
export async function getStaticProps(context) {
const meetupId = context.params.meetupId;
console.log(meetupId);
return {
props: {
meetupData: {
title: "A First Meetup",
image:
"https://img.khan.co.kr/news/2020/10/16/2020101601001687000138341.jpg",
address: "Some Street 5, Some City",
description: "This is a first meetup",
id: meetupId,
},
},
};
}
export default MeetupDetails;
<meta>
태그는 해당 애플리케이션에 대한 정보를 정의할 때 사용합니다. <meta>
태그를 반드시 설정해줘야 하는 이유는 브라우저 검색엔진이 이를 탐색하는 경우 검색 결과로서 나타내는 요약된 정보에 <meta>
태그 정보가 활용될 가능성이 매우 높기 때문입니다. 유저에게 개발자가 만든 웹사이트에 대한 소개가 검색결과에 나타나게 하고 싶다면, 마케팅적인 요소로서 반드시 처리를 해주는게 좋겠죠??
next.js에서 <head>
태그를 설정하는 방법은 매우 간단합니다. next.js의 Head 기능을 사용하면 됩니다.
import Head from "next/head";
...
...
return (
<>
<Head>
<title>Add a New Meetup</title>
<meta
name="description"
content="Add your own meetups and create amazing networking opportunities."
/>
</Head>
<NewMeetupForm onAddMeetup={addMeetupHandler} />
</>
);