Client-Side Rendering vs Server-Side Rendering 의 차이.
최근에 React, vue 같은 CSR 이 유행이었는데 이는 큰 두가지의 단점을 가지고 있다.
첫번째는 구글의 검색노출이 어렵다는 것. (검색엔진 최적화가 불리)
두번째는 초기 로딩속도가 느리다는 것.
둘은 페이지의 수익성과 직결되는 문제이기 때문에 문제가 된다.
Next.js 는 풀스택 프레임워크를 지향하고 있다.
리액트 최신 문법을 따르고 있으며 (리액트 공식 홈페이지에서도 사용을 종용하고 있을 정도로)
클라이언트 사이드 렌더링을 자체적으로 지원하며
자바스크립트가 들어가지 않는 view 단의 페이지를 쉽게 만들 수 있어
초기 렌더링 속도가 중요한 페이지에 적용할 수 있다.
서버 데이터 캐싱이나 이미지 최적화도 자체적으로 지원한다.
이러한 장점은 2022년 개발자 프레임워크 재사용 의사 설문조사에도 드러나는데,
비슷한 기능을 하는 프레임워크 대비 만족도가 가장 높다.
따라서, 리액트와 마찬가지로 많은 개발자들이 사용하고 있어 관련 레퍼런스를 찾는 것이
다른 프레임워크보다 용이하다.
몇가지의 단점이 있다면
서버 사이드 렌더링의 단점과도 일맥상통한다.
서버의 부하가 CSR에 비해 많다는 점. 서버가 느리면 웹사이트의 속도가 기하급수적으로 느려진다.
또한, 새로운 HTML 을 불러올 때마다 화면이 깜빡이는 등의 좋지못한 UX를 가지고 있다는 점이다.
추가적으로는 클라이언트 컴포넌트, 서버 컴포넌트를 명확하게 구분하여 코드를 짜야하고,
웹 소켓이나 RTC 같은 기능을 이용하는 방법이 조금 까다롭다. (직접 Node 서버를 만드는 편이 쉽다)
npx create-next-app@latest
// Installation
What is your project named? my-app
// 프로젝트 이름은 무엇입니까? (my-app)
Would you like to add TypeScript with this project? Y/N
// 이 프로젝트에 TypeScript를 사용하시겠습니까? (Y/N)
Would you like to use ESLint with this project? Y/N
// 이 프로젝트에 ESLint를 사용하시겠습니까? (Y/N)
Would you like to use Tailwind CSS with this project? Y/N
// 이 프로젝트에 Tailwind CSS를 사용하시겠습니까? (Y/N)
Would you like to use the `src/ directory` with this project? Y/N
// 이 프로젝트에서 src/ directory를 사용하시겠습니까? (Y/N)
What import alias would you like configured? `@/*`
npm run dev
// 개발 환경 Next.js 를 시작
CRA랑 비슷한 프로젝트 자동 빌드 툴 인것 같다.
자체적으로 타입스크립트다 ES Lint, Tailwind 를 지원하는 것이 인상깊다.
옛날부터 궁금했던건데 Next js 를 사용할 때 스타일링 도구로 Tailwind 를 자주 사용하던데,
공식 문서에도 종용할 정도라면 정말 둘이 잘 맞아서 그런가 ?
스타일링 라이브러리 간의 성능상의 이슈라거나 next js 에서 tailwind 가 특별하게 가지는 장점은
크게 없다고 한다. tailwind 의 장점이 유독 next js 에서만 통용되는 장점이 아니라는 의미.
그러나, 현재 Next js 13 버전의 서버 컴포넌트는 css-in-js가 적용되지 않는다고 한다.
css-in-js 는 css를 자바스크립트 내에서 적용할 수 있도록 도와주는데
우리가 가장 많이 사용하고 있는 Styled-components 또한 css-in-js 이다.
반면에 유틸리티 기반 css 프레임워크인 Tailwind 는 유틸리티 클래스를 제공해서 디자인할 수 있기 때문에
css-in-js 가 적용되지 않는 서버 컴포넌트에서 좋은 해결책이 된다.
음- 그래서 그렇군.
페이지 기반 라우팅을 지원하기 때문에
pages 폴더가 자체적으로 내장되어 있다.
public 폴더에는 assets 파일들이 들어가고 styles 폴더에는 css 관련 속성이 들어갈 것이라는 것을
어렵지 않게 유추할 수 있다.
전반적으로 기본적인 페이지 디렉토리나 기본 문법은 React 와 큰 차이가 없는 것 같다.
app 디렉토리와 pages 디렉토리가 다르다는 것을 인지하지 못했다.
빌드 옵션에 app 디렉토리를 묻는 옵션이 있었는데 무슨 옵션인지 몰라서 넘어갔던게 화근.
결국 다시 빌드해야 했다.
다시 빌드한 프로젝트 리렉토리
pages 폴더가 사라지고 app 폴더 디렉토리로 빌드된 모습이다.
뭐가 다르지 ?
Next js 는 파일로 라우팅을 하는데 app 디렉토리는 Next 13 버전에 도입된 기능이다.
서버 컴포넌트를 기반으로하는 새로운 라우터를 사용하는 기능이라고 한다.
공유 레이아웃이나 중첩 라우팅, 404, 로딩 및 에러 컴포넌트 처리를 지원한다.
리액트에서 따로 레이아웃 컴포넌트나 로딩, 에러 컴포넌트를 만드는 것과 비슷한 느낌인가보다.
그리고 pages 기반 라우팅과 비교했을 때 성능상의 이점도 가진다.
이는 app 디렉토리 내부의 컴포넌트가 React 서버 컴포넌트 기반으로 설계되어 있기 때문인데,
이를 통해서 성능 최적화를 쉽게 적용할 수 있다고 한다.
아직까지는 크게 와닿지 않지만 프로젝트를 진행하면서 비교해 볼 예정.
app 디렉토리의 라우팅은 굉장히 간단하다.
/app/list/page.js
cosnt List = () => {
return (
<div>
<h4>상품목록</h4>
</div>
)
}
export default List;
app 폴더 안에 라우팅 할 폴더를 만들고 그 안에 page.js 파일를 만들면 자동으로 라우팅이 된다.
중첩 라우팅도 굉장히 편한데 폴더 안에 폴더를 하나 더 만들어서 page.js 파일을 만들면 된다.
네비게이션 바 같은 ‘모든 페이지에서 보여져야 하는 컴포넌트’ 는 layout.js 파일에 두는 것이 바람직하다.
이를 통해 하위 페이지의 내용만 변경하고 공통으로 보여져야 하는 레이아웃을 쉽게 설계할 수 있다.
굉장히 편리한 기능.
프레임워크 자체적으로 layout.js 컴포넌트가 page.js 컴포넌트보다 ‘먼저’ 그려준다.
공식문서를 보면 이런식으로 작동한다고 한다. 중첩 레이아웃도 가능하다.
최상위 root 레이아웃 컴포넌트는 Required 옵션을 가지고 있어서 반드시 설정해주어야 하는 모양.
음.. 근데 이렇게는 안되는 모양
import Navbar from "@/components/Navbar";
import "./globals.css";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<Navbar />
<body className={inter.className}>{children}</body>
</html>
);
}
components 폴더를 만들어서 navbar 라는 컴포넌트를 만들고
app 폴더 안에 최상위 layout 컴포넌트에 내가 만든 navbar 컴포넌트를 넣어서 렌더링을 시켜봤더니 안된다.
위의 구현이 안될 리 없다고 생각했는데 역시나 컴퓨터는 잘못이 없다.
Navbar 컴포넌트가 들어갈 자리는 html 태그 안쪽이 아니라 body 태그 안쪽이다.
react 에 너무 익숙해져 있다 보니까 html, head, body 태그에 대해서도 까맣게 잊어버린 모습.
정말 허접하지 않을 수 없다.
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<Navbar />
{children}
</body>
</html>
);
}
이런식으로 수정하면 제대로 렌더링 된다.
기능을 추가적으로 사용하다 보니
react-router 의 outlet 기능과 비슷해서 사용하는 데 어려움은 없을 것 같다.
export default function List() {
let products = [
{ id: 1, name: "tomato", price: "40$" },
{ id: 2, name: "pasta", price: "50$" },
{ id: 3, name: "coconut", price: "30$" },
];
return (
<div className="container">
<h4 className="title">상품목록</h4>
{products.map((product) => (
<div className="food" key={product.id}>
<h4>
{product.name} {product.price}
</h4>
</div>
))}
</div>
);
}
음 아주 굉장히 리액트스럽군.
추가적으로 next js 에서도 jsx 문법을 사용하고 있기 때문에 반복문에는 key 값을 부여해줘야 한다.