
UI/UX
개인 포트폴리오 사이트 제작
최근 빠르게 발전하는 바이브 코딩을 이용해 작업물을 보여주는 동시에 AI 활용 역량까지 보여주는 개인 포트폴리오 사이트를 제작했습니다. Figma로 제작한 디자인을 기반으로 AI 에이전트와 함께 개발, 테스트와 배포까지 진행했으며, 새로운 작업물을 지속적으로 업데이트 할 수 있는 CMS 화면까지 설계했습니다.

머릿속에 있던 포트폴리오를 실제 제품으로
2026년 5월, 시간이 지나 기존에 사용하던 포트폴리오를 업데이트 해야겠다고 생각하던 중, 정해진 페이지 안에 결과물을 배치하는 PDF 포트폴리오보다 제가 의도한 흐름과 인터렉션을 자유롭게 보여줄 수 있는 웹 형식이 더 적합하다고 생각했습니다.
이전 직장에서 AI를 활용해 웹앱을 개발한 경험이 있던 저는 그 경험을 살려 AI를 활용해 아이디어를 실제 제품으로 구체화하고 웹으로 구현하는 역량까지 보여준다면 그 자체가 하나의 새로운 작업물이 될 수 있다고 생각했습니다.
또한 지속적으로 업데이트 할 떄에도 새로운 작업물만 추가하면 된다는 점도 웹 포트폴리오 형식으로 제작하게 된 중요한 이유였습니다. 일회성 결과물이 아닌, 경험과 경력에 따라 지속적으로 성장하는 포트폴리오를 목표로 프로젝트를 시작했습니다.
도메인 한 장 차이로 나뉘는 사용성
이 사이트는 저와 포트폴리오를 보고 있는 한 사람 사이의 공간이라고 생각했습니다. 메인 페이지에서는 채용 담당자를 최우선 사용자로 두고, 짧은 몇 번의 스크롤이 내려가는 동안 제가 누구인지 몰입감 있게 전달하는 것에 집중했습니다.
반면 관리자 페이지에서는 사이트를 지속적으로 운영하는 저 자신을 최우선 사용자로 설정했습니다. 콘텐츠를 추가하고 수정하는 과정이 번거롭다면 업데이트에 귀찮음을 느낄 수 있기때문에 최대한 제가 쓰기 편하도록 설계했습니다.

코스 먼저, 그다음 자유롭게
메인 페이지 초반은 음식점의 코스 요리처럼 구성했습니다. 인트로와 소개, 경력, 대표 작업물까지는 제가 전달하고 싶은 내용을 정해진 순서로 경험하도록 설계했습니다. 한 번의 스크롤 입력으로 한 장면씩 이동하게 해 각각의 콘텐츠에 집중할 수 있도록 설계했습니다.
충분한 맥락을 전달한 이후의 보여지는 작업물 갤러리는 자유롭게 탐색할 수 있도록 일반 스크롤로 전환됩니다.
초반에는 가장 보여주고 싶은 내용을 선별해 보여주고, 이후에는 방문자가 관심 있는 카테고리와 프로젝트를 직접 선택하도록 자유도를 열어두었습니다.

에이전트에게 의존하지 않는 디자인
메인 화면은 AI가 임의로 디자인하게 두지 않고 Figma에서 직접 설계했습니다. 완성된 화면을 Figma MCP로 연결해 디자인에 가깝게 구현하도록 했으며, 필요한 경우 참고 이미지와 구체적인 수치와 설명을 함께 전달했습니다.
CSS 프레임워크가 만드는 획일적인 인상을 줄이고 세부적인 디자인을 직접 조정하기 위해 Astro와 Native CSS를 중심으로 구현했습니다. 에이전트가 만든 결과가 의도와 다를 때는 화면에서 느껴지는 문제와 원하는 인상을 반복적으로 설명하며 수정했습니다.

뻔한 에디터는 그만
새로운 경력과 작업물이 계속 추가되는 포트폴리오의 특성상, 내용을 수정할 때마다 코드를 변경하는 방식은 적합하지 않다고 판단했습니다. 프로필과 경력, 작업물, 노출 순서와 공개 상태를 직접 관리할 수 있는 관리자 CMS를 처음부터 주요 기능으로 계획했습니다.
기존 오픈소스 에디터를 그대로 적용하기보다 포트폴리오의 콘텐츠 구조와 디자인 규칙에 최적화된 편집 경험을 만들고 싶었습니다. 공개 화면은 방문자를 위한 제품으로, 관리자 화면은 콘텐츠를 작성하는 저를 위한 별도의 제품으로 설계했습니다.

일관된 결과를 만드는 블록 에디터
작업물마다 내용은 달라도 포트폴리오 전체의 디자인 스타일은 유지되어야 했습니다. 이를 위해 본문을 Heading, Paragraph, Image, Gallery, Quote, Website, Divider와 같은 블록으로 구분했습니다.
각 블록에는 글의 행간과 간격, 정렬, 콘텐츠 너비 등 미리 정의한 규칙을 적용했습니다. 사용자는 블록을 원하는 순서로 조합할 수 있지만, 최종 결과물은 사이트의 일관된 디자인 언어 안에서 완성됩니다.
편집 내용은 오른쪽 미리보기에 저장 전부터 실시간으로 반영됩니다. 실제 페이지를 반복해서 열어보는 과정을 줄이고, 필요할 때는 편집기와 미리보기 사이의 경계를 드래그해 확인하고 싶은 영역을 더 크게 볼 수 있도록 했습니다.

직접 검증하는 사용성
실제로 직접 만든 에디터를 사용해 긴 원고를 작성하면서 처음에는 예상하지 못했던 불편이 생겼습니다. 바로 내용이 길어질수록 상단에 고정된 블록 추가 버튼으로 다시 이동하기 어려워졌고, 새로운 블록을 추가할 때마다 편집 흐름이 끊겼습니다.
이를 해결하기 위해 본문을 스크롤해도 블록 추가 도구가 편집기 상단을 따라오도록 수정했습니다. 또한 콘텐츠 너비를 변경한 뒤 새 블록을 만들면 기존 기본값으로 돌아가던 문제를 발견하여 새 블록이 현재 선택한 설정을 그대로 상속하도록 개선했습니다.
기능을 만든 뒤 끝내는 것이 아니라, 제가 직접 사용하며 발견한 문제를 제품에 다시 반영하여 개선하는 과정을 반복하여 완성도를 높였습니다.
직관적인 감각을 구현하기까지
가장 많은 시행착오를 겪은 부분은 GSAP를 활용한 Career 스크롤 인터랙션이었습니다. 머릿속으로 생각한 움직임은 분명했지만, 사람이 느끼는 자연스러운 속도와 체류감을 자연어만으로 설명해 구현하는 일은 쉽지 않았습니다.
초기 결과에서는 스크롤이 강제로 고정되거나, 트랙패드의 관성으로 여러 항목이 한 번에 지나가는 문제가 있었습니다. 활성 항목이 펼쳐지며 높이가 달라지고, 모바일에서는 첫 번째와 두 번째 항목이 동시에 강조되는 현상도 발생했습니다.
화면에서 발생하는 현상과 원하는 감각을 계속 구체적으로 설명하고, 참고 자료와 애니메이션 곡선을 전달하며 수정을 반복했습니다. 최종적으로 페이지 스크롤 자체는 막지 않으면서 각 경력에 잠시 머물 수 있는 구간을 만들고, 항목 사이에는 cubic-bezier(.4, 0, .6, 1) 곡선을 적용했습니다.
import assert from "node:assert/strict";
import test from "node:test";
import { createCubicBezierEasing, remapProgressWithDwell } from "../src/lib/scroll-dwell.ts";
const careerEase = createCubicBezierEasing(0.4, 0, 0.6, 1);
const anchors = [0, 0.25, 0.5, 0.75, 1];
test("career progress holds briefly around every card without changing its anchor", () => {
assert.equal(remapProgressWithDwell(0.02, anchors, 0.12, careerEase), 0);
assert.equal(remapProgressWithDwell(0.23, anchors, 0.12, careerEase), 0.25);
assert.equal(remapProgressWithDwell(0.25, anchors, 0.12, careerEase), 0.25);
assert.equal(remapProgressWithDwell(0.27, anchors, 0.12, careerEase), 0.25);
assert.equal(remapProgressWithDwell(0.98, anchors, 0.12, careerEase), 1);
});
test("career progress uses the symmetric cubic bezier through each transition", () => {
const midpoint = remapProgressWithDwell(0.375, anchors, 0.12, careerEase);
assert.ok(Math.abs(midpoint - 0.375) < 0.0002);
});
test("career dwell remapping remains monotonic across the full scroll range", () => {
const values = Array.from({ length: 201 }, (_, index) => (
remapProgressWithDwell(index / 200, anchors, 0.12, careerEase)
));
values.slice(1).forEach((value, index) => {
assert.ok(value >= values[index], `progress moved backward at sample ${index + 1}`);
});다양한 환경에서 같은 경험을 제공하기 위해
데스크톱에서 완성된 화면도 모바일에서는 카드가 겹치거나 레이아웃이 틀어지는 문제가 반복적으로 발생했습니다. 직접 보유하지 않은 기기에서는 주변 사람들에게 비공개 링크를 전달해 사용성을 확인하고, 화면 크기와 입력 방식에 따라 나타나는 문제를 계속 수정했습니다.
첫 화면과 마지막 화면의 정렬, 모바일 브라우저의 가변적인 화면 높이, 마우스 휠과 트랙패드의 입력 차이 등을 하나씩 검증했습니다. 눈으로 느껴지는 문제를 전달한 뒤에는 에이전트가 브라우저상의 위치와 진행률을 측정하고, 자동 테스트를 추가해 같은 문제가 다시 발생하지 않도록 했습니다.
배포 이후에도 계속된 수정
사이트 운영 환경은 여러 서비스를 비교한 뒤 도메인 관리부터 배포까지 하나의 환경에서 처리할 수 있는 Cloudflare를 선택했습니다. 콘텐츠는 D1에, 업로드한 이미지는 R2에 저장하고, Astro 기반 사이트를 Cloudflare Workers로 배포했습니다.
운영을 준비하며 Google PageSpeed로 사이트를 점검했을 때 많은 이미지로 인해 성능 점수가 낮아지는 문제를 발견했습니다. 해결 방안을 검토한 뒤 반응형 WebP 이미지 생성과 지연 로딩, 캐시 정책 등을 적용해 공개 화면의 로딩 부담을 줄였습니다.
이후 SEO와 키보드 접근성, 모션 축소 설정, 관리자 인증과 입력값 검증, 데이터 백업과 오류 상황의 대체 콘텐츠까지 보완했습니다. 화면을 구현하는 데서 끝내지 않고 실제로 운영할 수 있는 제품에 필요한 조건을 하나씩 갖춰나갔습니다.

AI를 개발 도구에서 협업 파트너로
프로젝트 초반에는 Codex를 제가 정한 기획과 디자인을 코드로 구현하는 도구로 활용했습니다. 하지만 기능이 복잡해질수록 구현 가능성을 함께 검토하고, 발생한 오류의 원인을 분석하며 더 나은 해결책을 찾는 개발 파트너에 가까워졌습니다.
대부분의 개발은 Codex를 중심으로 진행했습니다. 토큰 사용량과 문제의 성격에 따라 Antigravity 환경의 Gemini를 활용해 간단한 수정을 진행하거나, 한 모델이 해결 방향을 찾지 못하는 문제를 다른 모델의 관점에서 다시 분석하기도 했습니다.
중요한 것은 어떤 모델을 사용했는지가 아니라, 제가 해결하려는 문제와 원하는 결과를 명확히 전달하고 제안된 해결책을 직접 비교하고 판단하는 것이었습니다. AI의 결과를 수동적으로 받아들이기보다 디자인과 기능의 최종 의사결정은 직접 내렸습니다.
계속해서 성장 중인 포트폴리오
이 프로젝트는 2026년 5월에 시작해 현재도 계속 운영하고 있습니다. 일주일에 한두 시간씩 새로운 기능을 추가하고 불편한 점을 개선하고 있으며, 앞으로도 새로운 작업물과 콘텐츠를 지속적으로 채워나갈 예정입니다.
개발을 전문적으로 할 수 없었다면 이전에는 머릿속으로만 상상했을 구성을, 이제는 AI 에이전트와 협력해 실제 웹 제품으로 구현할 수 있게 되었습니다. 디자인뿐 아니라 기획과 개발, 테스트와 배포까지 프로젝트의 전체 과정을 경험하면서 제 역할의 범위를 스스로 확장할 수 있었습니다.
