Add new project experience or work experience with images to the portfolio website. Use when: adding a project to the Projects tab, adding a new job/company to the Experience tab, updating achievements for an existing role, or adding project screenshots/images. Triggers: "add project", "add experience", "새 프로젝트 추가", "경력 추가", "포트폴리오 업데이트".
| Purpose | File |
|---|---|
| Skill & experience data | data/portfolio.ts |
| Project image paths | data/portfolio.ts → projectImages object |
| Project text (KO) | app/[locale]/dictionaries/ko.json → projects.<key> |
| Project text (EN) | app/[locale]/dictionaries/en.json → projects.<key> |
| Project text (ZH) | app/[locale]/dictionaries/zh.json → projects.<key> |
| Projects list component | components/portfolio/projects-content.tsx |
| Image assets | public/projects/<project-key>/ |
Copy screenshots to public/projects/<project-key>/ (e.g., public/projects/my-project/1.png).
Use lowercase-kebab-case for the folder name. Supported formats: .png, .jpg, .jpeg, .webp.
data/portfolio.tsAdd an entry to the projectImages object (keep as const):
projectImages = {
// existing entries …
myProject: [
"/projects/my-project/1.png",
"/projects/my-project/2.png",
],
} as const;
app/[locale]/dictionaries/ko.jsonInsert under the "projects" object. content is a string array — each item renders as one bullet point:
"myProject": {
"title": "프로젝트 제목",
"subtitle": "한 줄 설명",
"content": [
"주요 성과 1",
"주요 성과 2"
]
}
en.json and Chinese to zh.jsonSame structure. Keep the same key as step 3 (myProject).
components/portfolio/projects-content.tsxAdd an entry to the projects array inside useMemo:
{
title: dict.projects.myProject.title,
subtitle: dict.projects.myProject.subtitle,
content: dict.projects.myProject.content,
images: projectImages.myProject,
},
Insert at the desired display position (top = first, newer projects first).
Run in order:
pnpm typecheck # catch missing dict keys or projectImages keys
pnpm lint # ESLint auto-fix
pnpm build # full Next.js build check
experiences in data/portfolio.tsThe Experience interface requires company, position, period, description, achievements, and technologies. All text fields must have ko, en, zh locales:
{
company: "회사명 (Company Name)",
position: { ko: "직책", en: "Job Title", zh: "职位" },
period: {
ko: "YYYY년 MM월 – YYYY년 MM월 (N년 N개월)",
en: "Mon YYYY – Mon YYYY (N years N months)",
zh: "YYYY年M月 – YYYY年M月 (N年N个月)",
},
description: {
ko: "한국어 회사/역할 설명 (2-3 문장)",
en: "English company/role description (2-3 sentences)",
zh: "中文公司/职位描述 (2-3句)",
},
achievements: {
ko: ["성과 1", "성과 2"],
en: ["Achievement 1", "Achievement 2"],
zh: ["成就 1", "成就 2"],
},
technologies: ["Tech1", "Tech2", "Tech3"],
},
Insert at the top of the experiences array (most recent first).
Update all three language dictionaries for the new experience entry.
pnpm typecheck
pnpm lint
pnpm build
public/projects/<key>/projectImages.<key> added to data/portfolio.tsprojects-content.tsx useMemo arrayexperiences array updated in data/portfolio.tspnpm typecheck passespnpm build passesko.json but forgetting en.json or zh.json causes a TypeScript error in getDictionary.public/ folder name exactly.as const removal: Removing as const from projectImages breaks readonly string[] types in ProjectImageSwiper.onPress vs onClick: Any new HeroUI buttons in related components must use onPress, not onClick.