-
Notifications
You must be signed in to change notification settings - Fork 5
[운영] SEO 및 성능 개선 #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[운영] SEO 및 성능 개선 #148
Changes from all commits
9faa189
29bb44d
88cb50f
6b98c93
84a2bbc
4ac93bd
693927a
bc504c7
e6416a7
c073d6e
1b4c397
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,42 +1,156 @@ | ||
| { | ||
| "breadcrumb": { | ||
| "get-started": "시작하기", | ||
| "get-docker": "Docker 설치", | ||
| "docker-overview": "Docker 개요", | ||
| "introduction": "소개", | ||
| "build-and-push-first-image": "첫 번째 이미지 빌드 및 푸시", | ||
| "develop-with-containers": "컨테이너로 개발", | ||
| "get-docker-desktop": "Docker Desktop 설치", | ||
| "whats-next": "다음 단계", | ||
| "docker-concepts": "Docker 개념", | ||
| "the-basics": "기본 사항", | ||
| "what-is-a-container": "컨테이너란?", | ||
| "what-is-a-registry": "레지스트리란?", | ||
| "what-is-an-image": "이미지란?", | ||
| "what-is-docker-compose": "Docker Compose란?", | ||
| "building-images": "이미지 빌드", | ||
| "understanding-image-layers": "이미지 레이어 이해", | ||
| "writing-a-dockerfile": "Dockerfile 작성", | ||
| "build-tag-and-publish-an-image": "이미지 빌드, 태그, 게시", | ||
| "using-the-build-cache": "빌드 캐시 사용", | ||
| "multi-stage-builds": "멀티 스테이지 빌드", | ||
| "running-containers": "컨테이너 실행", | ||
| "multi-container-applications": "멀티 컨테이너 애플리케이션", | ||
| "overriding-container-defaults": "컨테이너 기본값 재정의", | ||
| "persisting-container-data": "컨테이너 데이터 유지", | ||
| "publishing-ports": "포트 게시", | ||
| "sharing-local-files": "로컬 파일 공유", | ||
| "docker-workshop": "Docker 워크샵", | ||
| "resources": "리소스", | ||
| "workshop": "워크샵", | ||
| "02_our_app": "애플리케이션 컨테이너화", | ||
| "03_updating_app": "애플리케이션 업데이트", | ||
| "04_sharing_app": "애플리케이션 공유", | ||
| "05_persisting_data": "데이터베이스 유지", | ||
| "06_bind_mounts": "바인드 마운트 사용", | ||
| "07_multi_container": "멀티 컨테이너 애플리케이션", | ||
| "08_using_compose": "Docker Compose 사용", | ||
| "09_image_best": "이미지 빌드 모범 사례", | ||
| "10_what_next": "Docker 워크숍 이후 단계" | ||
| "segments": { | ||
| "get-started": { | ||
| "name": "시작하기", | ||
| "linkable": true | ||
| }, | ||
| "get-docker": { | ||
| "name": "Docker 설치", | ||
| "linkable": true | ||
| }, | ||
| "docker-overview": { | ||
| "name": "Docker 개요", | ||
| "linkable": true | ||
| }, | ||
| "introduction": { | ||
| "name": "소개", | ||
| "linkable": true | ||
| }, | ||
| "build-and-push-first-image": { | ||
| "name": "첫 번째 이미지 빌드 및 푸시", | ||
| "linkable": true | ||
| }, | ||
| "develop-with-containers": { | ||
| "name": "컨테이너로 개발", | ||
| "linkable": true | ||
| }, | ||
| "get-docker-desktop": { | ||
| "name": "Docker Desktop 설치", | ||
| "linkable": true | ||
| }, | ||
| "whats-next": { | ||
| "name": "다음 단계", | ||
| "linkable": true | ||
| }, | ||
| "docker-concepts": { | ||
| "name": "Docker 개념", | ||
| "linkable": false | ||
| }, | ||
| "the-basics": { | ||
| "name": "기본 사항", | ||
| "linkable": false | ||
| }, | ||
| "what-is-a-container": { | ||
| "name": "컨테이너란?", | ||
| "linkable": true | ||
| }, | ||
| "what-is-a-registry": { | ||
| "name": "레지스트리란?", | ||
| "linkable": true | ||
| }, | ||
| "what-is-an-image": { | ||
| "name": "이미지란?", | ||
| "linkable": true | ||
| }, | ||
| "what-is-docker-compose": { | ||
| "name": "Docker Compose란?", | ||
| "linkable": true | ||
| }, | ||
| "building-images": { | ||
| "name": "이미지 빌드", | ||
| "linkable": false | ||
| }, | ||
| "understanding-image-layers": { | ||
| "name": "이미지 레이어 이해", | ||
| "linkable": true | ||
| }, | ||
| "writing-a-dockerfile": { | ||
| "name": "Dockerfile 작성", | ||
| "linkable": true | ||
| }, | ||
| "build-tag-and-publish-an-image": { | ||
| "name": "이미지 빌드, 태그, 게시", | ||
| "linkable": true | ||
| }, | ||
| "using-the-build-cache": { | ||
| "name": "빌드 캐시 사용", | ||
| "linkable": true | ||
| }, | ||
| "multi-stage-builds": { | ||
| "name": "멀티 스테이지 빌드", | ||
| "linkable": true | ||
| }, | ||
| "running-containers": { | ||
| "name": "컨테이너 실행", | ||
| "linkable": false | ||
| }, | ||
| "multi-container-applications": { | ||
| "name": "멀티 컨테이너 애플리케이션", | ||
| "linkable": true | ||
| }, | ||
| "overriding-container-defaults": { | ||
| "name": "컨테이너 기본값 재정의", | ||
| "linkable": true | ||
| }, | ||
| "persisting-container-data": { | ||
| "name": "컨테이너 데이터 유지", | ||
| "linkable": true | ||
| }, | ||
| "publishing-ports": { | ||
| "name": "포트 게시", | ||
| "linkable": true | ||
| }, | ||
| "sharing-local-files": { | ||
| "name": "로컬 파일 공유", | ||
| "linkable": true | ||
| }, | ||
| "docker-workshop": { | ||
| "name": "Docker 워크샵", | ||
| "linkable": true | ||
| }, | ||
| "resources": { | ||
| "name": "리소스", | ||
| "linkable": true | ||
| }, | ||
| "workshop": { | ||
| "name": "워크샵", | ||
| "linkable": false | ||
| }, | ||
| "02_our_app": { | ||
| "name": "애플리케이션 컨테이너화", | ||
| "linkable": true | ||
| }, | ||
| "03_updating_app": { | ||
| "name": "애플리케이션 업데이트", | ||
| "linkable": true | ||
| }, | ||
| "04_sharing_app": { | ||
| "name": "애플리케이션 공유", | ||
| "linkable": true | ||
| }, | ||
| "05_persisting_data": { | ||
| "name": "데이터베이스 유지", | ||
| "linkable": true | ||
| }, | ||
| "06_bind_mounts": { | ||
| "name": "바인드 마운트 사용", | ||
| "linkable": true | ||
| }, | ||
| "07_multi_container": { | ||
| "name": "멀티 컨테이너 애플리케이션", | ||
| "linkable": true | ||
| }, | ||
| "08_using_compose": { | ||
| "name": "Docker Compose 사용", | ||
| "linkable": true | ||
| }, | ||
| "09_image_best": { | ||
| "name": "이미지 빌드 모범 사례", | ||
| "linkable": true | ||
| }, | ||
| "10_what_next": { | ||
| "name": "Docker 워크숍 이후 단계", | ||
| "linkable": true | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,31 @@ | ||||||||||
| import translations from '../data/breadcrumb.json'; | ||||||||||
|
|
||||||||||
| interface BreadcrumbItem { | ||||||||||
| interface SegmentData { | ||||||||||
| name: string; | ||||||||||
| linkable: boolean; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface BreadcrumbItem extends SegmentData { | ||||||||||
| path: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface TranslationData { | ||||||||||
| breadcrumb: Record<string, string>; | ||||||||||
| segments: Record<string, SegmentData>; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * 경로 세그먼트를 번역합니다. | ||||||||||
| * 경로 세그먼트를 번역하고 링크 가능 여부를 확인합니다. | ||||||||||
| * @param segment 번역할 세그먼트 | ||||||||||
| * @returns 번역된 텍스트 또는 원본 텍스트 | ||||||||||
| * @returns 세그먼트 데이터 또는 기본값 | ||||||||||
| */ | ||||||||||
| function translatePathSegment(segment: string): string { | ||||||||||
| function getSegmentData(segment: string): SegmentData { | ||||||||||
| const translationData = translations as TranslationData; | ||||||||||
| return translationData.breadcrumb[segment] || segment; | ||||||||||
| return ( | ||||||||||
| translationData.segments[segment] || { | ||||||||||
| name: segment, | ||||||||||
| linkable: false, | ||||||||||
| } | ||||||||||
| ); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -26,23 +35,26 @@ function generateBreadcrumbItems(): BreadcrumbItem[] { | |||||||||
| const hash = window.location.hash.slice(1); // # 제거 | ||||||||||
|
|
||||||||||
| if (!hash || hash === '/') { | ||||||||||
| return [{ name: '홈', path: '#/' }]; | ||||||||||
| return [{ name: '홈', path: '#/', linkable: true }]; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const pathSegments = hash.split('/').filter((segment) => segment !== ''); | ||||||||||
| const breadcrumbItems: BreadcrumbItem[] = [{ name: '홈', path: '#/' }]; | ||||||||||
| const breadcrumbItems: BreadcrumbItem[] = [ | ||||||||||
| { name: '홈', path: '#/', linkable: true }, | ||||||||||
| ]; | ||||||||||
|
|
||||||||||
| let currentPath = ''; | ||||||||||
|
|
||||||||||
| pathSegments.forEach((segment) => { | ||||||||||
| currentPath += `/${segment}`; | ||||||||||
|
|
||||||||||
| // 세그먼트 이름을 한국어로 변환 | ||||||||||
| const displayName = translatePathSegment(segment); | ||||||||||
| // 세그먼트 데이터 가져오기 (이름과 링크 가능 여부) | ||||||||||
| const segmentData = getSegmentData(segment); | ||||||||||
|
|
||||||||||
| breadcrumbItems.push({ | ||||||||||
| name: displayName, | ||||||||||
| name: segmentData.name, | ||||||||||
| path: `#${currentPath}`, | ||||||||||
| linkable: segmentData.linkable, | ||||||||||
| }); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
|
|
@@ -66,8 +78,13 @@ function createBreadcrumbElement(items: BreadcrumbItem[]): HTMLElement { | |||||||||
| if (isLast) { | ||||||||||
| // 현재 페이지는 span으로 표시 | ||||||||||
| return `<span class="truncate">${item.name}</span>`; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!item.linkable) { | ||||||||||
| // linkable이 false인 경우 span으로 표시 (링크 없음) | ||||||||||
| return `<span class="truncate text-blue-500">${item.name}</span> / `; | ||||||||||
|
||||||||||
| return `<span class="truncate text-blue-500">${item.name}</span> / `; | |
| return `<span class="truncate text-gray-500">${item.name}</span> / `; |
Copilot
AI
Jun 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using a blue text color on non-linkable breadcrumb items may imply they are clickable; consider a neutral color or alternative styling to clearly differentiate non-clickable items.
| return `<span class="truncate text-blue-500">${item.name}</span> / `; | |
| return `<span class="truncate text-gray-500">${item.name}</span> / `; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add aria-expanded and aria-controls attributes to these toggles to communicate the expanded/collapsed state and associated content region to assistive technologies.