프로그래밍 언어/HTML · CSS

시멘틱 태그, noopener noreferrer, 하드웨어 가속

원코 2026. 4. 9. 18:38

  웹 개발을 시작한 것이 2021년쯤이니.. 벌써 5년쯤 되었다.

  처음 시작할 때는 제이쿼리라는 $ 달린 것이 대세였는데, HTML CSS를 배우면서 JS로 넘어가기도 전에 이미 제이쿼리의 시대가 끝나 버리고 말았다. IT 업계가 그런 곳이니까. 늘상 빠르게 바뀌는 유행하는 기술들을 보며 계속해서 새로운 것을 시도해 보고 싶어서 새로운 것만 나오면 둘러봤다.

 그러다 SOPT에 들어오게 되면서 오랜만에 다시 21년도에 만졌던 바닐라 웹을 만져보니 참 불편하면서도 이걸 배울 때 쯤엔 재사용성이니 뭐니 아무것도 몰랐던 나였던 걸 느꼈다. 그래서 개발하다 보니 여기서는 어떻게 현대적인 개발 방법을 적용할 수 있을지 궁금해져서 좀 찾아보았고.. 알게 된 것과 고민을 좀 적어 보려 한다.

시멘틱 태그 (Semantic Tags)

  태그가 가진 의미를 브라우저와 개발자에게 명확하게 전달하는 태그를 말한다. 흔히 우리가 쓰는 <header>, <nav>, <footer> 이런 거다. 시멘틱 태그 자체를 알고 사용했지만 정작 시멘틱 태그라는 명칭 자체는 몰랐다. nav, header 이런 것은 다 아니까.. 내가 볼 때 조금 명확히 역할을 나누려면 알아둘 만한 것은

  • article: 뉴스 기사, 블로그 포스트처럼 독립적으로 떼어내도 말이 되는 본문
  • main: 해당 페이지의 독보적인 핵심 콘텐츠 (페이지당 1번만 사용)
  • aside: 사이드바, 광고 등 본문과 직접적인 관련이 적은 부가 정보들

이 정도로 보인다. 이건 알아둘 만한 거고.. 진짜 중요한 것은 이걸 왜 써야 하는가??? 인 것 같다.

왜 쓰는가?

 

1. SEO (검색 엔진 최적화): 검색엔진 봇이 글을 살펴볼 때 보다 의미를 잘 해석함.

2. 웹 접근성 (Accessibility): 스크린 리더기가 태그의 의미를 해석해서 알려줄 수 있음.

3. 유지보수의 편의성: 코드를 나중에 다시 봐도 <div> 도배보단 태그 이름을 보고 의미를 바로 알 수 있어 작업 효율 상승.

 

  물론 1, 2번의 기계적 접근성도 중요하지만, 개인적으로는 3번이 가장 중요한 이유라고 생각한다. 남이 읽기 쉬운 코드가 코드의 미덕이라고 생각하는 만큼 유지보수의 편의성을 위해서라도 시멘틱 태그를 잘 사용해야겠다.

a 태그의 rel = "noopener noreferrer"

  개인적으로 어떤 페이지든 외부 페이지 이동 시 같은 탭에서 열리는 걸 굉장히 싫어한다. 그래서 항상 내 페이지를 만들 때는 외부 페이지가 다른 탭에서 열리도록 해 둔다. html에서는 이를 a 태그의 target 속성의 값을 "_blank"로 줌으로써 이를 구현한다. 이번에 이걸 좀더 찾아보게 되었다가 noopener noreferrer를 보게 되자마자 이게뭐냐? 라고 생각했다. 알고보니 꼭 알아 두어야 할 것이었다...

이게 뭔가?

두 개는 약간 다르다.

noopener

  자식 창이 생성될 때, 해당 창의 실행 환경 내에 존재하는 window.opener 속성값을 null로 강제 설정한다. 이로 인해 자식 창은 부모 창의 메모리 주소나 객체에 접근할 수 있는 참조 수단(Reference)을 완전히 잃게 된다. 또 브라우저 엔진은 이 속성을 감지하면 자식 창을 부모 창과 별개의 운영체제 프로세스(Process)로 할당한다.

noreferrer

  HTTP 요청 시 발생지 정보를 전송하지 않도록 제어하며, 동시에 noopener의 모든 논리적 기능을 수행하는 상위 집합 속성. 브라우저가 대상 URL로 GET 요청을 보낼 때, 네트워크 패킷의 헤더 영역에서 Referer 필드를 생성하지 않거나 빈 값으로 전송한다. 이로 인해 수신측 서버는 요청이 어느 페이지로부터 유입되었는지 식별할 수 없게 된다.

왜 쓰는가?

1. 탭내빙(tabnabbing) 공격의 방지

  탭내빙이란 사용자가 현재 보고 있는 탭이 아닌, 열려 있는 다른 탭(비활성 탭)의 내용을 악의적으로 교체하여 정보를 탈취하는 피싱 공격의 일 한 종류이다.

  기존 부모 페이지에서 새 탭으로 열린 페이지는 부모 페이지의 제어권의 일부를 가진다. target="_blank"로 창을 열면 브라우저는 자식 창에게 window.opener라는 객체를 넘겨준다. window.opener는 현재 창을 open한 부모 창의 window 객체에 대한 참조(Reference)를 담고 있는 읽기 전용 속성으로, 두 개의 독립된 브라우징 맥락 사이를 잇는 메모리상의 직접적인 연결 통로 역할을 한다.

 

  이때 부모와 자식 창의 도메인이 같을 경우, 자식 창은 window.opener를 통해 부모 창의 모든 객체(DOM, 변수, 함수 등)에 완전한 읽기/쓰기 권한을 가진다. 그러나 부모와 자식 창의 도메인이 다를 경우 보안을 위해 접근이 엄격히 제한된다. 하지만 window.opener.location과 같은 일부 속성은 수정이 가능하도록 설계되어 있으며, 바로 이 지점이 보안 취약점의 핵심이 된다.

  noopener (상위 개념인 noreferrer 포함) 속성은 자식 창이 부모 창의 window.opener를 아예 갖지 못하게 해 이를 사전에 방지한다.

2. 브라우저 최적화

  현대 브라우저(Chrome, Safari 등)는 단일 프로세스가 아닌 멀티프로세스 아키텍처를 사용한다. 주소창, 북마크, 뒤로 가기 버튼 등 UI와 네트워크 요청, 파일 접근을 관리하는 브라우저 프로세스, 웹사이트가 표시되는 탭 내부의 모든 동작(HTML/CSS 파싱, JS 실행)을 처리하는 렌더러 프로세스로 동작한다.

  이때 격리 원리는 기본적으로 탭마다 별도의 렌더러 프로세스를 할당하여 한 탭이 멈춰도 전체 브라우저가 종료되지 않게 하는 것이다. 이때 부모 탭과 자식 탭이 연결되어 있으면 자식 탭의 JS가 실행될 때 부모 탭의 이벤트 루프가 대기해야 하는 상황이 발생할 수 있다. noopener는 부모 탭과 자식 탭을 물리적으로 쪼개어 각 탭이 독립적인 CPU 코어에서 병렬로 실행될 수 있게 한다.

 

noopener와 noreferrer을 함께 쓰는 이유?

  위에서 설명한 대로 noreferrer은 noopener은 상위 집합 속성이지만, 부모 탭의 URL에 민감 정보가 포함된 경우 (세션 토큰, 이메일 주소, 혹은 내부 시스템의 구조를 유추할 수 있는 정보)가 포함되어 있다면 더 확실한 보안을 위해 사용한다. 그러나 오히려 유입 경로 등을 확인할 필요가 있을 경우에는 noopener만 사용하는 것이 좋다.

  그런데 왜 관습적으로 둘 다 사용할까? 이는 과거 특정 버전의 브라우저들이 noopener는 무시하고 noreferrer만 인식하거나, 그 반대인 경우가 있었기 때문이다. 이 두 가지를 모두 적는 것은 어떤 브라우저에서도 최소한 하나의 속성은 작동하게 하려는 방어적 프로그래밍의 결과로 볼 수 있다. 그래서 업계 표준처럼 되었다고 한다.

요즘의 브라우저에서는...

 

  현재 대부분의 최신 브라우저(Chromium 기반 브라우저 등)는 target="_blank"가 선언되면 개발자가 아무것도 적지 않아도 자동으로 rel="noopener"를 적용한다고 한다... 따라서 최신 환경만 고려한다면 사실 아무것도 안 써도 보안상 안전하지만, 여전히 보안 처리가 되어 있다는 명시적 의도의 전달과 구형 브라우저의 하위 호환성 보장을 위해 작성하는 것이 좋다.

결론

 

  • 보안과 성능만 신경 쓴다면: rel="noopener"만 쓰자.
  • 어디서 왔는지까지 숨기고 싶다면: rel="noreferrer"를 쓰자.
  • 그냥 가장 안전하고 확실한 방법을 원한다면: 관습대로 rel="noopener noreferrer"를 쓰자.

하드웨어 가속

css에서 애니메이션을 구현할 때, translateX 대신 translate3d를 사용하면 애니메이션이 더 부드러워진다고 한다. 대체 왜? 그래서 알아보게 되었다.

브라우저의 렌더링 파이프라인

브라우저가 화면을 그리는 과정은 보통 Layout → Paint → Composite 단계를 거친다.

  1. Layout: 요소의 기하학적 형태(위치, 크기)를 계산한다. CPU를 많이 소모하며, 한 요소가 변하면 주변 요소까지 영향을 받아 다시 계산해야 한다.
  2. Paint: 계산된 요소를 실제 픽셀(색상)로 변환한다. 역시 CPU의 영역이다.
  3. Composite: 각각 그려진 레이어들을 하나의 화면으로 합친다. 이 단계는 GPU가 담당한다.

translateX와 translate3d의 차이점

translateX(n)

일반적인 2D 변환 속성이다. 최신 브라우저는 이를 최적화하기도 하지만, 기본적으로 브라우저는 이를 일반적인 요소 변경으로 취급할 가능성이 높다. 상황에 따라 CPU가 레이아웃을 다시 계산하거나(Reflow), 페인트(Repaint) 과정을 거쳐 화면을 갱신한다.

translate3d(x, y, z)

3D 변환 속성을 사용하면 브라우저는 해당 요소를 별도의 컴포지터 레이어(Compositor Layer)로 강제 분리한다. 이를 하드웨어 가속이라 부른다.

  • 레이어 분리: 요소가 포토샵의 '레이어'처럼 독립적인 층이 된다.
  • GPU 위임: 이 독립된 레이어는 CPU가 아닌 GPU의 메모리에 텍스트로 업로드된다.
  • 합성 전용: 위치값만 바뀔 때 CPU는 레이아웃이나 페인트를 다시 하지 않고, GPU가 이미 메모리에 있는 레이어의 위치만 옮겨서 합쳐버린다. 연산량이 극적으로 줄어들어 프레임 드랍 없이 부드럽게 움직이는 것이다.

주의점

  모든 요소에 하드웨어 가속을 남용해서는 안 된다. GPU 메모리는 한정되어 있으며, 너무 많은 레이어를 생성하면 오히려 메모리 부족으로 인해 브라우저가 느려지거나 화면이 깨지는 부작용이 발생할 수 있다. 꼭 부드러운 움직임이 필요한 애니메이션 요소에만 선택적으로 적용하는 것이 성능 최적화의 정석이다.