page Router 기준 공부내용
pages 폴더안에 모든것을 정의해야 인식함.
기본 폴더구조
project-root
├── pages
│ ├── index.js // 홈 페이지
│ ├── about.js // About 페이지
│ ├── blog
│ │ ├── index.js // 블로그 메인 페이지
│ │ ├── [postId].js // 블로그 개별 포스트 페이지
│ │ └── latest.js // 최신 블로그 포스트 페이지
│ └── contact.js // Contact 페이지
├── components // 재사용 가능한 컴포넌트들
├── styles // 전역 스타일과 스타일 유틸리티
└── public // 정적 파일들 (이미지, 폰트 등)
폴더이름, 파일이름이 라우팅경로이기 때문에 정해진 Convention에 따라 사용해야함.
pages 디렉토리 내에 생성된 파일 이름은 해당 파일이 나타내는 페이지의 URL 경로와 일치함.
예를 들어, pages/about.js 파일은 /about 경로에 해당하는 페이지가 된다.
pages 디렉토리 내의 서브 디렉토리 구조도 URL 경로에 영향을 미친다.
예를 들어, pages/blog/[slug].js 파일은 /blog/:slug 경로에 해당하는 페이지가 됨. slug는 경로 매개변수를 나타낸다.
각 폴더마다 index.tsx가 존재해야함(홈페이지)
page directory 최상단에 위치
app에는 공통 컴포넌트들을 정의함.
- _app .js .jsx .tsx 맞춤형 앱
- _document .js .jsx .tsx 맞춤 문서
- _error .js .jsx .tsx 사용자 정의 오류 페이지
- 404 .js .jsx .tsx 404 오류 페이지 => 최상단에 정의해야 인식함.
- 500 .js .jsx .tsx 500 오류 페이지
- 폴더 규칙
[folder]/index .js .jsx .tsx 동적 경로 세그먼트
[...folder]/index .js .jsx .tsx 포괄 경로 구간
[[...folder]]/index .js .jsx .tsx 선택적 포괄 경로 구간
- 파일 규칙
[file] .js .jsx .tsx 동적 경로 세그먼트
[...file] .js .jsx .tsx 포괄 경로 구간
[[...file]] .js .jsx .tsx 선택적 포괄 경로 구간
// pages/blog/[[...slug]].js
import { useRouter } from 'next/router';
const BlogPost = () => {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>Blog Post: {slug.join('/')}</h1>
</div>
);
};
export default BlogPost;
포괄경로는 선택적 포괄경로와 비슷하지만 경로 요소가 반드시 존재해야 한다는 차이가 있음.
[...slug].js 파일은 /categories/*와 같은 경로에 매핑됨
// pages/categories/[...slug].js
import { useRouter } from 'next/router';
const Category = () => {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>Category: {slug.join('/')}</h1>
</div>
);
};
export default Category;
import Layout from "@/components/Layout";
import "@/styles/globals.css";
import type { AppProps } from "next/app";
/*앱 */
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
만약 App에서 페이지가 공용 layout과 다른 layout을 갖거나, Nested Layout이 필요한 구조라면, page 계층에 getLayout 속성을 따로 정의 가능함
먼저 다른 layout을 정의할 페이지 계층에 getLayout 속성을 정의함.
getLayout은 React.Element를 파라미터로 받아서 다른 Layout 구조를 감싸서 return함.
// /todolist/index
TodoList.getLayout = function getLayout(page: ReactElement) {
return (
<Layout2>
<div className="text-6xl">Nested Layout is Applied</div>
{page}
</Layout2>
);
};
_app.tsx에서 layout에 따라 getLayout 프로퍼티가 있는 구조라면, currying을 이용해 React Element를 감싸서 반환함.
먼저 NextPageWithLayout라는 새로운 type을 정의함. 이는 NextPage type에 getLayout Prop을 확장한 형태임. optional property로 지정해서 getLayout 프로퍼티가 없는 경우도 고려함.
이를 이용해 AppPropsWithLayout을 정의 => &연산자를 통해 Component 프로퍼티의 값을 덮어씌움
TypeScript에서 & 연산자를 사용한 타입 확장은 두 개 이상의 타입을 병합하여 새로운 타입을 생성하는 기능을 제공함. 이 연산자를 사용하면 여러 타입의 프로퍼티와 메서드를 하나의 타입으로 합칠 수 있음.
또한, & 연산자를 이용해 두 개의 타입을 병합할 때, 같은 프로퍼티가 존재하는 경우 후술된 타입의 프로퍼티가 우선적으로 사용됨.
null 병합 연산자 사용 => getLayout이 있다면, page계층에서 정의한 함수가 없다면 받은 페이지를 받아서 반환하는 커링함수를 반환.
getLayout 유무에 따라 다른 Layout을 적용함.
// _app.tsx
type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
}; // ?로 optional로 지정함.
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
}; //
export default function App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page);
//null병합 연산자 => null or undefined가 아니라면 a이고 나머지는 b
console.log("getLayout", getLayout);
/*없다면 page를 그대로 반환하는 함수가 반환됨. */
/* getLayout이 있다면 => Curing 형식으로 Page계층에서 정의한 함수가 반환됨. ReactElement를 감싸서 반환 */
if (!Component.getLayout) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
return getLayout(<Component {...pageProps} />);
}
ref) Next.js 공식문서 : https://nextjs.org/docs/pages/building-your-application/routing