JavaScript 애플리케이션에서 크롤링 가능하고 접근 가능한 링크를 구축하는 방법
요약
JavaScript 애플리케이션에서 접근성과 크롤링 성능을 높이기 위한 올바른 링크 구축 방법을 설명합니다. 이벤트 핸들러 대신 의미론적 HTML 요소를 사용하여 검색 엔진과 보조 기술이 탐색을 원활히 수행하도록 하는 가이드를 제공합니다.
핵심 포인트
- onclick 핸들러 대신 href 속성을 가진 앵커(<a>) 태그를 사용해야 함
- URL 이동에는 링크(link)를, 페이지 내 동작에는 버튼(button)을 사용
- 의미론적 HTML 구조는 검색 크롤러와 스크린 리더의 접근성을 보장함
- 클라이언트 사이드 라우터 사용 시에도 실제 앵커 요소를 렌더링해야 함
현대적인 JavaScript 애플리케이션은 빠른 탐색과 대화형 사용자 경험을 제공할 수 있지만, 탐색이 전적으로 이벤트 핸들러 (event handlers)에 의존할 경우 링크 문제를 야기할 수 있습니다.
방문자는 링크처럼 보이는 클릭 가능한 요소를 볼 수 있지만, 브라우저, 키보드, 스크린 리더 (screen reader) 또는 검색 크롤러 (search crawler)는 완전히 다른 것을 볼 수 있습니다.
개발자는 탐색을 시각적 효과가 아닌 애플리케이션 아키텍처 (application architecture)의 일부로 취급해야 합니다.
이 가이드는 JavaScript 애플리케이션에서 크롤링 가능하고, 접근 가능하며, 신뢰할 수 있는 링크를 만드는 방법을 설명합니다.
무엇이 링크를 크롤링 가능하게 만드는가?
전통적인 HTML 링크는 유효한 href 속성을 가진 앵커 (anchor) 요소를 사용합니다:
<a href="/documentation">
Documentation
</a>
이 구조는 여러 가지 사항을 전달합니다:
- 해당 요소가 링크라는 점.
- 목적지가
/documentation이라는 점. - 브라우저가 이를 직접 열 수 있다는 점.
- 키보드 사용자가 포커스 (focus)하고 활성화할 수 있다는 점.
- 사용자가 목적지를 복사할 수 있다는 점.
- 링크를 새 탭에서 열 수 있다는 점.
- 자동화된 시스템이 목적지를 식별할 수 있다는 점.
JavaScript는 이러한 동작을 강화할 수 있지만, 근본적인 링크 구조를 제거해서는 안 됩니다.
Onclick 핸들러만 사용하는 탐색은 피하세요
클릭 가능한 요소가 다음과 같이 작성될 수 있습니다:
<div onclick="window.location='/documentation'">
Documentation
</div>
마우스 사용자는 이를 활성화할 수 있을지 모르지만, 해당 요소는 여전히 div입니다. 이는 실제 링크가 가진 키보드, 접근성 및 브라우저 동작을 자동으로 받지 못합니다.
또 다른 문제적인 예시는 다음과 같습니다:
<span onclick="openPage()">
View documentation
</span>
목적지가 일반적인 href를 통해 노출되지 않고 JavaScript 내부에 숨겨져 있습니다.
목적이 탐색일 때는 앵커 (anchor)를 사용하세요:
<a href="/documentation">
Documentation
</a>
클라이언트 사이드 (client-side) JavaScript를 사용할 수 있다면, 탐색을 가로채서 전체 페이지 새로고침 없이 애플리케이션을 업데이트할 수 있습니다.
동작에는 버튼을, 목적지에는 링크를 사용하세요
간단한 규칙 하나가 많은 인터페이스 문제를 방지하는 데 도움이 됩니다:
- 다른 URL로 이동할 때는 링크 (link)를 사용하세요.
- 현재 페이지에서 동작 (action)을 수행할 때는 버튼 (button)을 사용하세요.
적절한 버튼 동작의 예시는 다음과 같습니다:
- 모달 (modal) 열기
- 폼 (form) 제출
- 설정 저장
- 섹션 확장
- 텍스트 복사
- 프로세스 시작 또는 중지
적절한 링크 목적지의 예시는 다음과 같습니다:
- 문서 페이지
- 제품 페이지
- 계정 페이지
- 기사 (articles)
- 검색 결과
- 다운로드 위치
- 외부 웹사이트
버튼은 단지 스타일을 입히기 쉽다는 이유만으로 링크를 흉내 내서는 안 됩니다. 두 요소 모두 올바른 의미론적 목적 (semantic purpose)을 유지하면서 CSS로 스타일을 지정할 수 있습니다.
클라이언트 사이드 라우터 (Client-Side Routers)는 실제 앵커 (anchors)를 렌더링해야 합니다
프레임워크 라우터 (framework routers)는 일반적으로 앵커 (anchor) 요소를 렌더링하는 컴포넌트를 제공합니다.
개념적인 예시는 다음과 같습니다:
<RouterLink href="/dashboard">
Dashboard
</RouterLink>
정확한 컴포넌트 이름과 속성은 프레임워크에 따라 다르지만, 최종적으로 렌더링된 HTML은 사용 가능한 목적지를 노출해야 합니다:
<a href="/dashboard">
Dashboard
</a>
컴포넌트가 올바른 출력을 생성할 것이라고 가정하기보다, 렌더링된 DOM을 검사하세요.
브라우저 개발자 도구를 열고, 탐색 요소를 찾아 다음 사항을 확인하세요:
<a>요소인가?href를 포함하고 있는가?- URL이 정확한가?
- 앵커 (anchor)에 접근 가능한 이름 (accessible name)이 있는가?
- 링크를 직접 열었을 때 작동하는가?
JavaScript URL을 사용하지 마세요
다음과 같은 URL은 피하세요:
<a href="javascript:void(0)">
Open documentation
</a>
이 패턴은 실제 목적지를 제공하지 않습니다.
이는 다음과 같은 사항을 방해할 수 있습니다:
- 키보드 상호작용 (keyboard interaction)
- 링크 복사
- 새 탭 열기
- 브라우저 히스토리 (browser history)
- 접근성 도구 (accessibility tools)
- 자동화된 링크 테스트
- 콘텐츠 발견 (content discovery)
요소가 동작을 수행한다면 버튼을 사용하세요. 만약 이동(navigate)한다면, href에 실제 URL을 제공하세요.
모든 라우트 (route)가 직접 열었을 때 작동하도록 만드세요
클라이언트 사이드 네비게이션 (Client-side navigation)은 애플리케이션이 로드된 후에는 완벽하게 작동할 수 있지만, 누군가가 동일한 URL을 직접 입력했을 때는 실패할 수 있습니다.
예를 들어, /dashboard/reports로의 이동은 애플리케이션 내부에서는 작동할 수 있습니다. 하지만 서버가 해당 라우트 (route)를 인식하지 못한다면, 해당 페이지를 새로고침했을 때 서버 사이드 404 응답이 발생할 수 있습니다.
다음 방법으로 라우트를 테스트하세요:
- 애플리케이션 홈페이지를 엽니다.
- 내부 링크를 따라 이동합니다.
- 목적지 페이지를 새로고침합니다.
- 시크릿 창 (private window)에서 목적지를 엽니다.
- URL을 새 브라우저 탭에 복사합니다.
- 이전 애플리케이션 세션이 없는 상태에서 URL을 테스트합니다.
유효한 애플리케이션 라우트가 적절한 애플리케이션 응답을 반환하도록 서버 또는 호스팅 플랫폼을 구성하세요.
실제로 존재하지 않는 라우트에 대해 만능 성공 응답 (universal success response)을 사용하지 마세요. 유효하지 않은 URL은 여전히 의미 있는 에러 동작을 생성해야 합니다.
서버 렌더링 (Server Rendering)은 초기 네비게이션을 개선할 수 있습니다
클라이언트 렌더링 (client-rendered) 애플리케이션은 초기에 최소한의 HTML만 전송하고 JavaScript가 실행된 후에 인터페이스를 구축할 수 있습니다.
서버 사이드 렌더링 (Server-side rendering) 또는 정적 생성 (static generation)은 초기 문서에 네비게이션 링크를 제공할 수 있습니다.
이는 다음과 같은 사항을 개선할 수 있습니다:
- 초기 로딩 경험
- 링크 발견 (Link discovery)
- 공유 미리보기
- 느린 기기에서의 신뢰성
- JavaScript 실패 시의 회복 탄력성
- 접근성 테스트
서버 렌더링이 올바른 링크 마크업 (link markup)을 대체할 수는 없습니다. 결과물인 HTML에는 여전히 유효한 앵커 요소 (anchor elements)와 목적지가 포함되어야 합니다.
서술적인 앵커 텍스트 (Anchor Text)를 제공하세요
눈에 보이는 텍스트는 방문자가 목적지를 예측하는 데 도움이 되어야 합니다.
취약한 앵커 텍스트의 예는 다음과 같습니다:
- 여기를 클릭 (Click here)
- 더 보기 (More)
- 이동 (Go)
- 이것 (This)
- 읽기 (Read)
- 링크 (Link)
더 서술적인 대안은 다음과 같습니다:
- JavaScript 접근성 체크리스트
- 계정 보안 설정
- API 인증 문서
- 데이터베이스 마이그레이션 가이드
- 설치 패키지 다운로드
문단 전체를 하나의 링크 안에 넣는 것은 피하세요. 링크가 걸린 텍스트가 너무 길면 훑어보거나 탐색하기 어려울 수 있습니다.
앵커(anchor)는 목적을 설명하면서도 간결해야 합니다.
아이콘 전용 링크에 접근 가능한 이름 부여하기
아이콘은 시각적으로는 이해될 수 있지만, 접근 가능한 이름(accessible name)이 없을 수 있습니다.
예를 들어, 돋보기 아이콘은 검색을 나타낼 수 있습니다. 스크린 리더(screen reader)에는 동일한 목적을 전달하는 텍스트 대안이 필요합니다.
개념적인 구조는 다음과 같습니다:
<a href="/search" aria-label="Search">
<svg aria-hidden="true">
...
...
링크가 이미 aria-label로부터 이름을 전달받기 때문에, SVG는 보조 공학 기술(assistive technology)로부터 숨겨집니다.
공간적 여유가 있다면 가시적인 텍스트를 사용하는 것이 대개 더 바람직합니다. 아이콘이 친숙하고 접근 가능한 이름이 제공되는 경우에만 아이콘 전용 내비게이션(icon-only navigation)을 사용하세요.
문맥 없는 중복 링크 레이블 피하기
"더 읽어보기"라는 이름의 링크가 여러 개 포함된 페이지는, 해당 링크들이 주변 문단 외부에 나열될 때 혼란을 줄 수 있습니다.
각 목적지를 식별할 수 있는 레이블을 사용하세요:
- 캐싱 가이드 읽기
- API 보안 사례 연구 읽기
- 배포 체크리스트 읽기
디자인상 짧은 반복 문구가 필요하다면, 프로그래밍 방식으로 관련 기사 제목과 연결하거나 숨겨진 설명 텍스트를 포함하세요.
스크린 리더나 접근성 검사 도구(accessibility inspection tool)를 사용하여 링크 목록을 테스트하세요.
링크를 가시적으로 만들기
링크는 일반 텍스트와 시각적으로 구별되어야 합니다.
인지하기 어려울 수 있는 미세한 색상 차이에만 의존하지 마세요. 색상과 함께 밑줄(underline)과 같은 다른 지표를 결합하는 것을 고려하십시오.
상호작용 상태(interactive states) 또한 가시적으로 유지되어야 합니다:
- 호버 (Hover)
- 키보드 포커스 (Keyboard focus)
- 방문함 (Visited)
- 활성 (Active)
- 해당되는 경우 비활성화 (Disabled)
명확한 대체 수단을 제공하지 않고 브라우저의 포커스 윤곽선(focus outline)을 제거하지 마세요.
키보드 사용자는 현재 어떤 링크에 포커스가 있는지 식별할 수 있어야 합니다.
예상되는 브라우저 동작 유지하기
사용자는 링크가 익숙한 브라우저 동작을 지원할 것이라고 기대합니다:
- 새 탭에서 열기 (Open in a new tab)
- 새 창에서 열기 (Open in a new window)
- 링크 주소 복사 (Copy link address)
- 목적지 저장 (Save the destination)
- 상태 영역에서 목적지 보기 (View the destination in the status area)
- 뒤로 가기 및 앞으로 가기 탐색 사용 (Use back and forward navigation)
- URL 공유 (Share the URL)
커스텀 JavaScript 탐색 (Custom JavaScript navigation)은 가능한 한 이러한 기대 사항을 유지해야 합니다.
왼쪽 마우스 클릭에만 반응하는 탐색 구현은 링크를 사용하는 다른 일반적인 방법들을 배제하게 됩니다.
새 탭을 안전하게 처리하기
링크가 반드시 새 탭을 열어야 한다면, 실제 목적지를 사용하세요:
<a
href="https://example.com/resource"
target="_blank"
...
모든 외부 링크를 자동으로 새 탭에서 열지 마세요. 사용자는 링크가 열리는 위치를 직접 제어하는 것을 선호할 수 있습니다.
워크플로상 새 탭을 여는 것이 필수적이라면, 적절한 곳에서 해당 동작을 알려주어야 합니다.
프래그먼트 링크(Fragment Links)를 올바르게 사용하기
프래그먼트 링크 (Fragment links)는 방문자를 페이지의 특정 섹션으로 이동시킵니다:
<a href="#installation">
설치 섹션으로 건너뛰기 (Skip to installation)
</a>
목적지에는 일치하는 식별자 (identifier)가 필요합니다:
<h2 id="installation">
설치 (Installation)
</h2>
각 id는 문서 내에서 유일해야 합니다.
프래그먼트 링크를 활성화한 후, 다음 사항을 확인하세요:
- 올바른 섹션이 화면에 보이는지.
- 고정 탐색 바 (Sticky navigation)가 제목을 가리지 않는지.
- 브라우저 히스토리 (Browser history)가 예상대로 작동하는지.
- 키보드 포커스 (Keyboard focus) 동작이 이해 가능한 상태로 유지되는지.
- 프래그먼트 URL (Fragment URL)을 공유할 수 있는지.
프래그먼트 링크는 목차 (tables of contents), 긴 문서 페이지, FAQ, 그리고 접근성 단축키 (accessibility shortcuts)에 유용합니다.
건너뛰기 링크(Skip Links) 추가하기
건너뛰기 링크 (Skip link)를 사용하면 키보드 사용자가 반복되는 탐색을 건너뛰고 메인 콘텐츠로 직접 이동할 수 있습니다.
예시:
<a class="skip-link" href="#main-content">
메인 콘텐츠로 건너뛰기 (Skip to main content)
</a>
목적지는 다음과 같을 수 있습니다:
<main id="main-content">
...
</main>
건너뛰기 링크는 키보드 포커스를 받기 전까지 시각적으로 숨겨져 있을 수 있습니다.
모든 중요한 페이지 템플릿의 시작 부분에서 이를 테스트하세요.
빈 링크 피하기
빈 앵커 (Empty anchor)는 유용한 이름을 제공하지 못합니다:
<a href="/settings"></a>
대체 텍스트 (alternative text)가 누락된 링크된 이미지는 유사한 문제를 일으킬 수 있습니다:
<a href="/settings">
<img src="/settings-icon.svg" alt="">
</a>
눈에 보이는 텍스트나 적절한 접근 가능한 이름 (accessible name)을 제공하세요.
간격 조절, 레이아웃, 트래킹 (tracking), 또는 JavaScript 훅 (hooks)을 위해 빈 앵커 (empty anchor)를 추가하지 마세요. 대신 CSS와 적절한 데이터 속성 (data attributes)을 사용하세요.
동적으로 생성된 URL 검증하기
애플리케이션은 종종 API 데이터, 경로 파라미터 (route parameters), 사용자 이름, 또는 콘텐츠 관리 시스템 (CMS)으로부터 링크를 생성합니다.
이 값들을 href에 렌더링하기 전에 검증하세요.
다음 사항을 주의 깊게 살펴보세요:
- 식별자 (identifiers) 누락
- 정의되지 않은 값 (undefined values)
- 잘못된 URL 인코딩 (URL encoding)
- 중복된 슬래시
- 지원되지 않는 프로토콜 (protocols)
- 안전하지 않은 사용자 제어 목적지
- 스테이징 도메인 (staging domains)
- 만료된 임시 URL
동적으로 생성된 링크가 깨지면, 해당 링크가 공유 컴포넌트 (shared component)에서 기원할 경우 수천 개의 페이지에 영향을 미칠 수 있습니다.
개발 중 링크 테스트하기
프로덕션 (production) 보고서를 기다리기보다 개발 프로세스에 링크 테스트를 포함시키세요.
유용한 테스트 계획에는 다음 항목들이 포함되어야 합니다:
- 네비게이션 메뉴 (navigation menus)
- 푸터 링크 (footer links)
- 버튼 및 콜 투 액션 (calls to action)
- 클라이언트 사이드 라우트 (client-side routes)
- 인증 리다이렉트 (authentication redirects)
- 페이지네이션 (pagination)
- 필터링된 URL
- 프래그먼트 링크 (fragment links)
- 외부 링크
- 다운로드 링크
- 에러 페이지
자동화된 테스트를 통해 요소가 올바른 목적지를 가지고 있는지 확인할 수 있습니다:
expect(link).toHaveAttribute("href", "/documentation")
엔드 투 엔드 (end-to-end) 테스트를 통해 네비게이션이 예상된 페이지를 로드하는지 확인할 수 있습니다.
자동화는 수동 검토를 대체하는 것이 아니라 지원해야 합니다.
마우스 없이 테스트하기
기본적인 키보드 테스트만으로도 많은 문제를 발견할 수 있습니다:
- 페이지를 새로고침합니다.
- Tab 키를 반복해서 누릅니다.
- 모든 중요한 링크가 포커스 (focus)를 받는지 확인합니다.
- 포커스가 논리적인 순서를 따르는지 확인합니다.
- Enter 키로 링크를 활성화합니다.
- 브라우저의 뒤로 가기 버튼을 테스트합니다.
- 포커스가 계속 보이는지 확인합니다.
- 숨겨진 요소가 키보드를 가두는지 (traps the keyboard) 확인합니다.
만약 탐색 요소(navigation element)에 도달하거나 활성화할 수 없다면, 부적절한 요소를 사용하고 있지는 않은지 또는 필수적인 동작(behavior)이 누락되지는 않았는지 점검하십시오.
링크 품질 체크리스트 (Link Quality Checklist)
JavaScript 애플리케이션을 배포하기 전에 다음 사항을 확인하십시오:
- 탐색(Navigation) 시 유효한
href값을 가진 앵커(anchor) 요소를 사용하는지 확인합니다. - 버튼(Buttons)은 동작(actions)을 위해서만 예약되어 있는지 확인합니다.
- 라우터(Router) 컴포넌트가 실제 앵커를 렌더링하는지 확인합니다.
- 직접적인 경로 로딩(Direct route loading)이 작동하는지 확인합니다.
- 경로를 새로고침했을 때 예상치 못한 404 에러가 발생하지 않는지 확인합니다.
- 링크가 설명적인 텍스트를 포함하고 있는지 확인합니다.
- 아이콘만 있는 링크에 접근 가능한 이름(accessible names)이 있는지 확인합니다.
- 키보드 포커스(Keyboard focus)가 보이는지 확인합니다.
- 프래그먼트 링크(Fragment links)가 유효한 목적지를 가지고 있는지 확인합니다.
- 빈 링크가 제거되었는지 확인합니다.
- 동적 URL(Dynamic URLs)이 검증되었는지 확인합니다.
- 새 탭 열기(New-tab) 동작이 신중하게 사용되었는지 확인합니다.
- 브라우저 히스토리(Browser history)가 올바르게 작동하는지 확인합니다.
- 중요한 링크들이 자동화된 테스트(automated tests)에 나타나는지 확인합니다.
- JavaScript가 실패하더라도 애플리케이션을 이해할 수 있는 상태를 유지하는지 확인합니다.
마치며 (Final Thoughts)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기