<

/>

imagenext

NEXT.JS@14 App Router 블로그 제작 후기

블로그 재설계 이유와 후기

2024년 02월 01일

개요

기존에 운영하던 블로그가 있었지만 문제점이 많이 보여서 수정을 하고싶었습니다.
그렇게 리팩토링과 업데이트를 거치다가, 잘못된 설계에서 계속 덧붙여 나가는 방향 보다는,
처음부터 다시 설계해서 만들고자 했습니다.

그렇게, 이 글은 제가 경험한 설계의 문제점을 공유하고 App Router로 인해 얻은 이점을 공유하려고합니다.

문제점

기존 블로그 메인화면

기존 블로그 메인화면

[ 기존 블로그 깃허브 , 기존 블로그 배포링크 ]
기존의 블로그는 프론트엔드를 공부하기 시작한 지 얼마 안 된 시절에 만들었기 때문에,
다양한 애니메이션, 개성 있는 트랜지션으로 화려함을 추구했습니다.

다만, 시간이 흘러 시선이 달라진 지금으로써는, 보여지는것 보다는 기술적인 부분에 더 관심을 기울이고,
성능과 최적화 그리고 간편함에서 오는 우아함을 추구하고 싶었습니다.

다음은 제 기존 블로그의 문제점들입니다.

Firebase는 어울리지않는다.

기존의 블로그 데이터(markdown 글, 댓글, 방명록)들은 Firebase에서 가져와서 사용했습니다.
이 당시에는 Firebase를 배운지 얼마 되지않아, 직접 응용해보고싶어서 적용했었습니다.

하지만, 블로그에 Firebase를 사용하는건 SSG로 만들었기 때문에 운영/배포 환경에선 괜찮았지만,
개발환경일때는 불필요한 DB접근을 너무 많이 하고, 정적인 데이터를 여러번 불러오는 것 자체가 잘못된 설계였습니다.
Firebase의 Database는 실시간 기능이 필요한 앱이나 복잡한 사용자 인증이 필요한 Realtime 서비스 등에는 유용할 수 있지만, 블로그에는 어울리지 않는 설계라고 생각합니다.

Next.js의 이점을 못살리는 설계

기존 블로그는 Next.js 를 사용하여 만들었지만, Next.js의 이점을 전혀 살리지 못하는 방법으로 사용중이었습니다.
이는 본인이 사용하는 라이브러리 및 프레임워크를 어떠한 방식으로 작동하는지,
이 프레임워크가 왜 사용되는지 같이 이론을 바탕으로 사용했어야 하지만,
코드만 작성하며 실무적인 요소로만 사용했기 때문입니다.

아래는 기존 블로그 pages 구조입니다.

// 기존 pages 형태 getStaticProps,getStaticPaths는 생략
 
export default function Home({ notesArr }: IHomeProps) {
  const [MainPageMobile, setMainPageMobile] =
    useState<React.ComponentType<IHomeProps> | null>(null);
 
  const { isDesktopView } = useDevicehook();
 
  useEffect(() => {
    import("@/components/Mobile/Home/MainPageMobile").then((module) => {
      setMainPageMobile(() => module.default);
    });
  }, []);
 
  return (
    <>
      {isDesktopView ? (
        <MainPage notesArr={notesArr!} />
      ) : (
        MainPageMobile && <MainPageMobile notesArr={notesArr} />
      )}
    </>
  );
}

isDesktopView 로 기기의 크기를 파악하고 모바일일 경우 동적 import를 사용하여 모바일 컴포넌트를 로드합니다.
CSR이라면 문제가 될게 없지만, 이는 Next.js 의 이점인 SSR과 SSG를 전혀 살리지 못하는 잘못된 설계입니다.

기존 블로그는 SSG 방식으로 설계를 했습니다. 그렇다는건 서버에서 정적인 html을 미리 생성하여 사용자에게 전달하는 것입니다. 하지만 블로그 설계에서 모순점이 나옵니다.

모바일 컴포넌트는 동적인 event를 받아 조건을 만족하면, 클라이언트 사이드에서 import되는 방식입니다.
이 모바일 컴포넌트는 서버에서 미리 생성된 html에 포함되지 않는것입니다.
결국, 모바일로 접속을 한 사용자들은 기존의 JS는 물론, 추가적인 모바일 컴포넌트 JS를 로드하고 실행해야 하므로, 페이지 로드 시간이 더 늘어납니다.

개선된 블로그 제작 후기

개선된 블로그 제작 과정에 대한 후기를 담고 있지만, 세세한 개발 과정이나 작동 원리는 다루지 않습니다.

Dependencies

  • Node.js@20
  • React@18
  • Next.js@14
  • TypeScript@5
  • tailwindcss
  • next-contentlayer
  • vercel

Rendering

서버/클라이언트 컴포넌트

서버/클라이언트 컴포넌트

서버 컴포넌트는 컴포넌트를 서버에서만 렌더링하여 사용자에게 전달합니다. 즉, 서버에서 많은 코드를 작업하여 유저에게 결과 UI만 제공한다는 것이고 이는 굉장히 사용자 친화적이고 제가 원하던 방식이었습니다.

Moving Client Components Down the Tree To reduce the Client JavaScript bundle size, we recommend moving Client Components down your component tree.

Next.js docs

Next.js 공식 문서에서는 클라이언트 최적화를 위해 클라이언트 컴포넌트를 리프트리에 두는걸 권장합니다.
이런 구조는 App Router 가 아니더라도 지향해야하는 설계 방향성이라고 생각합니다.

const HomePage = () => {
  const [count, setCount] = useState(0);
  return (
    <>
      <div>
        <p>{count}</p>
        <button onClick={() => setCount((prev) => prev + 1)}>count+1</button>
      </div>
      <OtherComponent1 />
      <OtherComponent2 />
      <OtherComponent3 />
    </>
  );
};

위와 같은 코드는 제가 리액트를 배운지 얼마 안된 시점에 자주 하던 실수입니다.
HomePage 컴포넌트에는 counter 역할을 하는 div와
count 상태와 관련이 없는 OtherComponent들이 존재합니다.

개발자가 의도한 설계는 버튼을 눌러 count 상태가 변하면 p 태그만 리렌더링 되어야 하겠지만,
위와 같이 설계하면 OtherComponent 들도 리렌더링됩니다.

const Counter = () => {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>count+1</button>
    </div>
  );
};
 
const HomePage = () => {
  return (
    <>
      <Counter />
      <OtherComponent1 />
      <OtherComponent2 />
      <OtherComponent3 />
    </>
  );
};

위와 같은 구조로 설계한다면 count가 변해도 Counter만 리렌더링 됩니다.
이 구조는 App Router가 지향하는 컴포넌트 패턴과 유사하게 보입니다.

Routing

블로그 레이아웃

블로그 레이아웃

새로운 블로그는 심플을 추구하였는데, Parallel Routes가 정말 유용하게 사용됐습니다.
심플하게, 페이지가 변해도 기본적인 레이아웃이 변하지 않게 설계할 수 있습니다.

app/
├── (about-layout)/
│   ├── about/
│   └── layout.tsx
├── (blog-layout)/
│   ├── @headSection/
│   ├── @rightSection/
│   ├── p/[title]/
│   ├── tags/tag/
│   ├── layout.tsx
│   ├── page.tsx
├── layout.tsx
├── not-found.tsx

또한 한가지 레이아웃을 강제해야 하는게 아닌,
소괄호로 (폴더)를 감싸서 페이지별로 다른 레이아웃을 구성할수 있습니다.

다만 파일 이름을 page.tsx, layout.tsx 와 같이 고정적으로 해줘야했기 때문에,
코드 에디터에 여러개의 창이 같은 이름으로 띄어져 있어서 불편할 수도 있다고 느꼈습니다.

그 외

  • tailwind 만의 css 파괴적인 문법(?) 은 긴 코드를 간편하게 줄여서 작성할수 있게 해주지만, 처음 보는 사람들에겐 기존의 css 속성값과 다른 클래스 이름이 불편할수도 있다는 생각이 들었습니다.
  • 마크다운 글을 firebase에 저장하여 각종 라이브러리로 크게 변환하지 않고, next-contentlayer를 이용해 간편한 설정만으로 마크다운 파일을 html 태그로 parsing 할 수 있었습니다.
  • 댓글은 간단한 설정으로 구현할수있고, 광고 기능이 없는 giscus로 선택했습니다.

마치며

블로그의 디자인, 설계, 개발도 중요하겠지만 가장 중요한 본질은 글입니다.
예전 블로그는 널리 알려져 있는 단순한 문제해결이나 기본적인 개념을 노트필기 하듯이 사용했습니다.
이제는 직접 문제를 탐구하며, 다양한 동작 원리를 쉽게 풀어서 설명하는 글을 작성하려고합니다.

블로그 디자인 참조

2024﹒©

 OU9999

Powered by Next.js﹒Vercel