Frontend
넘겨받은 WordPress 홈페이지 처음부터 다시 만든 이유
기타··수정됨 2026.07.22

멀쩡한 사이트를 왜 다시 만들었을까
현재 홈페이지의 가장 최근 버전은 이전 개발자가 작년에 WordPress로 만들어 놓은 것이었다.
처음엔 그냥 쓰면 된다고 생각했다. 그런데 막상 운영을 맡고 보니 세 가지 문제가 동시에 터졌다.
문제 1. 콘텐츠를 바꾸는 게 왜 이렇게 어렵지?

WordPress 자체가 콘텐츠 관리 목적으로 만들어진 시스템인데, 아이러니하게도 뭔가 바꾸는 게 생각보다 어려웠다.
구체적으로 이런 상황들이 반복됐다.
프로젝트 목록에 새 항목을 추가하려면 테마 에디터를 열어야 했다
페이지 레이아웃을 조금 바꾸려면 어떤 플러그인이 어떤 CSS를 덮어쓰는지 추적해야 했다
이미지 하나를 교체하는 데도 미디어 라이브러리, 특정 플러그인 설정, 테마 옵션을 함께 건드려야 했다
WordPress가 나쁜 게 아니었다. 이전 개발자가 여러 플러그인을 조합해서 구성한 방식이 우리 상황과 맞지 않았던 것이다. 그 조합의 의도를 알지 못한 채 유지보수를 이어받으니 모든 수정이 지뢰밭처럼 느껴졌다.
문제 2. WordPress 코드를, 우리가 읽을 수가 없었다

솔직하게 말하면 이게 가장 결정적이었다.
WordPress는 PHP 기반이다. 새 개발자 입장에서 PHP 템플릿 구조, functions.php, 훅(hook) 시스템, 그리고 플러그인들이 어떻게 얽혀 있는지를 파악하는 데만 상당한 시간이 필요했다. 문서도 없었고, 이전 개발자에게 물어볼 수도 없었다.
결국 "이걸 이해해서 고치는 것"과 "처음부터 우리가 아는 방식으로 만드는 것" 중 하나를 선택해야 했다.
문제 3. 성능과 SEO가 너무 나빴다
Google PageSpeed Insights를 돌려보니 LCP(Largest Contentful Paint)가 기준치를 훨씬 넘겼고, CLS(Cumulative Layout Shift)도 불안정했다. 검색 노출도 기대에 훨씬 못 미쳤다.
최고로 가장 불만인건 홈페이지 자체가 너무 느.리.다
WordPress에서 성능을 잡으려면 캐싱 플러그인, 이미지 최적화 플러그인, CDN 설정 등을 각각 챙겨야 한다. 반면 우리가 원하는 건 "애초에 느리지 않은 구조"였다.
그래서 처음부터 다시 만들기로 했다
세 가지 문제를 놓고 보니 방향이 명확해졌다.
우리 팀이 읽고, 고치고, 확장할 수 있는 코드베이스로 만들자.
기술 스택을 고를 때 기준은 하나였다. 나 혼자 개발을 진행해야 하기에 TypeScript로 작업하고, 프레임워크가 성능 최적화를 기본으로 처리해주며, 운영 부담이 최소화되는 조합.
스택 선택: 각각 왜 골랐나
Next.js 16 App Router
서버 컴포넌트와 클라이언트 컴포넌트를 명시적으로 나누는 구조가 처음엔 낯설었다. 하지만 이 구조 덕분에 "어떤 데이터를 서버에서 가져오고, 어떤 인터랙션을 클라이언트에서 처리할지"를 강제로 설계하게 된다.
그 결과로 페이지가 기본적으로 빠르다. 클라이언트로 내려가는 JavaScript 번들이 줄어들고, 초기 렌더링이 서버에서 완성된 HTML로 오기 때문이다.
SEO 면에서도 generateMetadata와 내장 sitemap.ts / robots.ts로 메타데이터 관리가 코드 레벨에서 가능하다.
// app/(public)/project/[slug]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const project = await getProjectBySlug(params.slug);
return {
title: `${project.title} | 기웍스`,
description: project.description,
openGraph: {
images: [project.thumbnail],
},
};
}
WordPress에서 SEO 플러그인을 설치하고 설정하던 것과 비교하면 확연히 다르다.
Neon + Drizzle ORM
데이터베이스로 PostgreSQL을 선택했고, 호스팅은 Neon을 골랐다.
Neon은 서버리스 PostgreSQL이다. 별도 DB 서버를 관리하지 않아도 되고, Vercel에 배포했을 때 연결 풀링도 내장되어 있다. 트래픽이 많지 않은 기업 홈페이지에 RDS 같은 걸 붙이는 건 오버스펙이라 판단했다.
ORM은 Drizzle을 선택했다. TypeScript 네이티브라 스키마가 코드 그 자체다. DB 테이블 구조를 바꾸면 타입이 즉시 반영된다.
// lib/db/schema.ts
export const projects = pgTable("projects", {
id: serial("id").primaryKey(),
slug: text("slug").notNull().unique(),
title: text("title").notNull(),
category: text("category").notNull(),
youtubeUrl: text("youtube_url"),
published: boolean("published").default(false),
createdAt: timestamp("created_at").defaultNow(),
});
스키마를 바꾸면 TypeScript 컴파일러가 영향을 받는 쿼리를 즉시 잡아준다. "DB를 바꿨는데 코드에 반영이 안 된" 상태가 일어날 수 없다.
Vercel Blob
이미지와 파일 업로드는 Vercel Blob으로 처리했다. 어드민에서 프로젝트 썸네일이나 회사 소개서 PDF를 업로드할 때 S3 버킷을 따로 만들고 IAM 정책을 설정하는 번거로움 없이 몇 줄로 해결된다.
import { put } from "@vercel/blob";
const blob = await put(file.name, file, { access: "public" });
return blob.url; // 바로 사용 가능한 CDN URL
운영 초기엔 파일 스토리지 설정에 시간을 쓰고 싶지 않았고, 실제로 쓰지 않았다.
NextAuth v5
어드민 패널 인증에 NextAuth v5를 사용했다. 설정이 간단하고 App Router와 자연스럽게 통합된다. 미들웨어 한 줄로 /admin/* 경로 전체를 보호할 수 있다.
실제로 만든 것들
공개 페이지
페이지 주요 내용 홈 애니메이션 히어로, 사업 분야, 프로젝트 슬라이더, 뉴스룸 회사소개 연혁, 조직도, 인증서, 회사소개서 다운로드 사업 분야 서비스 카드 그리드 프로젝트 필터링 가능한 목록, 슬러그 기반 상세 페이지, YouTube 임베드 경영공시 연도별 재무 테이블, 탭 전환 고객지원 카카오맵 연동, 문의 폼..등

어드민 패널
CMS가 없으니 직접 만들었다. 프로젝트 등록/수정/삭제, 뉴스 관리, 문의 내역 확인이 가능하다. WordPress 대시보드를 쓰던 것과 달리, 우리 서비스에 꼭 필요한 기능만 있다.

자동화된 SEO
각 페이지
generateMetadata로 타이틀, 디스크립션, OG 이미지 자동 생성프로젝트 상세 페이지는
opengraph-image.tsx로 동적 OG 이미지 생성JSON-LD 구조화 데이터 (Organization, BreadcrumbList, FAQPage)
sitemap.ts와robots.ts로 크롤러 대응 자동화
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const projects = await getPublishedProjects();
const projectUrls = projects.map((p) => ({
url: `https://giworks.co.kr/project/${p.slug}`,
lastModified: p.updatedAt,
}));
return [...staticPages, ...projectUrls];
}
가장 까다로웠던 것
서버 컴포넌트와 클라이언트 컴포넌트의 경계
App Router의 가장 큰 개념 전환이 여기에 있었다.
예를 들어 스크롤 애니메이션이 필요한 섹션은 클라이언트 컴포넌트여야 한다. 그런데 그 섹션이 DB에서 데이터를 받아야 한다면? 데이터 패칭은 서버 컴포넌트에서 하고, 그 결과를 props로 클라이언트 컴포넌트에 내려줘야 한다.
이 구분을 처음부터 명확히 하지 않으면 "use client" 선언이 컴포넌트 트리 위쪽으로 계속 올라가게 된다. 그러면 서버 컴포넌트의 이점이 사라진다.
결국 우리는 규칙을 하나 정했다. 데이터 패칭은 무조건 페이지 또는 레이아웃 서버 컴포넌트에서. 클라이언트 컴포넌트는 props를 받아서 보여주는 역할만.
디자인 시스템을 직접 만든 것
WordPress 테마에서 벗어나면서 디자인을 완전히 새로 구성해야 했다. 하지만 또 미리 워드프레스에서 디자이너분들이 만들어준 메인홈 디자인을 벗어나고 싶진 않았다. Tailwind CSS v4를 기반으로 색상 토큰, 타이포그래피, 애니메이션 클래스를 직접 정의했다.
브랜드 컬러로 blue-500을 메인 액센트로 잡고, 스크롤 진입 시 fade+slide 애니메이션을 공통화했다. 처음엔 페이지마다 따로 만들었다가 나중에 공통 클래스로 통일하는 과정이 있었다.
디자인 시스템을 직접 만드는 건 시간이 걸리지만, 완성되고 나면 "이 컴포넌트가 어떤 플러그인에 의존하는가"를 신경 쓸 필요가 없어진다.
달라진 것과 배운 것
콘텐츠 수정 흐름
이전엔 WordPress 대시보드 → 플러그인 설정 → 테마 옵션 순으로 여러 군데를 건드려야 했다. 지금은 어드민 패널에서 폼을 채우고 저장하면 끝이다.
프로젝트를 새로 추가하거나 뉴스를 올리는 데 걸리는 시간이 체감상 절반 이하로 줄었다.
배포
git push 하면 Vercel이 자동으로 빌드하고 배포한다. PR을 올리면 프리뷰 URL이 생긴다. WordPress 시절 FTP나 SSH로 파일을 올리던 것과는 다른 세계다.
다음엔 이렇게 할 것
솔직히 말하면 어드민 패널을 직접 만드는 건 꽤 많은 시간이 들었다. 콘텐츠 관리 요구사항이 더 복잡해진다면 Payload CMS나 Sanity 같은 헤드리스 CMS를 연결하는 쪽을 먼저 검토할 것이다.
또 초기에 컴포넌트 구조를 더 꼼꼼히 설계했더라면 나중에 "use client" 를 걷어내는 리팩터링 시간을 아낄 수 있었을 것이다.
마치며
WordPress를 다시 만들기로 결정한 순간엔 확신이 없었다. 이미 있는 걸 버리는 게 맞는 건지, 그냥 WordPress를 더 파는 게 나은 건지.
돌아보면 그 결정이 옳았다. 지금은 팀 누구든 코드를 열고, 읽고, 수정할 수 있다. 배포가 두렵지 않다. 콘텐츠 업데이트에 개발자가 필요 없다.
기술 스택이 멋있어서가 아니라, 우리 팀이 계속 관리할 수 있는 코드를 가지게 됐다는 것 — 그게 이 프로젝트에서 가장 중요한 성과다.
읽기 도구
약 11분 읽기
이 글이 도움이 되었나요?