> For the complete documentation index, see [llms.txt](https://book.hajoeun.dev/friendly-next-js/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.hajoeun.dev/friendly-next-js/part-3-next.js/undefined-2/vercel.md).

# Vercel 리더십 블로그

Vercel의 CEO인 Guillermo Rauch와 VP인 Lee Robinson의 블로그 아키텍쳐를 살펴보면서 Next.js를 이용해서 어떤 방식으로 블로그를 만들 수 있는지 살펴보려 합니다.

두 사람의 블로그는 GitHub에 공개되어 있습니다.&#x20;

1. Guillermo Rauch - <https://github.com/rauchg/blog>
2. Lee Robinson - [https://github.com/leerob/site](https://github.com/leerob/site/tree/369a5421d2d8f9d491548a27cf4eee6c912b3602)

<figure><img src="/files/weW7koEpYORnY1Ay7khH" alt=""><figcaption><p>Lee Robinson(좌) &#x26; Guillermo Rauch(우)</p></figcaption></figure>

{% hint style="info" %}
24년 9월 7일 이후로 Lee의 블로그가 변경되었습니다. (leerob.io -> leerob.com) 강의의 내용에 등장하는 블로그와 차이가 있을 수 있습니다.
{% endhint %}

## 데이터 관리

### 글을 코드로

Guillermo의 경우엔 글을 코드로 관리합니다. 바꿔 말하면 작성된 코드가 글이 된다는 의미입니다.&#x20;

이 방식에선 글을 `app` 디렉토리 하위에 작성합니다. Guillermo가 `app/(post)/2021/making-the-web-faster/page.mdx`와 같은 경로에 작성하는 것처럼요. 마크다운으로 작성된 파일이 글로 해석될 수 있는 비밀은 설정 파일에서 찾을 수 있습니다.

{% code title="next.config.js" %}

```javascript
const withMDX = require("@next/mdx")();

module.exports = withMDX({
  pageExtensions: ["ts", "tsx", "md", "mdx"],
});
```

{% endcode %}

Next.js에서 제공하는 마크다운 라이브러리를 사용해 설정을 완성하고 있습니다. `pageExtensions` 설정을 통해 `md`, `mdx`와 같은 마크다운 확장자를 `ts`, `tsx`와 같은 방식으로 다루려고 한다는 걸 알 수 있습니다. 이는 일반적인 블로그 아키텍쳐에서 확인했던 `getPostData` 와 같은 함수를 없이도 마크다운 파일을 글로 해석할 수 있게 된다는 의미입니다.&#x20;

Guillermo는 `useMDXComponents`를 활용해서 글 중간에 삽입한 트윗 컴포넌트를 스타일링하고 있습니다.

{% code title="mdx-components.ts" %}

```typescript
export function useMDXComponents(components: {
  [component: string]: React.ComponentType;
}) {
  return { ...components, Tweet };
}
```

{% endcode %}

### 글을 데이터로

Lee의 경우엔 글을 데이터로 관리합니다. 일반적인 Next.js 블로그와 동일한 방식을 사용하는 것이죠.

이 방식엔 글의 위치가 `app` 디렉토리 하위에 있을 필요가 없습니다. Lee는 `content/developer-marketing.mdx`와 같은 경로에 글을 작성합니다.&#x20;

`getPostData`와 유사한 함수를 구현해서 사용합니다.  파일시스템에 접근해서 content 디렉토리에 있는 데이터를 읽어내죠. 이후 메타데이터와 컨텐츠를 나눠서 결과를 반환하는 방식입니다.

{% code title="app/blog/\[slug].tsx" %}

```tsx
export default function Blog({ params }) {
  let post = getBlogPosts().find((post) => post.slug === params.slug);

  return (
    <section>
      <CustomMDX source={post.content} />
    </section>
  );
}
```

{% endcode %}

Lee는 `MDXRemote`를 사용해서 컴포넌트를 스타일링해서 글 중간에 트윗과 같은 컴포넌트를 첨부하고 있습니다.

{% code title="components/mdx.tsx" %}

```tsx
import { MDXRemote } from 'next-mdx-remote/rsc';
import { TweetComponent } from './tweet';

export function CustomMDX(props) {
  return (
    <MDXRemote
      {...props}
      components={{ StaticTweet: TweetComponent }}
    />
  );
}
```

{% endcode %}

## 라우팅과 렌더링

### Route Groups & ISR

Guillermo는 Route Groups를 활용하고 있습니다. `app/(post)` 하위에 있는 다른 폴더에서 같은 레이아웃을 공유하고 있죠. 글을 작성한 년도를 2021, 2022와 같이 폴더로 구분해서 폴더가 같은 레이아웃을 공유하도록 만들고 있습니다.

또 ISR을 사용해서 SSG의 한계를 극복하려고 하고 있습니다. 아래와 같이 레이아웃 파일에서 `revalidate`을 60으로 지정해 1분에 한 번씩 페이지를 재생성합니다.

{% code title="app/(post)/layout.tsx" %}

```tsx
export const revalidate = 60;

export default async function Layout({ children }) {
  return <article>{children}</article>;
}
```

{% endcode %}

### Dynamic Routes & PPR

Lee는 Dyanmic Routes를 활용하고 있습니다. `app/blog/[slug]/page.tsx`와 같은 방식이죠.&#x20;

렌더링의 경우 Partial Prerendering을 사용하고 있습니다. 이는 블로그를 빌드해보면 확인할 수 있는데요. 빌드 결과를 알려주는 메시지가 아래와 같이 출력됩니다.

```
Route (app)                              Size     First Load JS
┌ ◐ /                                    1.59 kB         102 kB
├ ○ /blog                                178 B          95.5 kB
├ ◐ /blog/[slug]                         16.7 kB         328 kB

(... 중략)

○  (Static)             prerendered as static content
◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content
```

글을 렌더링하는 `/blog/[slug]` 경로가 ◐ 표시로 Partial Prerendering 되었다고 알려주고 있습니다. Next.js 14 버전을 기준으로 Partial Prerendering을 실험적인 기능입니다. 이를 사용하기 위해선 아래와 같은 추가 설정이 필요합니다.

{% code title="next.config.js" %}

```javascript
export default {
  experimental: {
    ppr: true,
  },
};
```

{% endcode %}

## 메타데이터

### Static Metadata

Guillermo는 메타데이터를 정적인 방식으로 생성하고 있습니다. `metadata` 객체를 만드는 방식이죠.&#x20;

```tsx
// app/(post)/2021/making-the-web-faster/page.mdx

export const metadata = {
  title: 'Making the Web. Faster',
  description: 'Our vision of the Web is a global realtime medium for both creators and consumers, where all friction and latency are eliminated',
  openGraph: {
    title: 'Making the Web. Faster',
    description: 'Our vision of the Web is a global realtime medium for both creators and consumers, where all friction and latency are eliminated',
    images: [{ url: '/og/making-the-web-faster' }]
  }
}
```

일반적인 Next.js 블로그에서 살펴본 Front Matter와 유사한 방식으로 메타데이터를 정의하고 있습니다. 차이점이 있다면 우리에겐 친숙한 객체 형태라는 점입니다. 이번 App Router에서는 이렇게 정적으로 `metadata`라는 이름으로 객체를 정의하면 그걸 메타데이터로 적용해줍니다.

### Dynamic Metadata

Lee는 메타데이터를 동적인 방식으로 생성하고 있습니다. 함수로 메타데이터를 정의하는 방식입니다.

```tsx
// app/blog/[slug]/page.tsx

export async function generateMetadata({
  params,
}): Promise<Metadata | undefined> {
  let post = getBlogPosts().find((post) => post.slug === params.slug);
  
  let {
    title,
    publishedAt: publishedTime,
    summary: description,
    image,
  } = post.metadata;
  let ogImage = image
    ? `https://leerob.io${image}`
    : `https://leerob.io/og?title=${title}`;

  return {
    title,
    description,
    openGraph: {
      title,
      description,
      type: 'article',
      publishedTime,
      url: `https://leerob.io/blog/${post.slug}`,
      images: [
        {
          url: ogImage,
        },
      ],
    },
  };
}
```

`generateMetadata`라는 이름의 함수가 실행되며 `metadata` 객체를 생성합니다. 이때 생성되는 객체는 앞서 정적으로 만들던 `metadata`와 같은 타입의 객체입니다.&#x20;

이 함수가 실행되면서 조건에 따라 다른 `metadata` 객체를 만들어 내고 있습니다. 원한다면 API를 호출할 수도 있습니다. 말 그대로 호출할 때마다 다른 결과를 반환할 수도 있는 동적인 방식입니다.

## 정리

Vercel 리더십의 블로그 아키텍쳐를 표로 정리하면 다음과 같습니다.

|        | Guillermo                       | Lee                  |
| ------ | ------------------------------- | -------------------- |
| 데이터 관리 | 글을 코드로                          | 글을 데이터로              |
| 라우팅    | Route Groups                    | Dynamic Routes       |
| 렌더링    | Incremental Static Regeneration | Partial Prerendering |
| 메타데이터  | Static Metadata                 | Dynamic Metadata     |
