-
[Portfolio #1] React로 웹사이트 구성하고 Tailwind로 디자인 입히기Project/Portfolio Website 2025. 12. 17. 23:44
전역 후 개발 공부를 위해 기획했던 포트폴리오 웹사이트를 리액트로 구축하고, 디자인하기로 하였다. 프로젝트의 완성 단계인 지금 회고록을 작성하고자 한다.
총 세 편으로 프로젝트 회고록을 작성할 것이며 순서는 다음과 같다.
- [Portfolio #1] React로 웹사이트 구성하고 Tailwind로 디자인 입히기
- [Portfolio #2] Firebase 연동과 트러블 슈팅 (CRUD & 보안)
- [Portfolio #3-1] Zustand 도입으로 동기적 UI 상태 관리 리팩토링하기
- [Portfolio #3-2] Zustand 도입으로 비동기적 서버 데이터 관리 리팩토링하기 + React Router & 폴더 구조 리팩토링하기
웹사이트 초기 기획

초기 기획안 실행 기술 선정: React와 TypeScript를 이용하여 컴포넌트 기반 아키텍처를 통해 재사용성을 높이고 타입 에러를 방지하기로 했다. Tailwind를 이용하여 스타일링 시간을 단축하고 직관적인 클래스 명을 통해 반응형 디자인을 적용을 하려했다.
구현 목표: 사이트들로 이루어진 기존의 이력서를 탈피하여 웹 앱처럼 부드럽게 작동하는 SPA를 구현하기로 정했다.
컴포넌트 확립: 기능 별로 컴포넌트를 분류할 계획이었고, 메인 화면<Main>에서 크게 네 파트로 나누었다.
- <사진 영역>: PhotoComponent로 따로 분류할 계획이었으나 프로필 부분이라 크게 변경할 계획이 없어 <Main> 안에 남겼다.
- <Contact 영역>: 출신 학교, 연락처 등을 표기한 컴포넌트로 분류했다.
- <Nav 영역>: 당시 Introduce, Portfolio, Project으로 분류할 계획이었으나 대중적으로 사용하는 About Me, Project, Activities, Feedback으로 분류하였다.
- <내용 영역>: 각 <Nav>에 맞는 내용을 연결지었다.
위 분류는 앞으로 다음과 같은 아키텍처를 가지게 된다.
단순히 폴더에 파일을 모아두는 것이 아니라 유지보수를 고려한 구조이다.
- components/: 공통 UI 요소 (Button, Card, Layout)
- projects/ 또는 activities/: 메인 화면의 각 섹션들
- store/: Zustand 스토어 관리 (3편에서 다룰 것임)
- assets/: 이미지 및 커스텀 CSS 파일
이점: 프로젝트 규모가 커져도 원하는 코드를 빠르게 찾을 수 있도록 설계했다.
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
초기 기획을 바탕으로 useState를 활용해 메뉴 탭 전환 기능을 구현하고, Tailwind로 전체적인 레이아웃을 잡았다. 또 components 폴더 아래에 Main.tsx, Portfolio.tsx, Contact.tsx, Introduce.tsx, Nav.tsx 컴포넌트를 두었다.
import { useState } from "react"; import Nav from "./Nav"; import Introduce from "./Introduce"; import Project from "./Project"; import profileImg from "../assets/profile.jpeg"; import Portfolio from "./Portfolio"; import Contact from "./Contact"; export default function Main() { const [activeMenu, setActiveMenu] = useState("Introduce"); // 목록(초기값) 설정 return ( <div className="flex justify-center min-h-screen items-center gap-8 px-4 py-10 md:px-16 md:py-20 bg-white"> <div className="flex flex-col-reverse md:flex-row w-full max-w-5xl min-h-[600px] h-auto gap-8 p-4 items-center md:items-start"> {/* 왼쪽 영역 */} <div className="w-full md:w-1/3 flex flex-col gap-4 h-full"> {/* 사진 영역 */} <div className="flex-1 bg-gray-100 rounded-2xl flex items-center justify-center border-2 border-gray-200 brightness-100"> <img src={profileImg} alt="Profile" className="w-full h-full object-cover rounded-2xl border border-gray-300" /> </div> {/* 연락처 영역 */} <div className="h-[140px] flex-none w-full"> <Contact /> </div> </div> {/* 오른쪽 영역 */} <div className="w-full md:w-2/3 flex flex-col h-full"> <div className="mb-6"> <Nav activeMenu={activeMenu} onMenuClick={setActiveMenu} /> </div> <div className="flex-1 flex flex-col justify-center"> {activeMenu === "Introduce" && <Introduce />} {activeMenu === "Portfolio" && <Portfolio />} {activeMenu === "Project" && <Project />} </div> </div> </div> </div> ); }이후 Main.tsx -> Portfolio.tsx, Introduce.tsx -> AboutMe.tsx, Portfolio.tsx -> Activities.tsx로 직관적인 명칭으로 바꾸었고, 뼈대를 잡았다.
웹사이트 세부 구성 및 디자인 디테일
다음은 About Me 내용과 Project, Activities의 구성과 디자인을 기획했다.
About Me는 처음 소개 글인만큼 나의 철학과 개발 목표를 소개, 기술 스택으로 구성했고, Project, Activities는 둘 다 미니멀한 카드로 디자인적 통일감을 주려고 했다.
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
각 Nav 별 누를 때마다 끊기는 느낌이 있어 구현 목표에 맞게 animate-fade-in-up 스타일을 추가했고, 카드 섹션 별로 주요 키워드를 목차에서 바로 볼 수 있게 했다.
animate-fade-in-up을 적용하는 과정에서 기존의 tailwindcss로 적용이 안되는 버그가 있었다. 이에 index.css에서 직접 원하는 애니메이션 효과를 만들었다.
// index.css @import "tailwindcss"; @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .animate-fade-in-up { animation: fadeInUp 0.5s ease-out both; }직관적인 디자인을 위해 react-icons로부터 아이콘들을 배치했고, 반응형 디자인으로 포인터가 올라가 있을 때(hover), 모바일로 볼 때와 데스크탑(md)으로 보았을 때 차이를 주었다.
주요 css 클래스
flex justify-center min-h-screen items-center gap-8 px-4 py-10 md:px-16 md:py-20 bg-white위 클래스를 통해 단순히 콘텐츠를 가운데 배치하는 것에 그치지 않고, md: 를 활용해 모바일과 데스크탑 환경의 여백을 다르게 설계했다. 모바일에서는 가독성을 위해 여백을 줄이고, 넓은 화면에서는 큰 여백을 주어 시각적인 편안함과 몰입감을 제공하고자 했다.
- justify-center: 가로 방향에서 자식 요소들을 가운데로 모은다.
- items-center: 세로 방향에서 자식 요소들을 가운데로 모은다.
- min-h-screen: 최소 높이를 화면 전체 높이(100vh)로 잡는다.
위 세 클래스를 통해 화면 정중앙에 위치하도록 했다.
flex flex-col-reverse md:flex-row w-full max-w-5xl min-h-[600px] h-auto gap-8 p-4 items-center md:items-start아래 영상과는 다르게 모바일 환경에서는 시각적 편의성을 위해 주요 인터페이스의 순서를 뒤집는(사진을 아래로) flex-col-reverse를 적용했다. 반면 화면이 넓은 데스크탑(md) 환경에서는 시각적 안정감을 위해 flex-row와 items-start를 사용하여 컨텐츠를 좌우로 배치했다.
사용한 Tailwind 핵심:
- 배치: flex, flex-wrap, justify-center, items-center, md
- 크기: min-h-screen, w-full, max-w-5xl, flex-none
- 디테일: rounded-full, transition-all, hover:scale-105
React&TypeScript로 구성한 1차 완성본(flex-col-reverse 미적용)
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
이렇게 Tailwind CSS를 활용해 깔끔한 UI를 완성했지만, 아직은 데이터가 저장되지 않는 정적인 상태이다. 다음 편에서는 별도의 백엔드 없이 Firebase를 연동하여 실시간 Feedback 방명록 기능을 구현하고, 그 과정에서 겪은 보안 규칙(Rules)과 권한 오류를 어떻게 해결했는지 공유하겠다.
'Project > Portfolio Website' 카테고리의 다른 글
[Portfolio #3-2] Zustand 도입으로 비동기적 서버 데이터 관리 + React Router & 폴더 구조 리팩토링하기 (0) 2026.06.04 [Portfolio #3-1] Zustand 도입으로 동기적 UI 상태 관리 리팩토링하기 (0) 2026.01.02 [Portfolio #2] Firebase 연동과 트러블 슈팅 (CRUD & 보안) (0) 2025.12.21