[Next.js][TypeScript] 기초 이해하기
안녕하세요!
이번 게시글에서 다룰 내용의 범위를 고민하다가, 위 커밋을 진행하면서 있었던 일을 작성해보기로 결정했습니다. (개발 진행하면서 바로바로 작성했으면 이런 고민을 하지 않아도 됐을 것 같은데)
React와 JavaScript를 이용한 프로젝트 경험은 몇 번 있었지만, Next.js와 TypeScript는 새로운 만남이어서 버벅거린 부분이 있습니다. 그렇게 버벅거리면서 알게 된 내용을 주로 기록해두려고 합니다. 그 내용은 다음 목록과 같습니다.
- [Next.js] _app.tsx, _document.tsx
- [Next.js] Pages Router vs. App Router
- [Next.js] 컴포넌트 type 지정
1. [Next.js] _app.tsx, _document.tsx
`src/pages` 디렉토리에 있는 이 파일들이 궁금해서 조사해 봤습니다.
두 파일은 각각 중요한 역할을 하는 파일들로, 앱의 구조와 렌더링 방식을 제어하는 데 사용되는 server only file 입니다. 따라서, 해당 파일들 내에서는 Next.js 서버 로직에 사용되는 파일이기 때문에 client에서 사용하는 로직(이벤트 리스너, Window, DOM 등)은 사용이 불가합니다.
Next.js의 커스터마이징을 위한 기본적인 설정을 제공하며, 앱의 전체적인 레이아웃과 HTML 문서 구조를 수정할 수 있도록 해줍니다.
(1) _app.tsx
- 위치: `src/pages/_app.tsx`
- 역할: Next.js의 앱 초기화를 담당하는 컴포넌트 (앱의 모든 페이지에 공통된 레이아웃, 상태, 스타일, 데이터 제공 등을 설정할 때 사용)
- 모든 페이지에서 공통적으로 사용되는 레이아웃 or 상태 관리 (Redux, Context API 등)
- 앱의 루트 컴포넌트로 작용
- `Component`: 현재 페이지 컴포넌트를 렌더링 (라우팅되는 페이지마다 다른 컴포넌트가 이 자리에 렌더링 되는 것)
- `pageProps`: 각 페이지에서 `getServerSideProps`, `getStaticProps`, `getInitialProps`를 통해 전달된 props가 전달되는 곳
(2) _document.tsx
- 위치: `src/pages/_document.tsx`
- 역할: Next.js 앱의 HTML 문서 구조를 커스터마이즈
- 페이지의 html과 body 태그 수정 가능
- 메타 태그, 글꼴, 외부 스크립 등 삽입..
- 다크모드, 폰트 로딩 등 처치..
- 여기서 작성된 내용은 서버 사이드 렌더링 시에만 실행되며, 클라이언트에서는 실행되지 않음
- 페이지의 html과 body 태그 수정 가능
- `Html`: HMTL 문서의 루트 요소
- `Head`: <head> 태그 안에 들어가는 내용을 설정하는 공간 (ex: 폰트 링크, 메타 태그, 외부 스크립트 등
- `Main`: 실제 페이지가 렌더링되는 위치
- `NextScript`: Next.js에서 자동으로 삽입해야 하는 스크립트들이 (ex: next.js 빌드에 필요한 스크립트 파일들)
웹 페이지의 메타 태그를 적절히 설정하는 것이 SEO에 도움이 된다고 들었습니다. `_document.tsx`가 SSR시에만 실행된다는 것과 연계되는 것 같네요!!
2. [Next.js] Pages Router vs. App Router
Next.js에서는 두 가지 주요 라우팅 방식을 제공하고 있었는데, 저는 이것도 새로웠어서 기록을 남깁니다. 편리하고도 유용해 보이는 매커니즘 입니다.
(1) Pages Router
- Next.js의 초기 버전부터 존재하던 라우팅 방식
- 파일 기반 라우팅:
- `pages/` 디렉토리 내부의 파일 이름이 라우트 경로로 매핑
- ex: `pages/index.js` → `/`, `pages/about.js` → `/about`
- 전통적인 디렉토리 구조 (pages 디렉토리 구조)
- 간단한 프로젝트에 적합하며, 설정이 직관적
- 중첩 레이아웃 구현 복잡
(2) App Router
- 더 모던한 기능과 React의 새로운 기능을 활용하기 위해 도입
- 디렉토리 기반 라우팅:
- `app/` 디렉토리를 사용하며, 디렉토리 구조가 URL 경로로 매핑
- ex: `app/page.js` → `/`, `app/about/page.js` → `/about`
- 더 세부적으로 분리되는 디렉토리 구조 (레이아웃, 페이지, API 등)
- 중첩 레이아웃 기본 제공
- `layout.js` 파일로 각 경로에 대해 쉽게 레이아웃 공유 가능
- 페이지 간 전환 시 레이아웃이 유지됨
중첩 레이아웃은 코드 재사용성이 높고(이름부터 그래보임), 렌더링 시 최적화가 이루어지며(페이지 전환 시 공통 레이아웃은 리렌더링 되지 않음!! 똑똑함), 유연성이 좋아(상위 레이아웃과 하위 레이아웃을 분리하다보니, 세부적인 디자인 구현에 용이) 복잡한 애플리케이션(블로그, 쇼핑몰, 대시보드 등)에서 특히 유용하다고 합니다.
이번 Web 에어 하키 프로젝트에서는 전통적인 맛을 보기 위해(?) Pages Route 기반으로 진행했습니다. App Router를 써보면서 복잡한 UI를 구현해보는 연습도 해보고 싶네요.
① Web 에어 하키 - Pages Router 적용 예시
디렉토리 구조가 이런식이라면
이렇게 생긴 URL로 라우팅된다!
`RoomItem`을 클릭하면 `room/[id]`로 페이지 이동시킨다는 내용
"next/router"의 useRouter 훅을 사용하면,
URL에서 제시된 query를 쉽게 받아올 수 있다!
(feat. 구조분해할당)
3. [Next.js] 페이지, 컴포넌트, Props - Type 지정
Next.js에서 TypeScript를 사용하게 되면, '타입 안정성'과 '개발자 경험'을 개선할 수 있습니다. 화살표 함수만 사용해도 컴파일 오류 없이 페이지나 컴포넌트를 만들 수 있지만, 적절한 타입을 활용했을 때 기대되는 장점으로 다양한 것들이 있습니다.
- 타입 명시 및 가독성
- 코드의 의도를 분명히 함, 다른 개발자들이 코드를 이해 용이
- 타입 안정성과 추론 지원
- 타입을 지정한 데이터에 대해서, 잘못된 타입을 사용했거나 전달이 없는 경우 컴파일 단계에서 오류를 발생시켜 실수를 방지
프로젝트를 진행하면서 처음 만나본 다음 세 가지의 Type에 대해 간략히 정리해 보았습니다.
(1) NextPage
(2) React.FC
(3) Props
(1) NextPage
`Room` 이라는 페이지 컴포넌트에 대한 타입으로 `NextPage`가 지정되어 있어, Next.js 페이지 컴포넌트라는 것을 명확히 나타냅니다. NextPage는 Next.js와 관련된 다른 타입들과도 자연스럽게 호환되어, 일관된 타입 정의를 제공하는 데 도움을 준다고 합니다.
화살표 함수만으로도 페이지 컴포넌트를 만들 수 있지만, NextPage를 사용하면 타입 안정성, 가독성을 높이는 데 유리하므로, 팀 프로젝트나 장기적으로 유지보수 해야 할 프로젝트에서는 NextPage와 같은 타입을 적극 활용하면 좋을 것 같습니다.
import { NextPage } from 'next';
const HomePage: NextPage = () => {
return <h1>Welcome to Next.js</h1>;
};
export default HomePage;
(2) React.FC
React에서는 함수형 컴포넌트를 사용하는 것이 일반적입니다. `React.FC` 타입은 Functional Component 기반으로, 선택적으로 사용할 수 있습니다.
import React from 'react';
type Props = {
title: string;
subtitle?: string; // 선택적 속성
};
const Header: React.FC<Props> = ({ title, subtitle }) => {
return (
<header>
<h1>{title}</h1>
{subtitle && <h2>{subtitle}</h2>}
</header>
);
};
export default Header;
그런데, React.FC에는 몇 가지 단점이 있어 사용을 지양하며, 웬만하면 일반 함수로 컴포넌트를 선언하기를 추천한다고 합니다. props를 제네릭으로 설정하는 것이 불가능 (즉, <T> 사용 불가능) 하다든지, `defaultProps`를 사용 할 수 없다든지 등의 단점이 있습니다. 아래 발표 내용에서 확인하실 수 있습니다.
(이미 모든 컴포넌트를 React.FC로 설정해서 구현했는데 악)
React.FC 쓰지 마세요?
https://youtu.be/Xve998VkZTg?t=584
(3) Props - interface vs. type
`interface` 또는 `type`으로 props의 타입을 지정하는 예시를 많이 접할 수 있었고, 저도 그러한 방식을 배워서 프로젝트에 적용했습니다. 둘 다 props를 정의하는 데 사용되지만, 약간의 차이가 있습니다.
interface
- 확장 용이 (다른 interface를 상속받을 수 있다..!)
- 객체 타입을 정의할 때 선호
interface User {
id: number;
name: string;
}
interface Props {
users: User[];
onSelect: (id: number) => void;
}
const UserList: React.FC<Props> = ({ users, onSelect }) => (
<ul>
{users.map((user) => (
<li key={user.id} onClick={() => onSelect(user.id)}>
{user.name}
</li>
))}
</ul>
);
type
- 더 유연하며, 유니온 타입 및 기타 복합 타입 작성에 적합
type ButtonProps = {
label: string;
onClick: () => void;
};
const Button: React.FC<ButtonProps> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
type CardProps = {
title: string;
description?: string; // 선택적
};
const Card: React.FC<CardProps> = ({ title, description = "Default description" }) => (
<div>
<h1>{title}</h1>
<p>{description}</p>
</div>
);
4. 마무리
정리하다 보니 이런 저런 내용이 섞여 복잡해진 것 같습니다만, 당시 상황에 대한 복기도 되고, 앞으로는 어떻게 게시글을 쓰면 좋을지 감도 잡혀가고 있습니다.
다음은 Express.js의 기초 이해하기(..내가 이해했던 내용만 정리)를 정리해보겠습니다.
부록
코드 저장소 바로가기
https://github.com/yewon-saurus-mini-project/air-hockey-app
GitHub - yewon-saurus-mini-project/air-hockey-app
Contribute to yewon-saurus-mini-project/air-hockey-app development by creating an account on GitHub.
github.com