4.3 Next.js 톺아보기
✨ 리액트 서버 사이드 렌더링 프레임워크로서 최전성기를 달리고 있는 Next.js 를 살펴보자
4.3.1 Next.js란?
🔖 Next.js는 서버사이드 렌더링을 염두에 두고 PHP를 대체하기 위해 만들어진 리액트 프레임워크다.
- 기존 리액트에서 진행했던 프로젝트인
react-page
에서 영감을 받은 디렉토리 기반 라우팅을 지원한다.
4.3.2 Next.js 시작하기
🔖 create-next-app을 사용하여 프로젝트를 시작할 수 있다.
npx create-next-app@latest --ts
npm
을 통해 구동하므로 기본적인package.json
을 통해 대략적인 파악이 가능하다.next.config.js
파일을 통해 Next.js의 환경 설정을 할 수 있다.
🏷️ pages 폴더의 _app, _document.tsx
_app.tsx는 앱 전역의 에러를 처리하거나,
reset.css
같은 글로벌 css 선언 및 공통으로 사용할 데이터를 각 페이지에 뿌리는 등의 역할을 수행한다. (루트 개념)_document.tsx는 리액트나 바닐라 JS의 index.html 파일의 역할을 수행한다. (
html
,body
등의 태그에 DOM 속성 직접 추가,Head
태그 사용 등)주의할 점은 _document.tsx는 순수 HTML 태그만 사용 가능하다는 것이다.
또한 _document.tsx는 CSS-in-js 형태 파일을 모아 HTML로 제공하는 역할도 수행한다.
🏷️ pages 폴더의 error, 404, 500.tsx
error.tsx는 기본값이 아니며, 클라이언트의 에러 혹은 서버의 500 에러를 처리하기 위해 만들어졌다.
전역적인 에러를 처리하고 싶다면 사용하자
개발 모드에서는 방문할 수 없어 빌드 후 프로덕션 모드에서 접속해야한다.
404.tsx는 말그대로 404 페이지를 커스텀할 수 있는 페이지다.
500.tsx는 error.tsx보다 우선적으로 실행되며 용도는 동일하다.
🏷️ index.tsx
개발자가 명칭을 자유로이 할 수 있으며,
export default
로 내보내는 각 컴포넌트들은 해당 페이지의 루트 컴포넌트가 된다.네이밍이 곧 경로이며, 대괄호([])를 사용해 동적인 경로를 만들 수도 있고, 폴더를 또 만들어 폴더 안의 파일들을 경로로 삼을 수도 있다.
└── pages
├── _app.tsx
├── _document.tsx
├── index.tsx // /(루트)
├── about.tsx // /about
├── contact.tsx // /contact
├── blog
│ ├── index.tsx // /blog
│ ├── [id].tsx // /blog/[id]
└── user
├── index.tsx // /user
├── [id].tsx // /user/[id]
└── settings.tsx // user/settings
🔖 서버 라우팅과 클라이언트 라우팅의 차이
Next.js는 SSR를 수행함과 동시에 클라이언트 라우팅 또한 수행한다.
최초 렌더링을 서버에서 수행한다.
Next.js에서 자체적으로 제공하는 Link 컴포넌트를 통해 빠르게 최초 렌더링하는 SSR의 이점과 깜박거림이 없는 클라이언트 라우팅/렌더링의 장점을 모두 챙겼다.
🔖 getServerSideProps를 제거할 경우
getServerSideProps
를 제거하고 빌드하면 서버에 로그가 남지 않는다.Next.js는 페이지를 SSG 방식으로 처리하게 되며, 이는 요청마다 페이지를 동적으로 생성할 필요가 없음을 의미한다.
따라서 추가 작업없이 정적 파일로 페이지를 제공하게 된다.
🏷️ pages/api 폴더의 hello.ts
서버의 API를 정의하는 폴더이며 HTML 요청이 아닌 단순 서버 요청용 파일이다.
서버에서 내려주는 데이터를 활용하거나 풀스택 앱 및 CORS 문제 우회를 위해 사용 가능하다.
4.3.3 Data Fetching
🔖 서버 사이드 렌더링 전략을 지칭하며 pages 폴더의 라우팅 가능 파일에서만 사용 가능하며 export
를 통해 정해진 함수명을 사용 및 내보내야 한다.
🏷️ getStaticProps, getStaticPaths
이미 정적으로 완성된 페이지를 보여주고자 할 때 사용하는 함수이다. 특히
getStaticPaths
를 통해 동적으로 결정되는 페이지의params
를 받아와 HTML 페이지를 만들 수 있다.path에서 해당하는 모든 동적인 경로의 페이지를 사전에 미리 빌드하여 빠르게 페이지를 볼 수 있다.
또한 fallback 옵션을 통해 사전에 로딩같은 컴포넌트를 미리 보여주고 페이지를 보여주거나, 아예 옵션을 끄는 등의 지정도 가능하다.
🏷️ getServerSideProps
- 서버에서 실행되며 페이지 진입 전에 미리 함수를 실행한다. 실행 시 응답값에 따라
props
를 제공하거나 리다이렉트 시킬 수도 있다.
export const getServerSideProps = async (context) => {
const { query: {id: ''}, } = context;
const post = await fetchPost(id.toString())
return {
props: { post },
}
}
context
를 통해 id값에 접근 및 서버 사이드 렌더링을 위한fetch
에 사용 가능하다.
< 서버 사이드 렌더링 작동 방식 >
- 서버에서
fetch
등으로 관련 정보 가져오기 - 정보로 HTML 완성하기
- HTML을 클라이언트에 제공하기
- 클라이언트에서
hydrate
작업하기 - 컴포넌트 트리와 서버의 HTML이 다를 시 불일치 에러
- 5번 또한 마찬가지로
fetch
등으로 정보를 가져와야 함
✅ 결국 1번과 6번 사이 불일치로 인한 에러를 방지하기 위해 직접 Script 형태로 HTML에 내려주는 방식을 채택했다.
따라서 내려주는 값은 JSON 타입으로 한정된다. (props의 값을 HTML로 정적 작성 후 내려주기 때문)
항상 서버에서만 실행되기 때문에 여러가지 제약이 존재한다. (브라우저 접근 객체에서 접근 불가, protocol, domain 없이 fetch 요청 불가 등)
또한 페이지 호출 시마다 실행되고 완료 전까진 어떠한 HTML도 보여줄 수 없다.
정보를 보여줄 수 없다면
redirect
를 지정하여 페이지를 리다이렉트 시킬 수도 있다.
🏷️ getInitialProps
getStaticProps
,getServerSideProps
가 나오기 전 데이터를 불러오던 수단아직도 일부 페이지에선 사용되는 경우가 있으므로 알고 있어야 한다.
import React from "react";
import axios from "axios";
interface Props {
data: any;
}
const HomePage: React.FC<Props> = ({ data }) => {
return (
<div>
<h1>Data from API</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
HomePage.getInitialProps = async () => {
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts/1");
return { data: response.data };
} catch (error) {
console.error("Error fetching data", error);
return { data: null };
}
};
export default HomePage;
페이지 루트 함수에 정적 메서드로 추가하며,
props
가 아닌 객체 자체를 반환한다.라우팅에 따라 서버와 클라이언트 모두에서 실행 가능하므로 코드 작성 시 주의를 요한다.
🏷️ context 객체 안의 값
값 | 용도 |
---|---|
pathname | 현재 경로명이지만 실제 경로가 아니니 페이지상 경로 |
asPath | 브라우저에 표시되는 실제 경로, 사용자에게 표시되는 주소가 보임 |
query | URL에 존재하는 쿼리, pathname 값도 포함된다. |
req | HTTP request 객체 |
res | HTTP response 객체 |
4.3.4 스타일 적용하기
🔖 Next.js에서 스타일을 적용하는 다양한 방법을 알아보자
_App.tsx
파일을 이용해 공통적인 스타일을 적용할 수 있다.컴포넌트 레벨 CSS (module.css) 를 통해 충돌하지 않는 고유한 클래스의 CSS 스타일링 적용이 가능하다.
SASS(SCSS)의 적용 또한 가능하다. (module.scss)
🏷️ CSS-in-js 사용하기 (중요!)
CSS를 JS 내부에 직접 작성하는 방식으로, 최근엔
Styled-Component
,Emotion
등이 있다.짬킹은
Styled-Component
이다. (난 싫다)tailwind.css
도 야무진 CSS-in-js 중 하나이다.
import Document from "next/document";
import { ServerStyleSheet } from "styled-components";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}
export default MyDocument;
리액트 트리 내부의
styled-component
의 스타일을 모두 모아 유니크 클래스명 부여 및 스타일 정리를 통해_document.tsx
가 이를React.Context
형태로 제공한다.서버에서 미리 모아 제공하지 않으면
FOUC
라는 HTML을 날것으로 제공하는 친구를 만날 수 있다.
👍 책에선 소개되지 않았지만 tailwind도 매우 좋다.
위와 같은
critical-css
의 경우, 클라이언트 쪽에서 실행될 JS에도 포함되어야 한다. 때문에 동일한 스타일에 대한 코드가 초기 HTML에서 한 번, JS 번들에서 두 번 클라이언트에게 전달된다.또한 페이지의 방향성이 컴포넌트(앱)으로 넘어간 Next.js 13버전 이상에선 사용이 제한적이다.(서버 컴포넌트와 방향 충돌)
tailwind
는 유틸리티 CSS로써 확장성있게 CSS를 작성 가능하고, 빌드 시 사용되지 않는 클래스는 제거되어 번들 크기를 줄일 수 있다.atomic한 특성을 통해 프로젝트 크기가 커져도 CSS의 크기는 영향받지 않는다.
또한 런타임이 아닌 빌드 타임에 CSS 시트를 가져오기 때문에 SSR에서도 효율적으로 사용 가능하다.
4.3.5 _app.tsx 응용하기
🔖 최초 진입점임을 활용하여 처음 서비스 접근 시 처리할 작업을 실행 가능하다.
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
</>
);
}
MyApp.getInitialProps = async (context: AppContext) {
const appProps = await App.getInitialProps(context)
return appProps
}
getInitialProps
의 실행을 위해선 반드시const appProps = await App.getInitialProps(context)
구문을 실행해야 한다.
MyApp.getInitialProps = async (context: AppContext) {
const appProps = await App.getInitialProps(context)
return appProps
const.log(
`[${isServer ? '서버' : '클라이언트'}] ${context.router.pathname}에서 ${context.ctx?.req?.url}을 요청함.`;
)
}
이 코드를 통해 페이지 첫 방문 시 페이지 전체를 요청하고, 깜박거림 해소를 위해
Link
컴포넌트 등을 사용 시 전체 페이지를 가져오지 않고getInitialProps
의 결과만 가져온다.이러한 특성을 이용해 최초 접근 시에만 작업하고 싶은 내용을 담아둘 수 있다.
4.3.6 next.config.js 살펴보기
🔖 Next.js 실행에 필요한 다양한 설정을 추가할 수 있는 파일
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// env 설정
env: {
API_URL: "https://api.example.com",
NEXT_PUBLIC_ANALYTICS_ID: "your-analytics-id",
},
// 이미지 도메인 설정
images: {
domains: ["example.com", "another-domain.com"],
},
// 플러그인 추가
webpack: (config, { isServer }) => {
// 예를 들어, 특정 플러그인을 추가하는 경우
if (!isServer) {
config.resolve.fallback.fs = false;
}
return config;
},
// 리다이렉트
async redirects() {
return [
{
source: "/old-page",
destination: "/new-page",
permanent: true, // 301 리다이렉트
},
{
source: "/temporary-redirect",
destination: "/another-page",
permanent: false, // 302 리다이렉트
},
];
},
};
module.export = nextConfig;
4.3.7 책 정리 + 주관적인 정리
🔖 책정리
- Next.js는 리액트 기반 SSR 프레임워크의 대명사이며 사용을 권장한다.
🏷️ 주관적인 정리
최근 프로젝트나 강의 등으로 Next.js를 꾸준하게 접하면서 친근해졌다고 생각했는데 역시 만만하지 않다.
getInitialProps
라는 친구를 새로 알게되어 흥미로웠다.
'Front-End Study > 모던 리액트 딥다이브 스터디' 카테고리의 다른 글
모던 리액트 딥다이브 - 11회차 [6-1, 6-2, 6-3, 6-4] (0) | 2024.07.11 |
---|---|
모던 리액트 딥다이브 - 10회차 [5-1, 5-2] (1) | 2024.06.30 |
모던 리액트 딥다이브 - 8회차 [4-1, 4-2] (0) | 2024.06.25 |
모던 리액트 딥다이브 - 7회차 [3-1, 3-2, 3-3] (2) | 2024.06.19 |
모던 리액트 딥다이브 6회차 [2-3, 2-4, 2-5] (0) | 2024.06.17 |