워드프레스 목차 만들기, 플러그인 없이 코드 복사로 3분

워드프레스 목차는 플러그인 없이 HTML 블록 1개로 만들 수 있습니다.

아래 2단계로 코드를 복사해서 붙여넣으면 끝입니다. 글마다 목차를 손으로 적을 필요가 없습니다.

본문의 H2·H3 제목을 자동으로 읽어서 순서대로 정렬해 줍니다.

붙여넣은 직후 편집창에 빈칸만 보여도 정상입니다. 이유는 아래에 설명합니다.

[완성된 목차가 글 상단에 표시된 화면]

1단계 – 도입부 아래에서 + 를 눌러 사용자 정의 HTML 블록을 추가합니다.

워드프레스 목차는 사용자 정의 HTML 블록 하나에 코드를 붙여넣는 것으로 끝납니다. 본문의 H2·H3 제목을 자동으로 읽어 번호가 매겨진 목차를 만들고, 항목을 클릭하면 해당 위치로 부드럽게 이동합니다. 플러그인 설치도, 글마다 목차를 적는 작업도 필요 없습니다.

설치는 2단계입니다.

지금 보고 계신 이 글의 목차도 같은 코드로 만들어졌습니다. 위로 올려서 실제로 눌러보세요.

💡 TIP: 소제목이 3개보다 적으면 목차가 자동으로 숨습니다. 짧은 글에 목차만 덩그러니 붙는 일이 없습니다.

2단계 – 아래 목차 코드 전체, 그대로 복사하세요

아래가 코드 전문입니다. 수정할 곳 없이 그대로 붙여넣으면 동작합니다.

<!-- ===================================================================
     자동 목차 (Table of Contents)
     워드프레스: [사용자 정의 HTML] 블록에 이 전체를 붙여넣으세요.
     본문의 H2 / H3 제목을 자동으로 읽어서 목차를 만듭니다.
     글마다 목차를 손으로 적을 필요가 없습니다. 그대로 붙여넣기만 하세요.
     =================================================================== -->

<div class="bizToc" id="bizToc" hidden>
  <button type="button" class="bizToc-head" id="bizToc-toggle" aria-expanded="true" aria-controls="bizToc-body">
    <span class="bizToc-label">목차</span>
    <span class="bizToc-state">접기</span>
  </button>
  <nav class="bizToc-body" id="bizToc-body" aria-label="이 글의 목차">
    <ol class="bizToc-list" id="bizToc-list"></ol>
  </nav>
</div>

<style>
.bizToc{--tc-fg:#111827;--tc-muted:#6b7280;--tc-line:#e5e7eb;--tc-soft:#f9fafb;--tc-accent:#2563eb;
  max-width:760px;margin:24px auto;background:#fff;border:1px solid var(--tc-line);border-radius:12px;
  font-family:-apple-system,BlinkMacSystemFont,"Apple SD Gothic Neo","Malgun Gothic","Noto Sans KR",sans-serif;
  color:var(--tc-fg);overflow:hidden;box-sizing:border-box}
.bizToc *,.bizToc *::before,.bizToc *::after{box-sizing:border-box}
.bizToc-head{display:flex;align-items:center;justify-content:space-between;width:100%;
  padding:13px 16px;border:0;background:var(--tc-soft);font:inherit;cursor:pointer;text-align:left}
.bizToc-label{font-size:15px;font-weight:800;color:var(--tc-fg)}
.bizToc-label::before{content:"☰";margin-right:8px;color:var(--tc-accent);font-weight:400}
.bizToc-state{font-size:13px;color:var(--tc-muted);font-weight:600}
.bizToc-body{padding:8px 16px 14px;border-top:1px solid var(--tc-line)}
.bizToc[data-open="false"] .bizToc-body{display:none}
.bizToc-list,.bizToc-list ol{list-style:none;margin:0;padding:0;counter-reset:tc}
.bizToc-list > li{counter-increment:tc;margin:0}
.bizToc-list a{display:block;padding:7px 4px 7px 26px;position:relative;font-size:14.5px;line-height:1.55;
  color:var(--tc-fg);text-decoration:none;border-radius:6px;transition:.12s}
.bizToc-list > li > a::before{content:counter(tc);position:absolute;left:0;top:7px;width:20px;
  font-size:12.5px;font-weight:800;color:var(--tc-accent);text-align:center}
.bizToc-list a:hover{background:var(--tc-soft);color:var(--tc-accent)}
.bizToc-list a.is-here{color:var(--tc-accent);font-weight:800;background:#eff6ff}
.bizToc-list ol a{padding-left:44px;font-size:13.5px;color:var(--tc-muted)}
.bizToc-list ol a::before{content:"└";position:absolute;left:26px;top:7px;font-size:11px;opacity:.5}
.bizToc-list ol a:hover,.bizToc-list ol a.is-here{color:var(--tc-accent)}
@media (max-width:560px){
  .bizToc{margin:20px 0;border-radius:10px}
  .bizToc-body{padding:6px 12px 12px}
  .bizToc-list a{font-size:14px}
}
</style>

<script>
(function () {
  /* ── 설정 ── */
  var OFFSET  = 90;              // 상단 고정 메뉴 높이(px). 이동 후 제목이 가려지면 이 값을 키우세요.
  var EXCLUDE = ".no-toc, .bizchk";  // 목차에서 뺄 영역의 선택자. 필요 없으면 "" 로 두세요.
  var MIN     = 3;               // 제목이 이 개수보다 적으면 목차를 만들지 않습니다.

  function init() {
    var root = document.getElementById("bizToc");
    var list = document.getElementById("bizToc-list");
    if (!root || !list) return;

    /* H2 / H3 수집 — 목차 자신, 사이드바·푸터, EXCLUDE 로 지정한 영역은 제외 */
    function collect(scope) {
      var out = [];
      if (!scope) return out;
      Array.prototype.forEach.call(scope.querySelectorAll("h2, h3"), function (h) {
        if (root.contains(h)) return;
        if (h.closest("header, footer, nav, aside, .widget, .comments-area, #comments")) return;
        if (EXCLUDE) { if (h.closest(EXCLUDE)) return; }
        if (!h.textContent.trim()) return;
        out.push(h);
      });
      return out;
    }

    /* 먼저 본문 영역 안에서 찾고, 못 찾으면 페이지 전체에서 찾습니다 (테마 구조가 달라도 동작) */
    var scope = root.closest(".entry-content, .wp-block-post-content, .post-content, .entry, article, main")
             || root.parentElement;
    var heads = collect(scope);
    if (heads.length < MIN) heads = collect(document.body);

    if (heads.length < MIN) {
      if (window.console) console.warn("[목차] H2/H3 제목을 3개 이상 찾지 못해 목차를 표시하지 않습니다. 편집기 화면에서는 정상입니다.");
      return;
    }

    /* id 부여 후 목차 생성 */
    var cur = null;
    heads.forEach(function (h, i) {
      if (!h.id) h.id = "sec-" + (i + 1);
      h.style.scrollMarginTop = OFFSET + "px";

      var a = document.createElement("a");
      a.href = "#" + h.id;
      a.textContent = h.textContent.trim();
      a.setAttribute("data-target", h.id);

      var li = document.createElement("li");
      li.appendChild(a);

      if (h.tagName === "H2" || !cur) {
        list.appendChild(li);
        cur = li;
      } else {
        var sub = cur.querySelector("ol");
        if (!sub) { sub = document.createElement("ol"); cur.appendChild(sub); }
        sub.appendChild(li);
      }
    });

    root.hidden = false;
    root.setAttribute("data-open", "true");

    /* 클릭 → 부드럽게 이동 */
    list.addEventListener("click", function (e) {
      var a = e.target.closest("a");
      if (!a) return;
      e.preventDefault();
      var el = document.getElementById(a.getAttribute("data-target"));
      if (!el) return;
      var y = el.getBoundingClientRect().top + window.pageYOffset - OFFSET;
      window.scrollTo({ top: y, behavior: "smooth" });
      if (history.replaceState) history.replaceState(null, "", "#" + el.id);
    });

    /* 접기 / 펼치기 */
    var btn = document.getElementById("bizToc-toggle");
    btn.addEventListener("click", function () {
      var open = root.getAttribute("data-open") !== "false";
      root.setAttribute("data-open", open ? "false" : "true");
      btn.setAttribute("aria-expanded", open ? "false" : "true");
      btn.querySelector(".bizToc-state").textContent = open ? "펼치기" : "접기";
    });

    /* 지금 읽고 있는 위치 표시 */
    if (!("IntersectionObserver" in window)) return;
    var links = {};
    Array.prototype.forEach.call(list.querySelectorAll("a"), function (a) {
      links[a.getAttribute("data-target")] = a;
    });
    var seen = [];
    var obs = new IntersectionObserver(function (entries) {
      entries.forEach(function (en) {
        var id = en.target.id, at = seen.indexOf(id);
        if (en.isIntersecting) { if (at === -1) seen.push(id); }
        else if (at > -1) { seen.splice(at, 1); }
      });
      if (!seen.length) return;
      var top = heads.filter(function (h) { return seen.indexOf(h.id) > -1; })[0];
      if (!top) return;
      Array.prototype.forEach.call(list.querySelectorAll("a"), function (a) { a.classList.remove("is-here"); });
      if (links[top.id]) links[top.id].classList.add("is-here");
    }, { rootMargin: "-" + OFFSET + "px 0px -65% 0px", threshold: 0 });
    heads.forEach(function (h) { obs.observe(h); });
  }

  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
  else init();
})();
</script>

바꾸고 싶다면 코드 위쪽의 설정값 3개만 건드리면 됩니다.

설정기본값언제 바꾸나
OFFSET90목차를 눌렀을 때 제목이 상단 메뉴에 가리면 숫자를 키웁니다
EXCLUDE.no-toc, .bizchk목차에 넣고 싶지 않은 영역의 선택자를 적습니다
MIN3이 개수보다 소제목이 적으면 목차를 만들지 않습니다

목차에서 빼고 싶은 부분이 있다면 그 블록에 no-toc 클래스를 주면 됩니다. 예를 들어 본문 중간에 넣은 계산기나 배너 안에 제목이 들어 있으면, 그게 목차에 섞이는 것을 막을 수 있습니다.

[이미지: 사용자 정의 HTML 블록에 코드를 붙여넣은 편집기 화면 / alt=”워드프레스 목차 코드 HTML 블록에 붙여넣기” / 파일명: wordpress-toc-html-block.jpg]

H2·H3 구조로 자동 정렬되는 원리

목차는 H2와 H3만 읽습니다. 알파벳순이 아니라 글에 나오는 순서 그대로 정렬되고, H3는 바로 위에 있는 H2 아래로 자동으로 들어갑니다.

H2  워드프레스 목차, HTML 블록 하나면 끝납니다     ← 1번 (번호 붙음)
H2  목차 코드 전체, 그대로 복사하세요              ← 2번
H2  H2·H3 구조로 자동 정렬되는 원리                ← 3번
H2  설치 후 막히는 2가지                           ← 4번
 H3  편집창에 빈칸만 보입니다                      ← 들여쓰기
 H3  실제 페이지에서도 안 보인다면                 ← 들여쓰기

H1은 글 제목이라 제외하고, H4 이하는 무시합니다. 목차가 3단계까지 내려가면 오히려 읽기 어려워지기 때문입니다.

소제목을 나중에 고치거나 추가해도 목차가 알아서 따라갑니다. 사이드바의 “인기글”, 푸터, 댓글 영역에 있는 제목은 목차에 섞이지 않도록 걸러냅니다.

각 소제목에는 sec-1, sec-2 같은 주소가 자동으로 붙습니다. 특정 섹션 링크를 남에게 공유하고 오래 유지해야 한다면, 제목 블록의 고급 → HTML 앵커 에 직접 이름을 넣으세요. 이미 붙어 있는 주소는 덮어쓰지 않습니다.

목차를 넣은 뒤 평균 체류시간이 증가하는 효과를 기대할 수 있습니다.

설치 후 막히는 2가지

편집창에 빈칸만 보입니다

이건 고장이 아니라 정상입니다. 붙여넣자마자 흰 네모만 보여도 당황하지 마세요.

목차는 본문의 소제목을 읽어서 만들어지는데, 편집기에서는 제목들이 각각 별개의 블록으로 떨어져 있습니다. 게다가 HTML 블록의 미리보기는 격리된 틀 안에서 돌아갑니다. 읽을 제목이 하나도 없으니 아무것도 만들어지지 않는 것입니다.

미리보기 버튼을 눌러 실제 페이지에서 확인하세요. 거기서는 정상적으로 뜹니다.

실제 페이지에서도 안 보인다면

원인은 둘 중 하나입니다.

첫째, 소제목이 3개보다 적은 경우입니다. 의도된 동작입니다. MIN 값을 낮추면 됩니다.

둘째, 스크립트가 죽은 경우입니다. 여기에 워드프레스 특유의 함정이 있습니다.

워드프레스는 본문에 들어간 앰퍼샌드 문자를 HTML 엔티티로 자동 변환합니다. 자바스크립트 안에서는 이 변환이 그대로 남아 문법 오류가 됩니다. 실제로 발행된 페이지의 소스를 열어보면 이렇게 되어 있습니다.

if (n &#038;&#038; list.indexOf(n) === -1) list.push(n);
       ↑ 원래는 && 였던 자리

문법 오류가 나는 순간 그 스크립트 전체가 실행되지 않습니다. HTML과 CSS는 멀쩡해서 겉모습은 정상으로 보이는데 아무 동작도 하지 않는 상태가 됩니다.

이 글의 목차 코드에는 앰퍼샌드가 한 글자도 들어 있지 않습니다. 그래서 이 문제가 생기지 않습니다. 다만 직접 만든 다른 스크립트를 워드프레스 본문에 넣으실 때는 이 점을 기억하세요.

💡 TIP: 무언가 안 될 때는 실제 페이지에서 F12 → Console 탭을 여세요. 스크립트가 죽었다면 빨간 오류가 찍혀 있고, 목차가 제목을 못 찾은 경우라면 한글 안내 문구가 나옵니다.

플러그인 대신 이 방법을 쓰는 이유

목차 플러그인도 좋은 선택입니다. 다만 목적에 따라 답이 갈립니다.

이 코드목차 플러그인
설치HTML 블록에 붙여넣기플러그인 설치 + 설정
추가로 불러오는 파일없음별도 CSS·JS 파일
디자인 수정CSS를 직접 고침설정 화면에서 선택
모든 글에 자동 적용안 됨 (패턴으로 삽입)됨
플러그인 개수그대로하나 늘어남

글이 수백 개이고 전부에 목차를 달아야 한다면 플러그인이 맞습니다. 하나하나 넣을 수 없으니까요.

반대로 글 몇 개에만 넣고 싶거나, 디자인을 내 사이트에 맞추고 싶다면 코드 쪽이 낫습니다. 플러그인이 늘어날수록 사이트가 무거워지고 충돌 가능성도 커집니다.

💡 TIP: 새 글마다 붙여넣기가 번거롭다면 목차 블록을 선택하고 ⋮ → 패턴 만들기 로 등록하세요. 이후 /목차 만 쳐서 넣을 수 있고, 디자인을 고칠 때 패턴 하나만 수정하면 그걸 쓴 모든 글에 반영됩니다.

Rank Math를 쓰신다면 한 가지 알아두세요. 콘텐츠 가독성 항목에 “Table of Contents plugin을 사용하지 않는 것 같습니다”라는 경고가 계속 뜹니다. Rank Math는 알려진 목차 플러그인이 설치되어 있는지만 확인하기 때문에, 직접 넣은 목차는 감지하지 못합니다. 실제 페이지에 목차가 보이면 목적은 달성된 것이니 무시하셔도 됩니다. 점수를 올리려고 플러그인을 깔 이유는 없습니다.

자주 묻는 질문

Q. 플러그인이 더 편하지 않나요?

글이 많고 전부에 자동으로 달아야 한다면 플러그인이 편합니다. 다만 플러그인 하나가 늘면 별도의 CSS·JS 파일을 매번 불러오게 됩니다. 글 몇 개에만 필요하거나 디자인을 직접 다루고 싶다면 코드 쪽이 가볍습니다.

Q. 목차가 안 보입니다.

먼저 편집창이 아니라 미리보기나 발행된 페이지에서 보고 계신지 확인하세요. 편집창에서는 원래 빈칸으로 보입니다. 실제 페이지에서도 안 보인다면 소제목이 3개보다 적거나, 다른 스크립트의 오류가 원인입니다.

Q. H4도 목차에 넣을 수 있나요?

기본은 H2와 H3만 읽습니다. 코드에서 "h2, h3" 로 되어 있는 부분을 "h2, h3, h4" 로 바꾸면 H4도 들어갑니다. 다만 3단계가 넘어가면 목차가 오히려 복잡해집니다.

Q. 글마다 붙여넣어야 하나요?

네, 본문에 들어가는 내용이라 글마다 넣어야 합니다. 블록을 동기화된 패턴으로 등록해두면 새 글에서 /목차 만 쳐서 바로 삽입할 수 있습니다.

Q. 목차를 눌렀더니 제목이 상단 메뉴에 가려집니다.

코드 위쪽의 OFFSET 값을 키우세요. 기본은 90인데, 고정 헤더가 두꺼운 테마라면 120이나 150으로 올리면 됩니다.

Q. 모바일에서도 잘 나오나요?

네. 화면 폭이 좁아지면 글자와 여백이 자동으로 줄어듭니다. 목차가 길어 답답하다면 접기 를 눌러 접을 수 있고, 그 상태가 화면에 그대로 유지됩니다.


워드프레스 목차는 플러그인 없이 HTML 블록 1개, 코드 복사 한 번으로 만들 수 있고, 본문의 H2·H3를 자동으로 읽어 정렬해 줍니다.

지금 쓰고 계신 글에 위 코드를 붙여넣고 미리보기 로 확인해 보세요. 편집창이 빈칸이어도 정상이라는 것만 기억하시면 됩니다.

댓글 남기기