Sticky Nav 때문에 앵커 링크가 가려질 때, 인라인 CSS 몇 줄로 깔끔하게 해결하기

문서(블로그/위키/가이드)에서 각주 링크나 TOC(목차)를 클릭하면 #some-id로 이동하면서 해당 id를 가진 요소가 뷰포트 최상단에 딱 맞춰지죠. 그런데 상단에 position: sticky(또는 fixed)로 고정된 네비게이션 바가 있으면, 스크롤은 최상단으로 맞춰지는데 실제 콘텐츠는 nav 뒤에 숨어버리는 문제가 생깁니다.

프론트엔드에서는 흔한 이슈지만, 백엔드/풀스택 입장에서는 “왜 링크가 제대로 갔는데 내용이 안 보이지?” 같은 상황이 꽤 난감할 수 있어요. 여기서는 제가 가장 자주 쓰는 “문제 생기는 페이지에 인라인 CSS 몇 줄 추가” 방식(가장 간단하고 즉효)을 중심으로 정리해볼게요.


왜 이런 일이 생길까?



브라우저의 기본 동작은 이렇습니다.

  • URL이 .../page#target 형태로 바뀌면
  • 브라우저는 id="target" 요소를 찾아
  • 그 요소의 시작점이 스크롤 컨테이너의 시작(보통 뷰포트 top)에 오도록 스크롤합니다.

하지만 sticky nav는 뷰포트 위에 겹쳐서 렌더링되므로, 결과적으로 요소가 nav 아래에 가려집니다.

링크의 target이 sticky nav에 의해 가려지는 사례 이미지


가장 간단한 해결책: scroll-margin-top (요소 쪽에 여백을 준다)

앵커로 스크롤되어 “보여질” 요소에 scroll-margin-top을 주면, 브라우저가 해당 요소를 최상단이 아니라 “여백만큼 아래”에 위치시키려 합니다.

인라인 CSS로 최소 적용 (추천)

문제 있는 페이지의 <head>나 해당 페이지 템플릿에 아래처럼만 넣어도 해결되는 경우가 많습니다.

<style>
  :root { --sticky-nav-h: 64px; } /* nav 실제 높이로 맞추기 */
  [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>
  • --sticky-nav-h: sticky nav 높이
  • + 12px: 살짝 숨 막히지 않게 주는 여유(취향)

[id] 전체에 적용하면 “이 페이지에서 앵커로 이동되는 모든 대상”이 자동으로 보정됩니다. 페이지 전체에 광범위하게 적용되는 게 부담스럽다면, 아래처럼 대상 범위를 줄이면 돼요.

범위를 줄여서 더 안전하게

예: 문서 본문 영역에서만 적용

<style>
  :root { --sticky-nav-h: 64px; }
  .article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>

예: TOC가 주로 h2/h3로 이동한다면

<style>
  :root { --sticky-nav-h: 64px; }
  .article h2[id], .article h3[id] {
    scroll-margin-top: calc(var(--sticky-nav-h) + 12px);
  }
</style>

함께 쓰면 더 편한 옵션: scroll-padding-top (컨테이너 쪽 기본 패딩)



scroll-padding-top은 “스크롤 스냅/프래그먼트 이동” 등에서 스크롤 컨테이너가 상단 안전영역을 갖도록 힌트를 줍니다. 실무에서는 scroll-margin-top이 더 직관적이지만, 조합해두면 레이아웃에 따라 안정성이 올라가는 경우가 있어요.

<style>
  :root { --sticky-nav-h: 64px; }
  html { scroll-padding-top: calc(var(--sticky-nav-h) + 12px); }
</style>
  • 페이지 단위로 빠르게 “전체 앵커 이동 보정”을 하고 싶을 때 유용합니다.
  • 다만 “특정 요소만” 세밀하게 제어하려면 scroll-margin-top이 편합니다.

레거시/호환성용 고전 트릭: ::before로 가짜 오프셋 만들기

아주 예전부터 쓰던 방식입니다. 앵커 타겟 요소 앞에 보이지 않는 블록을 만들어서, 실제 위치가 nav 아래로 내려오게 합니다.

<style>
  :root { --sticky-nav-h: 64px; }

  .anchor-target::before {
    content: "";
    display: block;
    height: calc(var(--sticky-nav-h) + 12px);
    margin-top: calc(-1 * (var(--sticky-nav-h) + 12px));
    visibility: hidden;
    pointer-events: none;
  }
</style>

사용 예시:

<h2 id="install" class="anchor-target">설치</h2>
  • 장점: 오래된 방식이라 “원리상” 확실히 동작하는 편
  • 단점: 클래스 부여가 필요하고, 요소 선택/구조를 조금 더 신경 써야 함

요즘은 가능하면 scroll-margin-top부터 시도하고, 특정 환경에서 꼬이면 ::before 트릭을 고려하는 흐름이 깔끔합니다.


JS로도 해결 가능하지만, “페이지 단위 인라인 CSS”가 이기는 순간들

물론 JavaScript로 scrollIntoView() 후에 window.scrollBy(0, -navHeight) 같은 식으로 보정할 수도 있어요. 하지만 이런 경우엔 CSS가 더 낫습니다.

  • 문서/정적 콘텐츠 페이지(각주/TOC가 많은 페이지)
  • 프레임워크/라우팅 레벨 수정 없이 “해당 페이지에서만” 빠르게 해결하고 싶을 때
  • 유지보수자가 FE 전문이 아닐 가능성이 높을 때

인라인 <style> 몇 줄은 의존성도 없고, 디버깅도 쉽고, 롤백도 쉬워서 문서성 페이지에 특히 잘 맞습니다.


실무 체크리스트

  • sticky nav 높이를 정확히 잡기

  • 반응형으로 높이가 바뀐다면, 브레이크포인트별로 --sticky-nav-h만 바꿔도 됩니다.

  • 적용 범위를 너무 넓히지 않기

  • [id] 전역 적용이 편하지만, 페이지 구조에 따라 과할 수 있어 .article [id]처럼 제한 추천

  • “약간의 여유(+8~16px)”를 주면 체감이 좋아짐

  • 딱 nav 높이만큼만 주면 심리적으로 붙어 보이는 경우가 많아요.


결론: 제가 가장 자주 쓰는 한 방

문제 생기는 페이지에 아래만 인라인으로 넣고 끝냅니다.

<style>
  :root { --sticky-nav-h: 64px; }
  .article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>

이걸로 TOC/각주/딥링크까지 한 번에 해결되는 경우가 대부분이니, 링크가 nav 뒤로 숨는 문제를 해결하고자 하시는 개발자분들에게 도움이 되었으면 합니다.


함께 보면 좋은 관련 글
- 이미지 태그에 width와 height를 명시하는 이유와 효과
- 백엔드 엔지니어라도 최소 이 정도는 알아야한다 - 프론트엔드 JS 메서드 , 모듈 Best 5