내 포트폴리오를 JSON 4줄로 무한히 확장 가능하게 만든 방법
Source: Dev.to
새 프로젝트를 포트폴리오에 추가하는 데는 30초면 충분합니다. 컴포넌트 변경도 없고, 새로운 라우트도 없으며, 레이아웃 조정도 필요 없습니다. JSON 4줄과 이미지를 폴더에 넣기만 하면 됩니다.
전형적인 포트폴리오 유지 관리 사이클에 지쳤습니다: 멋진 무언가를 만든 뒤, 포트폴리오 사이트에 연결하는 데 한 시간을 들이고, 레이아웃을 조정하고, 새 카드가 그리드를 깨뜨리지 않는지 확인해야 했죠. 그래서 저는 처음부터 완전히 데이터‑드리븐으로 설계했습니다.
작동 방식은 다음과 같습니다.
진실의 단일 소스
내 포트폴리오의 모든 프로젝트는 하나의 파일인 projects.js에 존재합니다. 각 프로젝트는 정확히 4개의 필드를 가진 객체입니다:
{
id: 'crystal-index',
title: 'Crystal Index',
techStack: 'TypeScript, Next.js, Prisma, SQL, GPT4, React 3 Fiber',
info: 'Custom CMS for cataloguing crystals with structured filters for colour, chakra, and properties, and GPT-4-generated descriptions.',
}
그게 전부입니다. 네 줄. 전체 포트폴리오는 이 객체들의 배열에서 렌더링됩니다.
ID는 세 가지 역할을 수행한다
id 필드는 설계가 흥미로워지는 지점이다. 단순한 식별자가 아니라 동시에 세 가지 용도로 사용된다:
-
GitHub 링크 경로 – 포트폴리오는 기본 GitHub URL에 ID를 붙여 저장소 URL을 만든다.
crystal-index→https://github.com/sammii-hk/crystal-index. -
이미지 파일명 조회 – 자동 생성된 이미지 맵이 ID를 올바른 이미지 파일 및 확장자로 매핑한다.
crystal-index→/assets/images/crystal-index.jpg. -
React 키 – 배열을 매핑할 때 ID가 고유 키 역할을 한다.
하나의 필드, 세 가지 작업. 이는 중복 데이터를 없애고 GitHub 링크, 이미지, React 키가 항상 일치하도록 보장한다.
GitHub 조직 아래의 프로젝트인 경우, ID에 조직 경로가 포함된다. 예: unicorn-poo/succulent. 이미지 유틸리티는 / 로 분리하고 마지막 세그먼트를 파일명 조회에 사용하며, 전체 경로는 올바른 GitHub URL을 만든다.
Auto‑Generated Image Map
각 프로젝트 스크린샷이 .jpg인지 .png인지 일일이 확인하고 싶지 않았습니다. 그래서 이미지 디렉터리를 스캔하고 JSON 맵을 생성하는 빌드 스크립트를 작성했습니다:
import { readdir, writeFile } from 'fs/promises';
import { join } from 'path';
const imagesDir = join(process.cwd(), 'public', 'assets', 'images');
async function generateImageMap() {
const files = await readdir(imagesDir);
const imageMap = {};
files.forEach(file => {
if (file === 'sammii.png') return;
const name = file.replace(/\.(jpg|png)$/, '');
const ext = file.endsWith('.png') ? 'png' : 'jpg';
if (!imageMap[name]) imageMap[name] = ext;
});
const outputPath = join(process.cwd(), 'app', 'common', 'utils', 'image-map.json');
await writeFile(outputPath, JSON.stringify(imageMap, null, 2));
}
generateImageMap();
출력은 간단한 조회표입니다:
{
"crystal-index": "jpg",
"lunary": "png",
"succulent": "png",
"day-lite": "jpg"
}
유틸리티 함수는 어떤 프로젝트 ID든 전체 이미지 경로로 변환합니다:
import imageMapData from './image-map.json';
const imageMap = imageMapData;
export const getImagePath = (projectId) => {
const projectBaseId = projectId.split('/').pop() || projectId;
const extension = imageMap[projectBaseId] || 'jpg';
return `/assets/images/${projectBaseId}.${extension}`;
};
폴더에 이미지를 넣고 스크립트를 실행하면 포트폴리오가 자동으로 이를 인식합니다.
Source: …
컴포넌트 변경 없음
포트폴리오에는 두 가지 완전히 다른 뷰 모드가 있습니다 — 클릭‑투‑확장 모달이 있는 반응형 그리드와 전체 화면 세로 캐러셀. 두 뷰 모두 정확히 같은 projects 배열을 사용합니다.
그리드 뷰는 배열을 순회하며 카드를 렌더링합니다:
{projects.map(project => (
setSelectedProject(project}>
))}
캐러셀 뷰는 같은 배열을 순회하며 전체 너비 슬라이드를 렌더링합니다:
(
)}
/>
ProjectItem은 isGrid prop을 받아서 컴팩트 카드 레이아웃과 확장 레이아웃을 전환합니다. 같은 컴포넌트, 같은 데이터, 두 가지 프레젠테이션. 배열에 프로젝트를 추가하면 추가 작업 없이 두 뷰 모두에 자동으로 표시됩니다.
30초 워크플로우
새 프로젝트를 마치면, 나는 다음과 같이 합니다:
- 스크린샷을 찍는다.
/public/assets/images/에project-name.png이름으로 넣는다.node scripts/generate-image-map.mjs를 실행한다.projects.js에 4줄을 추가한다.- GitHub에 푸시한다.
포트폴리오가 재빌드되고 새 프로젝트가 두 뷰 모두에 나타나며, 올바른 이미지, 올바른 GitHub 링크, 올바른 레이아웃이 적용됩니다.
다섯 단계, 30초, 컴포넌트 파일은 전혀 건드리지 않는다.
Source: …
그 뒤에 있는 철학
이것은 포트폴리오에서 시간을 절약하는 것만이 아니라, 어디서든 사용하는 패턴입니다.
제 점성술 앱인 Lunary는 2,000개가 넘는 기사들을 담은 마법서가 있습니다. 이들은 구조화된 데이터로 저장되고 공유 컴포넌트를 통해 렌더링됩니다. 크리스털이나 타로 카드에 대한 새로운 기사를 추가할 때 UI 코드를 건드릴 필요가 없습니다.
제 출판 도구 Spellcast는 여러 소셜 미디어 계정과 플랫폼을 관리합니다. 계정 설정은 데이터 객체로 되어 있습니다. 새로운 플랫폼을 추가한다는 것은 인터페이스를 다시 빌드하는 것이 아니라 설정에 추가하는 것입니다.
원칙은 언제나 같습니다: 데이터를 프레젠테이션과 분리합니다. 데이터 구조가 무거운 작업을 담당하게 하세요. 컴포넌트는 특정 콘텐츠에 대해 알 필요가 없을 정도로 일반적이어야 합니다.
포트폴리오에 새로운 콘텐츠를 연결하는 데 프로젝트 자체를 만드는 시간보다 더 많이 소비하고 있다면, 아키텍처가 뒤바뀐 것입니다. 포트폴리오가 여러분을 위해 일하도록 만들고, 그 반대가 되지 않게 하세요.
저는 Lunary의 창립자 Sammii이며, 실제로 자신의 출생 차트를 읽는 방법을 가르치는 점성술 앱을 만들었습니다. 저는 솔로 개발자로서 제품을 구축하는 데 있어 기술적 결정을 다룹니다. Dev.to에서 저를 팔로우하거나 GitHub에서 코드를 확인해 보세요.
