Next.js 블로그 만들기
June 27, 2025
Next.js 블로그 템플릿 커스터마이징
포트폴리오를 겸할 수 있는 공간을 만들고 싶어서, 기존 Next.js 블로그 템플릿을 나만의 스타일에 맞게 커스터마이징하고 필요한 기능들을 직접 추가했다. 그동안 티스토리에 공부한 내용을 기록해왔는데, 이제는 이곳에서 이어가려 한다. 나만의 공간이 생겨서 기쁘다.
한글이 잘리는 정규식 수정하기
function slugify(str: string) {
return str
.toString()
.toLowerCase()
.trim() // 앞뒤 공백 제거
.replace(/&/g, "-and-") // & → -and-
.replace(/[^\w가-힣\s\-]+/g, "") // 특수문자 제거 (공백과 하이픈, 한글 허용)
.replace(/\s{2,}/g, " "); // 두 칸 이상 공백 → 한 칸
}Heading 태그 안의 텍스트가 영문일 땐 a 태그의 href가 정상적으로 생성되는데, 한글일 땐 잘리는 현상이 발생했다. 문제를 찾아보니, mdx.tsx에서 slug 생성 시 사용한 정규식이 한글을 제외하고 있었던 게 원인이었다. 정규식을 수정해서 한글도 포함되도록 처리했고, 현재는 정상적으로 동작한다.
목차 자동 생성
function getHeadingsFromContent(content: string): Heading[] {
const headingRegex = /^(#{1,6})\s+(.*)$/gm;
const headings: Heading[] = [];
let match;
while ((match = headingRegex.exec(content)) !== null) {
const depth = match[1].length;
const text = match[2].trim();
const id = slugify(text);
headings.push({ depth, text, id });
}
return headings;
}기존에는 MDX의 Heading 태그 안에 <a> 태그가 자동으로 생성되어 앵커 기능을 처리하고 있었다. heading태그 안에 a태그를 자동으로 생성할 필요는 없기 때문에 이 부분을 제거해주고 utils.ts에 getHeadingsFromContent() 함수를 만들어 getBlogPosts() 함수 내에서 heading 정보를 함께 반환하도록 처리했다. 이제 페이지 렌더링 시 post.headings를 <Toc />에 넘겨주면 본문 구조에 맞는 목차가 자동으로 생성된다.
SEO 구조화 데이터 추가
검색엔진이 페이지 내용을 더 잘 이해할 수 있도록 JSON-LD 형식의 구조화 데이터를 추가했다. JSON-LD는 <script type="application/ld+json"> 태그 안에 작성하며, 사용자 눈에는 보이지 않고 크롤러만 읽는다.
포스트 상세 페이지(/blog/[slug])에는 BlogPosting 를 적용해 제목, 작성일, 설명, 작성자 정보를 포함했다.
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.metadata.title,
datePublished: post.metadata.publishedAt,
description: post.metadata.summary,
author: {
'@type': 'Person',
name: 'dpwl35',
},
}),
}}
/>목록 페이지(/blog)에는 ItemList 를 적용해 각 포스트의 제목과 URL을 순서 있는 목록으로 구조화했다.
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'ItemList',
itemListElement: posts.map((post, index) => ({
'@type': 'ListItem',
position: index + 1,
url: `${baseUrl}/blog/${post.slug}`,
name: post.metadata.title,
})),
};이전 글 / 다음 글 네비게이션 추가
getBlogPosts()로 전체 포스트를 날짜 기준으로 정렬한 뒤, 현재 slug의 인덱스를 찾아 인접한 포스트를 뽑는 방식으로 구현했다.
const posts = getBlogPosts().sort(
(a, b) =>
new Date(b.metadata.publishedAt).getTime() -
new Date(a.metadata.publishedAt).getTime(),
);
const currentIndex = posts.findIndex((p) => p.slug === params.slug);
const prevPost = posts[currentIndex + 1] ?? null;
const nextPost = posts[currentIndex - 1] ?? null;이전 글(prevPost)은 더 오래된 글, 다음 글(nextPost)은 더 최신 글이다. 포스트가 없는 경우에도 동일한 구조를 유지해 레이아웃이 깨지지 않도록 처리했다.
포스트 스타일 정의
인용문구 예시
- 목록 1
- 목록 2
- 목록 2-2
- 순서
- 순서
글 하이라이팅은 회색 배경으로 링크와 굵은 링크는 강조된 컬러 표시와 hover효과가 있다.
외부 링크는 새 탭을 나타내는 아이콘이 생긴다. 강조된 글씨는 이렇게 보인다.
| 제목1 | 제목2 |
|---|---|
| 값1 | 값2 |
Next.js 간단 정리
1. 렌더링
SSR : Next.js 에서 제공하는 가장 기본적인 사전 렌더링. 브라우저의 요청이
있을 때 서버에서 HTML을 완성해서 브라우저로 전달.
SSG : 정적 서비스에서 많이 사용하는 사전 렌더링 기법. 데이터가 변경될 여지가
적다. HTML은 빌드 시점에 완성된다.
ISR : SSG의 단점을 보완하는 사전 렌더링 기법. ex) 게시글의 조회수 빌드 시점은
SSG와 같다.
PPR : 부분 사전 렌더링. Next.js 14버전에서 등장했다. 빌드 시점에 미리 만들어지는
정적인 영역. Shell(정적인 영역), HOles(동적인 영영)
2. 라우팅
Dynamic Routes : 동적으로 경로를 지정하는 방식. App Router : 리액트 서버 컴포넌트를 적극적으로 활용하는 라우터. Route Groups : 경로들을 묶어서 관리하는 기법. 괄호로 폴더명을 지으면 그 아래에 있는 다른 폴더들, 다른 경로들이 하나의 레이아웃을 공유하는 구조.
3. 데이터 페칭
Route Handlers : 어디서든 api를 정의할 수 있다. Server Actions : 'use server' 라는 문장이 있는 스코프의 로직은 서버에서 동작한다. 컴포넌트 안도 가능하다.
Front Matter : 상단에 3개의 하이픈으로 구분된 영역에 작성되는 값. 이후 메타데이터로 활용. remark : 라이브러리. html 플러그인을 집어 넣어서 마크다운을 HTML로 변환.