포트폴리오에서 가장 눈에 띄는 부분 중 하나는 프로젝트 섹션입니다.
단순히 “프로젝트 나열”이 아니라,
프로젝트 정보는 따로 data/projects.ts 파일에 배열로 관리합니다.
이렇게 하면 프로젝트를 추가/수정할 때 UI를 건드릴 필요 없이 데이터만 수정하면 자동 반영됩니다.
// data/projects.ts
import { Project } from "@/types/project";
export const projects: Project[] = [
{
title: "검색 성능 개선 실험",
description: "Next.js SSR 캐싱과 이미지 최적화를 통해 초기 로딩 속도를 줄인 프로젝트",
tags: ["Next.js", "TypeScript", "Tailwind"],
github: "https://github.com/username/search-perf",
demo: "https://search-perf.vercel.app",
cover: "/thumbs/search.webp",
highlights: ["LCP -28%", "A11y 97점", "코드 스플리팅 적용"],
metrics: [
{ label: "LCP", value: "-28%" },
{ label: "A11y", value: "97" },
{ label: "Build", value: "-15%" },
],
caseStudy: {
problem: "초기 렌더링 지연으로 사용자 이탈률이 증가",
approach: [
"Next/Image와 WebP, AVIF 적용",
"코드 스플리팅과 dynamic import",
"SSR 캐싱과 불필요 렌더 제거",
],
result: ["LCP -28%", "접근성 점수 97점 달성"],
roleStack: "개발 100% · Next.js, React, TypeScript, Tailwind",
},
},
];
카드 단위로 나누어 정보를 보여주되, 필요하다면 <details>를 이용해
Case Study를 열어볼 수 있도록 했습니다.
// components/ProjectCard.tsx
import Image from "next/image";
import { Project } from "@/types/project";
export default function ProjectCard({
title, description, tags, github, demo,
cover, highlights = [], metrics = [], caseStudy,
}: Project) {
return (
<article className="rounded-2xl border p-6 shadow-sm transition hover:-translate-y-1">
{cover && (
<div className="relative mb-4 h-44 w-full overflow-hidden rounded-xl">
<Image src={cover} alt={`${title} thumbnail`} fill className="object-cover" />
</div>
)}
<h3 className="text-lg font-semibold">{title}</h3>
<p className="mt-2 mb-4 text-sm">{description}</p>
{/* 태그 */}
<ul className="mb-4 flex flex-wrap gap-2 text-xs">
{tags.map((tag) => (
<li key={tag} className="rounded-full bg-zinc-100 px-2 py-1">#{tag}</li>
))}
</ul>
{/* 성과 지표 */}
{!!metrics.length && (
<div className="mb-4 grid grid-cols-3 gap-2 text-center">
{metrics.map((m) => (
<div key={m.label} className="rounded-lg border p-2">
<p className="text-sm font-bold">{m.value}</p>
<p className="text-[10px] text-zinc-500">{m.label}</p>
</div>
))}
</div>
)}
{/* 링크 */}
<div className="flex gap-3">
<a href={github} target="_blank" className="text-blue-500 underline">GitHub</a>
{demo && <a href={demo} target="_blank" className="text-blue-500 underline">Demo</a>}
</div>
{/* Case Study */}
{caseStudy && (
<details className="mt-4">
<summary className="cursor-pointer text-sm font-medium">Case Study</summary>
<div className="mt-2 space-y-2 text-sm">
<section>
<h4 className="font-semibold">문제 정의</h4>
<p>{caseStudy.problem}</p>
</section>
<section>
<h4 className="font-semibold">접근</h4>
<ul className="list-disc pl-5">
{caseStudy.approach?.map((a) => <li key={a}>{a}</li>)}
</ul>
</section>
<section>
<h4 className="font-semibold">결과</h4>
<ul className="list-disc pl-5">
{caseStudy.result?.map((r) => <li key={r}>{r}</li>)}
</ul>
</section>
{caseStudy.roleStack && (
<section>
<h4 className="font-semibold">역할 & 스택</h4>
<p>{caseStudy.roleStack}</p>
</section>
)}
</div>
</details>
)}
</article>
);
}
👉 성과 지표와 Case Study까지 담을 수 있어,
단순한 프로젝트 나열이 아니라 문제 해결 과정을 보여줄 수 있습니다.
이제 Projects 컴포넌트에서는 단순히 데이터를 돌려주면 됩니다.
// app/Projects.tsx
import { projects } from "@/data/projects";
import ProjectCard from "@/components/ProjectCard";
export default function Projects() {
return (
<section id="projects" className="min-h-screen py-20">
<h2 className="mb-8 text-2xl font-bold">📁 프로젝트</h2>
<div className="grid gap-8 sm:grid-cols-2">
{projects.map((p) => <ProjectCard key={p.title} {...p} />)}
</div>
</section>
);
}
1. 데이터 중심 구조
→ 프로젝트 추가/수정이 간단 (데이터만 수정하면 UI 자동 반영)
2. 성과 지표 강조
→ 신입 지원자라도 “무엇을 개선했는지”를 수치로 보여줄 수 있음
3. Case Study
→ “무엇을 만들었는가”보다 중요한 “왜, 어떻게 개선했는가”를 전달
4. 확장성
→ 프로젝트가 많아져도 필터/정렬 기능을 쉽게 추가 가능
오늘 작업한 프로젝트 섹션은, 단순한 목록을 넘어서
성과·지표·케이스 스터디까지 담을 수 있도록 발전시켰습니다.
앞으로는 프로젝트가 늘어나면 스택별 필터 / 정렬 옵션도 넣어서,
리쿠르터나 면접관이 원하는 프로젝트를 더 빠르게 찾을 수 있도록 개선할 계획입니다.