REST API

  • 관련 글 카드로 바꾸다 만난 버그 3개, 워드프레스 템플릿 직접 수정기

    관련 글 카드로 바꾸다 만난 버그 3개, 워드프레스 템플릿 직접 수정기

    이번에 손댄 건 글 상세 페이지(single 템플릿)다. 댓글창을 없애고, 관련 글 카드를 넣고, 저자 소개 박스를 추가했다. 작업 방식은 워드프레스 에디터에서 클릭하는 게 아니라 REST API(Application Programming Interface, 프로그램끼리 데이터를 주고받는 창구)로 템플릿 파일을 직접 꺼내서 코드를 고치는 방식이었다. 순서대로 정리한다.

    왜 기본 댓글창을 없앴을까?

    글 하단에 워드프레스 기본 댓글 폼(form, 입력창)이 그대로 붙어 있었다. 문제는 이걸 아무도 쓰지 않는다는 거다. 방문자가 거의 없는 블로그라 댓글이 달린 적이 한 번도 없었다. 빈 입력창이 화면 아래를 한 블록씩 차지하고 있는 셈이었다.

    없애는 건 결정했다. 그 자리에 뭘 넣을지가 문제였다. 고민 끝에 관련 글 카드 그리드로 채우기로 했다. 어차피 그 공간, 뭔가 의미 있는 걸 보여주는 게 낫다.

    REST API로 템플릿 파일을 직접 수정하는 방법

    워드프레스 블록 테마(block theme, 블록 단위로 페이지를 조립하는 방식의 테마)를 쓰면 템플릿 파일이 코드로 저장된다. 이걸 에디터에서 클릭해서 고칠 수도 있지만, 이번엔 REST API를 썼다.

    방식은 단순하다. 먼저 GET 요청(request, 서버에 데이터를 달라고 보내는 신호)으로 현재 템플릿 코드를 통째로 받아온다. 그 코드를 수정한 뒤, POST 요청으로 다시 서버에 올린다. 이번에 건드린 파일은 twentytwentyfive//single(글 상세 페이지 템플릿)과 twentytwentyfive//home(홈 페이지 템플릿) 두 개다.

    수정 전에는 반드시 원본을 백업해둬야 한다. 이건 선택이 아니다. 잘못 올리면 페이지가 깨지고, 원본이 없으면 되돌릴 방법이 없다.

    수정 후에도 주의할 게 있다. 캐시 플러그인인 LiteSpeed가 이전 버전을 그대로 보여주기 때문에, 코드를 올린 뒤 캐시를 반드시 지워야 실제로 반영된다. 이걸 모르면 코드는 바뀌었는데 화면은 그대로라 한참 헤맬 수 있다.

    바뀐 결과는 매번 데스크톱과 모바일 화면을 스크린샷으로 직접 확인했다. 눈으로 봐야 안다.

    관련 글 카드를 만들고 현재 글 중복 버그 고치기

    댓글 블록 자리에 쿼리 루프 블록을 넣었다. 카드 3개짜리 그리드다. 각 카드에는 대표 이미지, 카테고리, 제목, 날짜가 나온다.

    섹션 제목은 처음에 ‘이어서 읽을 글’이라고 썼다. 뉘앙스가 애매했다. ‘함께 보면 좋은 글’로 바꿨다.

    그런데 버그가 두 개 있었다.

    첫 번째. 지금 읽고 있는 글 자신이 관련 글 카드에 그대로 나왔다. 현재 글이 본문 위에도 있고, 아래 카드에도 있는 구조였다. 이건 WPCode 플러그인(코드 스니펫을 워드프레스에 안전하게 추가할 수 있는 도구)으로 PHP 필터를 추가해서 현재 글 ID를 쿼리에서 빼는 방식으로 해결했다.

    두 번째. 글 하단에 예전부터 있던 ‘더 많은 게시물’ 목록이 그대로 살아 있었다. 새로 넣은 카드 바로 아래에 같은 글들이 또 나오는 거다. 이건 그냥 지웠다. 살릴 이유가 없었다.

    카드 추천 로직도 손봤다. 같은 카테고리에 글이 4개 이상 있으면 그 카테고리 안에서만 추천하고, 부족하면 최신 글로 채운다.

    저자 이름 링크가 빈 페이지로 가던 문제 해결

    글 상단에 ‘작성자 JIN’이 표시되는데, ‘JIN’을 클릭하면 워드프레스 기본 작성자 아카이브 페이지인 /author/ujinify/로 이동했다. 그 페이지는 글 목록만 나열하는 화면이었다. 딱히 의미 있는 정보가 없었다.

    해결 방법은 단순했다. 링크를 그냥 껐다. 상단 작성자 표시와 하단 저자 소개 박스, 둘 다 이름에 걸린 링크를 제거했다.

    대신 저자 소개 박스 아래에 ‘소개 보기 →’ 링크를 달았다. /about/ 페이지로 연결된다. 소개가 필요하면 거길 누르면 된다. 저자 박스 구성은 프로필 사진, 이름 ‘JIN’, 짧은 소개 문구, 그리고 그 링크다.

    예전 ‘← 이전 글’ 내비게이션은 지금 읽는 글과 관련 없는 글로 넘어가는 경우가 많아서 클릭할 이유가 없었다. 그 자리를 저자 소개 박스가 대신한다.

    글 하단 저자 소개 박스와 '함께 보면 좋은 글' 카드 섹션 — 댓글창과 이전 글 내비게이션이 있던 자리를 대체한 실제 화면

    위가 완성된 하단 모습이다. 저자 소개 박스 아래로 관련 글 카드 3개가 이어진다. 현재 글 자신은 카드에 안 나온다.

    모바일에서 제목이 이상하게 줄바꿈되는 문제를 CSS로 간단히 고치기

    홈페이지 상단 제목이 원래 ‘블로그’라고만 적혀 있었다. 무슨 블로그인지 설명이 전혀 없었다. ‘Claude Code 실전 자동화 기록’으로 바꿨다.

    홈페이지 데스크톱 화면 ,  '블로그'였던 제목을 'Claude Code 실전 자동화 기록'으로 바꾼 모습

    위가 바뀐 홈 제목의 데스크톱 화면이다.

    제목이 길어지면서 문제가 생겼다. 모바일 화면에서 단어 중간이 잘려서 줄바꿈됐다. 예를 들면 ‘자동화’가 ‘자동’과 ‘화’로 나뉘어서 두 줄에 걸쳐 나오는 식이다. 보기 나빴다.

    처음엔 단어 사이마다 줄바꿈 방지 공백( )을 일일이 넣었다. 동작은 했지만 코드가 지저분했다. 그리고 제목이 바뀔 때마다 또 넣어야 하는 구조였다.

    CSS(Cascading Style Sheets, 웹페이지의 디자인과 레이아웃을 담당하는 언어) 속성 하나로 해결했다.

    • word-break: keep-all , 단어 단위로만 줄바꿈되게 하는 속성이다. 한국어, 중국어, 일본어에 효과적이다.

    속성 하나 추가했더니 공백을 일일이 넣은 코드가 전부 필요 없어졌다. 훨씬 깔끔하다.

    word-break: keep-all 적용 후 모바일 화면 ,  제목이 단어 단위로 자연스럽게 줄바꿈된다

    위는 word-break: keep-all 적용 후 모바일 화면이다. 단어가 잘리지 않고 자연스럽게 줄바꿈된다.

    이번 작업에서 가장 번거로웠던 건 매번 캐시를 지워야 변경이 반영된다는 점이었다. 코드는 올라갔는데 화면이 안 바뀌어서 잠깐 당황한 적도 있었다. 다음엔 REST API 작업 외에 홈 페이지 레이아웃 전체를 손볼 생각이다.

  • 랭크매스 포커스 키워드, wp.data로 직접 넣는 법

    랭크매스 포커스 키워드, wp.data로 직접 넣는 법

    ujinify.com 업로드 스크립트가 REST API(Application Programming Interface, 외부에서 워드프레스 데이터를 주고받는 통로)로 랭크매스 포커스 키워드를 보내고 있었다. 응답도 200이 왔다. 에러도 없었다. 그런데 글을 열어보면 포커스 키워드 칸이 비어 있다.

    조용히 무시당한 거다. 한동안 이걸 몰랐고, 그동안은 글마다 에디터를 열어서 손으로 키워드를 입력했다. 글이 늘수록 이게 제일 귀찮은 수작업이었다.

    이 글은 그 문제를 해결한 방법을 정리한다. wp.data라는 워드프레스 내부 저장소(앱 안에서 데이터를 관리하는 공간)를 직접 건드리는 방식이다.

    REST API가 랭크매스 포커스 키워드를 무시하는 이유

    REST API로 글을 저장할 때 meta 필드에 rank_math_focus_keyword를 넣으면 된다고 생각했다. 실제로 그렇게 했다. 그리고 저장은 됐다. 응답 코드도 200이었다.

    랭크매스가 이 값을 받아도 포커스 키워드로 반영하지 않는다. 에러를 주지도 않는다. 그냥 없는 것처럼 동작한다.

    정확한 내부 원인까지는 확인하지 못했다. 확인한 건 하나다. 랭크매스의 점수 계산 엔진(SEO 점수를 계산하는 프로그램)이 브라우저 자바스크립트(JavaScript, 웹 브라우저에서 실행되는 프로그래밍 언어)로 돌아가서, 에디터 화면을 열었을 때만 점수가 계산된다. 키워드도 그 화면 안에서 넣으면 들어갔다.

    스크립트가 이걸 감지해서 경고는 띄워주게 됐지만, 근본 해결은 아니었다. 에디터를 열고 직접 넣는 수밖에 없었다. 그러다 자동화할 방법을 찾기 시작했다.

    wp.data 저장소에서 랭크매스 액션 찾기

    wp-admin(워드프레스 관리자 화면) 글 에디터를 열어둔 상태에서 시작했다.

    이 글의 코드는 브라우저 개발자 도구 콘솔(Console, 코드를 직접 실행할 수 있는 창)에 그대로 붙여 넣어도 돌아가는 코드다. 나는 같은 코드를 Playwright(브라우저 자동 조종 도구)로 실행했다. 워드프레스 블록 에디터는 wp.data라는 내부 저장소 시스템을 쓴다. 워드프레스 공식 문서에도 나오는 방식이다. 플러그인도 여기에 자기 저장소를 등록할 수 있다. 랭크매스도 그렇게 했다.

    콘솔에 이렇게 입력하면 랭크매스 저장소에 접근할 수 있다.

    wp.data.select('rank-math')

    그리고 액션(Action, 저장소에 변경을 가하는 명령)을 확인하려면 이렇게 입력한다.

    wp.data.dispatch('rank-math')

    여기서 액션이 87개나 나왔다. 이름을 하나씩 보다가 바로 눈에 띄는 게 있었다. updateKeywords, updateDescription, updateSerpDescription, updateTitle. 이름만 봐도 뭘 하는지 명확했다.

    아래 스크린샷은 이 방식으로 값을 넣은 글의 에디터 화면이다. 오른쪽 랭크매스 패널에 포커스 키워드와 점수(78/100)가 들어가 있다.

    랭크매스 포커스 키워드가 REST로 안 들어갈 때, wp.data로 직접 넣는 법 스크린샷 1

    액션 이름은 랭크매스 버전에 따라 바뀔 수 있다. 지금 여기서 나열하는 이름들은 현재 ujinify.com에 설치된 랭크매스에서 직접 확인한 것들이다. 버전이 다르면 이름이 달라질 수 있으니 반드시 직접 확인해야 한다.

    updateKeywords로 포커스 키워드 직접 입력하기

    확인했으니 바로 써봤다. 콘솔에서 이렇게 입력했다.

    wp.data.dispatch('rank-math').updateKeywords('세션 ID')

    포커스 키워드 칸에 바로 들어갔다. 4초쯤 기다렸더니 랭크매스 점수가 22점에서 71점으로 뛰었다.

    이게 맞는 방법이었다. REST API를 우회해서 에디터가 직접 받아서 처리하는 경로로 넣는 거다.

    저장까지 마치려면 core/editor(워드프레스 에디터의 핵심 저장소)의 savePost()를 써야 한다. 비동기 함수라서 await를 붙여서 호출했다.

    await wp.data.dispatch('core/editor').savePost()

    내가 쓴 순서는 이렇다. updateKeywords를 먼저 호출하고, 점수 갱신을 잠깐 기다린 뒤, savePost()로 저장했다.

    updateDescription, updateTitle로 다른 메타데이터 채우기

    포커스 키워드만 넣었는데 점수가 71점이었다. 메타 설명(검색 결과에 표시되는 글 요약문)이 비어 있었다.

    updateDescription으로 메타 설명을 넣었더니 73점이 됐다.

    wp.data.dispatch('rank-math').updateDescription('여기에 메타 설명 텍스트를 넣는다')

    같은 방식으로 SEO 제목도 바꿀 수 있다.

    wp.data.dispatch('rank-math').updateTitle('여기에 SEO 제목을 넣는다')

    updateSerpDescription도 있었는데, SERP에 표시되는 설명을 따로 관리하는 액션이다. 저장소에 이름이 있으니 확인해두면 된다.

    이후 본문 첫 10% 안에 키워드가 없다는 항목이 걸려 있어서 첫 문단에 문장을 하나 추가했다. 긴 문단을 쪼개고, 제목이 키워드로 시작하게 순서를 바꿨다. 첫 번째 글은 최종 76점까지 올라갔다. 두 번째 글은 70점에서 시작해서 본문에 실제 캡처 이미지 2장을 추가했더니 78점이 됐다.

    아래 스크린샷은 랭크매스 패널의 항목 목록이다. 통과 항목은 초록, 감점 항목은 빨강이다. URL 항목이 빨간색으로 남아 있다.

    랭크매스 포커스 키워드가 REST로 안 들어갈 때, wp.data로 직접 넣는 법 스크린샷 2

    점수 갱신이 늦거나 예전 값이 나올 때 확인법

    헷갈리는 지점이 있었다. window.rankMathEditor.resultManager.getResults()로 테스트 통과 여부를 읽었는데, 키워드가 제목에도 본문에도 분명히 있는데 전부 false로 나왔다.

    화면 항목 목록에는 ‘제목에 포커스 키워드를 사용하고 있습니다’가 통과로 표시돼 있었다. 이 값은 갱신이 늦거나 예전 스냅샷(특정 시점의 데이터 복사본)을 돌려주는 거였다. 그래서 getResults() 반환값은 신뢰하지 않기로 했다. 화면에 보이는 항목 목록을 기준으로 삼는다.

    또 하나. 통과(has: true)로 표시돼도 점수가 다 들어오지 않는 항목이 있다. 이미지 항목은 1/6점, 본문 길이는 2/8점처럼 부분 점수로 처리되는 경우가 있었다. 점수가 기대보다 낮게 나오면 이걸 먼저 확인해야 한다. 통과 여부만 보고 끝내면 안 된다.

    화면에 뜬 점수와 데이터베이스에 저장된 점수가 다를 수도 있다. 저장 후에는 REST API로 get-post-seo-meta를 조회해서 DB에 실제로 반영된 점수를 따로 확인해야 한다. 이 단계를 빼면 안 된다. 화면 숫자만 믿다가 나중에 데이터가 다른 걸 발견하면 그때는 더 번거롭다.

    이 방법의 한계와 실제 적용 시 주의점

    이 방법에는 명확한 한계가 있다. 숨기지 않겠다.

    • 브라우저 에디터를 열어야 한다. REST API나 MCP(Model Context Protocol, AI 도구가 외부 서비스에 명령을 보내는 통신 방식)만으로는 동작하지 않는다. 점수 계산 엔진이 브라우저 자바스크립트라서, 에디터 화면이 열려 있어야 실행된다.
    • 액션 이름이 버전마다 다를 수 있다. updateKeywords, updateDescription 같은 이름은 현재 내 사이트 랭크매스에서 직접 확인한 것들이다. 업데이트 후에 이름이 바뀌면 동작하지 않는다. 사용 전에 반드시 wp.data.dispatch('rank-math')를 콘솔에서 직접 실행해서 목록을 확인해라.
    • 저장 후 DB 값을 반드시 확인해야 한다. 화면 점수와 실제 DB에 저장된 점수가 다를 수 있다. REST API로 get-post-seo-meta를 조회하는 단계를 절대 생략하지 마라.
    • 올릴 수 없는 점수가 존재한다. URL에 한국어 키워드가 없는 항목(5점), 유료 Content AI 항목(5점)은 구조적으로 올리기 어렵거나 올리지 않을 항목이다. 이 점수를 제외하면 실제 달성 가능한 최대치는 처음부터 그만큼 낮다.

    수작업으로 키워드를 하나씩 입력하는 것보다는 낫다. 하지만 완전 자동화는 아니다. 에디터 화면을 열고 콘솔에서 코드를 실행하는 단계는 여전히 남는다.

    지금은 이 정도로 쓰고 있다.

    랭크매스 점수가 왜 에디터를 열어야 계산되는지는 랭크매스 SEO 16점→76점 글에, 같은 결의 버그 다섯 개는 워드프레스 SEO 버그 5개 총정리에 정리해뒀다.

    같은 랭크매스 주제로 이어서 읽으면 좋은 글: 랭크매스 무료 기능으로 고아 포스트 2개 찾아낸 이야기, 메타 설명 오류 — 색인됐는데 검색 안 되던 이유.

  • AI 없이 코드로 만든 썸네일 자동 생성 (비용 0원)

    AI 없이 코드로 만든 썸네일 자동 생성 (비용 0원)

    블로그를 운영하다 보면 어느 순간 홈 화면이 이상하게 어수선해 보이는 때가 온다. 나한테는 그게 썸네일이었다.

    스크린샷으로 썸네일을 만들면 뭐가 문제일까?

    처음에는 대수롭지 않게 생각했다. 글 쓰고, 관련 화면 캡처하고, 그걸 대표 이미지로 올리면 끝. 빠르고 간단했다.

    근데 카드가 쌓이면서 문제가 드러났다. 어떤 카드는 흰 배경의 밝은 에디터 화면, 어떤 카드는 어두운 터미널 화면. 톤이 제각각이었다. 여기서 끝이 아니었다. 스크린샷마다 여백 비율이 달랐다. 카드 썸네일로 자르면 내용이 잘리거나 텅 빈 것처럼 보이는 것도 나왔다.

    사용자가 홈 화면을 보고 한마디 했다. “좀 더 깔끔하고 고급스럽게 만들 수 없을까요?” 그 말을 듣고 보니, 스크린샷 짜깁기로는 한계가 명확했다.

    스크린샷 대신 코드로 브랜드 썸네일 자동 생성하기 (AI 크레딧 0원) 스크린샷 1

    위가 당시 실제 홈 화면이다. 카드 하나하나를 따로 보면 그럭저럭 봐줄 만한데, 그리드 전체를 놓고 보면 통일감이 없다.

    AI 이미지 생성이 아닌 코드 합성을 고른 이유

    AI로 이미지를 생성하는 방법도 고려했다. 잠깐 진지하게.

    근데 두 가지 이유로 접었다. 첫 번째는 비용이다. 매 글마다 이미지 생성 API(외부 서버에 요청을 보내 이미지를 만드는 서비스)를 호출하면 글을 쓸 때마다 크레딧이 나간다. 두 번째는 일관성이다. AI 생성 이미지는 프롬프트(AI에게 전달하는 명령 텍스트)가 같아도 매번 스타일이 미묘하게 달라진다. 카드 그리드의 통일감을 위해 썸네일을 바꾸는 건데, AI로 만들면 오히려 통일감이 더 흐트러질 수 있다.

    결론은 단순했다. 제목 텍스트와 카테고리를 코드로 합성(여러 요소를 프로그래밍으로 조합해 이미지를 만드는 것)해서, 항상 똑같은 스타일의 썸네일을 찍어내는 쪽을 고른다. 비용은 0원. 스타일은 항상 동일하다.

    Sharp로 SVG 썸네일을 PNG로 변환하기

    실제 구현 방식은 이렇다. sharp라는 이미지 처리 라이브러리(Node.js 환경에서 이미지를 읽고 변환하고 저장하는 도구)를 사용했다. SVG(코드로 그리는 벡터 이미지 형식)로 썸네일 디자인을 정의하고, 그걸 PNG로 변환하는 구조다.

    캔버스(이미지의 전체 작업 공간) 크기는 1200×675. 16:9 비율이다. 배경은 인디고 계열 그라데이션을 깔았다. 왼쪽 위에는 ‘AgentLog’ 로고 텍스트를 넣고, 그 아래 카테고리 배지, 그리고 글 제목을 크게 배치했다.

    신경 쓴 부분이 두 가지 있었다. 제목이 길면 자동으로 줄바꿈되게 했고, 글자 수에 맞춰 폰트 크기도 줄어들게 처리했다. 제목이 30자짜리 글도 있고 10자짜리 글도 있으니, 이걸 고정 크기로 두면 어디선가 반드시 넘친다.

    제일 걱정한 건 한글 렌더링(화면에 글자를 실제로 그려내는 과정)이었다. SVG 안에서 한글이 깨지거나 이상하게 나오면 처음부터 다시 접근해야 하니까. 테스트해보니 macOS 기본 폰트로 깨짐 없이 잘 나왔다. 이 부분은 솔직히 예상보다 수월했다.

    스크린샷 대신 코드로 브랜드 썸네일 자동 생성하기 (AI 크레딧 0원) 스크린샷 2

    변환 결과물이다. 스크린샷 모음이랑 비교하면 차이가 바로 보인다. 단순하지만 톤이 일정하다.

    워드프레스 API로 기존 글 14개 일괄 처리하기

    새 글에만 적용하면 어색해진다. 예전 글 썸네일은 여전히 스크린샷이고, 새 글 썸네일만 정돈된 디자인이면 오히려 더 눈에 띄게 이상하다.

    그래서 기존 글도 전부 다시 처리했다. 발행된 글 8개, 예약된 글 6개. 총 14개를 한 번에 재생성하는 스크립트(자동으로 반복 작업을 처리하는 코드 파일)를 따로 만들었다.

    흐름은 이렇다. 전에도 REST API로 글 데이터를 직접 다룬 적이 있는데, 이번에도 같은 방식이었다. 워드프레스 REST API(워드프레스가 외부 코드와 데이터를 주고받을 수 있도록 제공하는 통신 창구)로 각 글의 제목과 카테고리를 가져온다. 그 데이터로 썸네일을 생성한다. 생성된 이미지를 미디어 라이브러리(워드프레스에서 이미지, 파일 등을 보관하는 공간)에 업로드하고, 해당 글의 대표 이미지를 갈아끼운다. 14개 전부 한 번에 실행했고, 몇 분 만에 끝났다.

    스크립트 하나로 14개를 처리한다는 게 처음엔 좀 무섭기도 했다. 잘못되면 14개가 동시에 망가지니까. 실행 전에 글 하나만 따로 테스트하고, 결과를 눈으로 확인한 다음 전체를 돌렸다.

    Alt 텍스트까지 한 번에 채우기

    썸네일은 바뀌었는데 문제가 하나 더 있었다. 워드프레스 미디어 라이브러리에 등록된 대체 텍스트(alt text, 이미지를 볼 수 없을 때 대신 읽히는 텍스트로 접근성과 SEO에 영향을 준다)가 비어있었다.

    마침 이 시점에 워드프레스에 새로 생긴 ‘커넥터’ 화면을 발견했다. AI API 키(외부 AI 서비스를 사용하기 위한 인증 코드)를 저장해두는 코어 기능인데, 여기에 연결하면 대표 이미지·대체 텍스트·제목을 AI로 자동 생성해주는 공식 플러그인을 설치할 수 있는 구조였다.

    잠깐 혹했다. 근데 생각해보면 이미 답이 있었다. 썸네일 이미지 안에 들어가는 내용 자체가 글 제목이다. AI를 부를 이유가 없다. 제목 텍스트를 그대로 alt로 넣으면 충분하다. 새 플러그인 설치 없이, 스크립트에 코드 몇 줄 추가하는 것으로 끝났다.

    스크린샷 대신 코드로 브랜드 썸네일 자동 생성하기 (AI 크레딧 0원) 스크린샷 3

    미디어 라이브러리에서 alt 텍스트가 채워진 상태다. 간단한 해결이었는데, 찾는 데 오히려 시간이 걸렸다.

    배포할 때 실제로 주의할 점

    이 작업을 따라 해보려는 사람이 있다면, 몇 가지는 반드시 미리 알고 시작해야 한다.

    • 글 하나만 먼저 테스트해라. 14개를 한 번에 돌리기 전에 반드시 글 하나에만 스크립트를 적용하고 결과를 확인해야 한다. 일괄 처리 스크립트는 잘못될 경우 피해 규모도 일괄이다.
    • 기존 썸네일을 먼저 백업해라. 미디어 라이브러리에서 원본 이미지를 지우지 마라. 대표 이미지만 갈아끼우고 기존 파일은 남겨두면, 문제가 생겼을 때 되돌릴 수 있다.
    • API 인증 정보를 코드 안에 직접 쓰지 마라. 워드프레스 REST API를 쓰려면 사용자 이름과 애플리케이션 비밀번호(Application Password, 워드프레스에서 API 접근용으로 따로 발급하는 비밀번호)가 필요하다. 이 값은 .env 파일(환경 변수를 따로 저장하는 설정 파일)에 넣고, 절대 GitHub(코드를 공개 저장하는 플랫폼) 같은 곳에 올리지 마라.
    • 폰트 문제는 환경마다 다르다. macOS에서 한글이 잘 나왔다고 서버에서도 잘 나온다는 보장이 없다. 리눅스 서버 환경에서 자동화할 계획이라면 폰트 파일을 따로 지정하는 방식을 써야 한다.

    참고로 이 썸네일, 글 상세 페이지에서는 다른 비율로 잘려서 나온 적이 있었다. 이미지 생성 코드는 멀쩡했는데 템플릿 설정이 문제였다.

    이번 작업을 끝내고 나서 하나 다시 확인한 게 있다. 새로운 기능이 필요할 때 플러그인이나 유료 API부터 찾는 건 좋은 습관이 아니다. 이미 가진 데이터로 해결되는지 먼저 따져봐야 한다. alt 텍스트가 그랬다. AI를 부르기 직전에 “잠깐, 제목이 이미 있잖아”라는 생각 하나로 불필요한 의존성이 사라졌다.

    다음에는 썸네일 디자인 자체에 변화를 줘볼 생각이다. 카테고리별로 배경색을 다르게 주는 방식. 지금은 전부 인디고 단색인데, 카테고리가 시각적으로 구분되면 홈 화면의 카드 그리드가 한 번 더 정돈될 것이다. 아직 확신은 없다. 일단 해봐야 안다.

  • 대표 이미지가 잘리는 이유, 템플릿 비율 3:2→16:9 차이

    대표 이미지가 잘리는 이유, 템플릿 비율 3:2→16:9 차이

    이미지가 잘린다는 제보를 받았다. 대표 이미지에 넣은 ‘AgentLog’ 로고 텍스트가 앞글자 ‘A’만 잘려서 ‘gentLog’처럼 보인다는 캡처였다.

    내 화면에서는 멀쩡했다. 데스크탑도, 모바일 에뮬레이션(브라우저에서 개발자 도구로 흉내 내는 모바일 화면)도 전부 정상이었다. 그래서 처음엔 캐시(cache, 웹사이트 속도를 높이려고 이전 화면을 임시 저장해두는 것) 문제라고 생각했다.

    아니었다.

    같은 이미지인데 왜 어디서는 잘릴까?

    이미지 자체의 문제가 아니었다. 소스 이미지는 1200×675픽셀, 정확히 16:9 비율로 만들었다. 문제는 그 이미지를 감싸는 HTML 박스의 비율이 페이지마다 달랐다는 것이다.

    블로그 홈 카드(목록 화면에서 글을 미리 보여주는 카드형 UI)에서는 이미지 박스가 16:9였다. 그런데 글 상세 페이지의 대표 이미지 블록(block, 워드프레스에서 콘텐츠를 구성하는 단위)은 3:2였다.

    object-fit: cover는 이미지를 박스에 꽉 채우되, 비율이 안 맞으면 삐져나온 부분을 잘라낸다. 16:9 이미지를 3:2 박스에 넣으면 가로가 남는다. 남은 부분이 양옆에서 잘린다. 그래서 로고 텍스트의 왼쪽이 사라진 것이다.

    CSS만 보고 “object-fit: cover면 다 똑같이 나오겠지” 하고 넘어갔으면 계속 몰랐을 문제다. 같은 이미지라도 어느 템플릿(template, 페이지 구조를 정해놓은 틀)에서 렌더링(rendering, 코드를 실제 화면으로 그려내는 것)되느냐에 따라 잘리는 방식이 완전히 달라진다.

    curl로 실제 코드를 확인해보기, 원인은 HTML에 있었다

    눈으로 확인이 안 되니까 curl(curl, 터미널에서 웹 주소로 요청을 보내 응답을 받아보는 도구)로 라이브 페이지의 실제 HTML을 직접 떠봤다.

    홈 카드에서 뽑은 <img> 태그:

    style="aspect-ratio:16/9"

    글 상세 페이지에서 뽑은 <img> 태그:

    style="aspect-ratio:3/2"

    숫자가 달랐다. 홈 카드용 CSS는 직접 16:9로 맞춰놨는데, 글 상세 페이지는 워드프레스 테마 템플릿 자체의 대표 이미지 블록 설정이 3:2로 고정돼 있었다.

    대표 이미지가 자꾸 잘려 보인 이유 — 템플릿마다 다른 비율(3:2 vs 16:9) 스크린샷 1

    위 캡처가 그 차이를 그대로 보여준다. 같은 이미지, 다른 숫자 하나.

    원인은 명확했다. 이제 고치면 됐다.

    워드프레스 템플릿을 REST API로 수정하기

    보통은 사이트 에디터(Site Editor, 워드프레스 관리자 화면에서 페이지 구조를 시각적으로 편집하는 도구) UI에 들어가서 블록을 클릭하고 비율 드롭다운을 바꾼다. 몇 번 클릭하면 끝나는 일이다.

    그런데 워드프레스 최신 버전은 이 템플릿을 REST API(REST API, 웹에서 데이터를 주고받는 표준 방식. 주소에 요청을 보내면 결과가 돌아온다)로도 읽고 쓸 수 있다. 주소는 이렇다:

    /wp-json/wp/v2/templates/{id}

    이 엔드포인트의 정식 스펙은 워드프레스 공식 REST API Handbook에 나와 있다. GET 요청으로 템플릿 원본 콘텐츠를 읽어오면, 안에 이런 블록 설정이 문자열로 들어있다:

    <!-- wp:post-featured-image {"aspectRatio":"3/2"} /-->

    이 부분을 "aspectRatio":"16/9"로 바꾸고, POST 요청으로 다시 보내면 끝이다. UI를 열고 찾고 클릭하는 것보다 빠르다. 실수할 여지도 적다. 바꿀 문자열이 정확히 무엇인지 보이니까.

    API 인증은 워드프레스 애플리케이션 비밀번호(Application Password, 관리자 화면 사용자 설정에서 발급하는 API 전용 비밀번호)를 쓰면 된다. curl에 -u 유저명:앱비밀번호를 붙이면 된다.

    캐시가 삭제 안 될 때, save_post 훅으로 우회하기

    템플릿은 고쳤다. 그런데 반영이 안 됐다.

    이전에도 다뤘던 라이트스피드 캐시가 예전 3:2 버전 HTML을 계속 들고 있었다. 원래라면 관리자 화면 상단바의 ‘전체 캐시 삭제’ 버튼을 누르면 된다. 그런데 브라우저 자동화 도구가 그 시점에 완전히 죽어있었다. 관리자 화면 자체에 들어갈 수가 없었다. 몇 번을 재시도해도 ‘browser closed’ 에러만 떴다.

    이건 좀 웃겼다. 템플릿은 API로 고쳐놨는데 캐시 하나 못 지워서 막힌 상황이었다.

    라이트스피드 REST API에 캐시 퍼지 전용 엔드포인트가 있는지 먼저 찾아봤다. 없었다.

    대신 워드프레스의 기본 동작을 이용했다. 글을 REST API로 다시 저장하면 , 실질적인 내용 변경 없이, status 값을 그대로 재전송해서 저장만 트리거하면 , 워드프레스의 save_post 훅이 실행된다. 라이트스피드는 이 훅에 연결돼 있어서, 훅이 실행되면 해당 글의 캐시를 자동으로 비운다.

    발행된 글 8개, 예약된 글 6개. 총 14개를 이 방식으로 한 번씩 재저장시켰다.

    캐시가 실제로 비워졌는지는 응답 헤더로 확인했다. curl에 -I 옵션을 붙이면 헤더만 볼 수 있다. x-litespeed-cache: miss가 뜨면 캐시 없이 원본 서버에서 응답이 온 것이다.

    대표 이미지가 자꾸 잘려 보인 이유 ,  템플릿마다 다른 비율(3:2 vs 16:9) 스크린샷 2

    miss가 확인된 다음에야 진짜 반영된 걸 믿었다.

    초보자도 활용할 수 있는 API 우회법

    UI가 막혔을 때 바로 포기하지 마라. 워드프레스는 REST API로 할 수 있는 일이 생각보다 많다.

    • 템플릿 읽기·쓰기: GET/POST /wp-json/wp/v2/templates/{id}
    • 글 저장 트리거: POST /wp-json/wp/v2/posts/{id} , 바디에 {"status":"publish"}만 넣어도 된다
    • 캐시 반영 확인: curl -I로 x-litespeed-cache 헤더 값을 본다

    인증은 반드시 애플리케이션 비밀번호를 써야 한다. 관리자 계정 비밀번호를 API에 직접 쓰지 마라. 관리자 화면 → 사용자 → 프로필 → 애플리케이션 비밀번호 항목에서 발급하면 된다.

    그리고 save_post 훅 방식은 캐시를 지우는 게 목적이지, 글 내용을 바꾸는 게 아니다. 실수로 status를 draft로 보내면 발행된 글이 비공개로 바뀐다. 요청 바디를 보내기 전에 현재 status 값을 GET으로 먼저 읽어야 한다. 당연한 말이지만 한 번은 짚고 넘어가야 한다.

    예전에 홈페이지 메타 설명이 반영 안 됐던 문제도 비슷했다 , 설정 화면에 값이 있다고 실제로 쓰이는 건 아니었다.

    이번 문제는 솔직히 이미지 생성 코드가 아니라 템플릿 설정을 끝까지 확인하지 않은 데서 생겼다. 홈 카드 CSS는 직접 썼으면서, 글 상세 페이지 템플릿 설정은 기본값을 그냥 믿었다.

    다음에 이미지 비율을 다루는 기능을 만들면, 렌더링되는 페이지 전부의 템플릿 설정을 먼저 확인하고 시작할 것이다.

    같은 워드프레스 주제로 이어서 읽으면 좋은 글: WPCode로 보안 헤더 추가하다 코드가 깨진 이유, 이미지가 삐져나가는 버그, 원인 찾고 자동화까지 한 후기, 워드프레스 SEO 플러그인 충돌 피하기 , 켜야 할 것, 꺼야 할 것.

  • 워드프레스 SEO 버그 5개, curl로 찾은 공통 원인

    워드프레스 SEO 버그 5개, curl로 찾은 공통 원인

    블로그에 글이 15개쯤 쌓였을 때 돌아봤다. 그중 5개가 전부 워드프레스 SEO 설정을 자동화하거나 고치다가 만난 버그 이야기였다. 흩어져 있으니까 불편했다. 나중에 비슷한 문제를 다시 만났을 때 어느 글을 찾아야 할지 헷갈렸다. 그래서 한 페이지에 몰아서 정리하기로 했다.

    설정을 저장했는데도 작동하지 않은 이유

    다섯 개를 나란히 놓고 보니까 패턴이 보였다. 전부 같은 이야기였다.

    ‘설정 화면에 값이 있다고 해서 실제로 그게 적용되고 있다는 보장은 없다.’

    WPCode에서는 코드를 저장했는데 실제로는 깨진 채로 배포됐다. 랭크매스에서는 홈페이지 메타 설명을 입력해도 워드프레스 읽기 설정 때문에 그 값이 조용히 무시됐다. REST API(워드프레스에 데이터를 보내고 받는 통로)로 포커스 키워드를 보내도 저장이 안 되는 경우도 여러 번 있었다.

    관리자 화면의 저장 성공 메시지, REST API의 200 응답, 설정 페이지에 입력된 값. 이런 것들은 전부 ‘내가 뭔가를 입력했다’는 증거일 뿐이다. ‘그게 실제로 작동하고 있다’는 증거가 아니다.

    아래 다섯 개가 그 증거다.

    버그 1: 랭크매스 점수가 재계산되지 않는 이유 (16점→76점의 함정)

    랭크매스(워드프레스용 SEO 플러그인)가 매기는 SEO 점수가 16점이었다. 뭘 고쳐야 할지 몰랐다. 점수만 보면 아무것도 알 수 없었다.

    처음에는 REST API로 점수를 강제로 재계산시키려고 했다. 여기서 함정에 빠질 뻔했다. API 응답은 200으로 떴다. 성공처럼 보였다. 그런데 실제 점수는 그대로였다.

    원인은 단순했다. 랭크매스 SEO 점수는 wp-admin(워드프레스 관리자 에디터 화면)을 직접 열어야 제대로 계산된다. API로 우회하면 점수가 갱신되지 않는다. 에디터를 열고 나서야 점수가 76점으로 올라갔다.

    이 경험에서 배운 것은 하나다. API 응답이 성공이어도 실제 결과를 화면에서 직접 확인해야 한다. 200은 ‘요청이 도착했다’는 뜻이지 ‘원하는 대로 됐다’는 뜻이 아니다.

    → 전체 진단 과정: 랭크매스 SEO 16점→76점, API 함정 피하고 올린 과정

    버그 2: WPCode 보안 헤더가 깨지는 현상 (저장은 됐는데 적용 안 됨)

    WPCode(워드프레스에 코드 조각을 추가하는 플러그인)로 보안 헤더(Security Header, 브라우저에 보안 정책을 전달하는 HTTP 응답값)를 추가했다. PHP 코드 스니펫(snippet, 짧은 코드 조각)을 넣고 저장했다. 저장 성공 메시지가 떴다.

    그런데 실제로는 헤더가 적용되지 않고 있었다.

    curl(터미널에서 웹 페이지 응답을 직접 확인하는 명령어)로 라이브 페이지 응답 헤더를 확인하고 나서야 알았다. 보안 헤더가 없었다. 원인은 사소한 문법 오류였다. PHP 코드 안에 오타가 있었고, 그 오류 때문에 코드 전체가 실행되지 않았다.

    저장 성공 메시지만 보고 넘어갔으면 계속 몰랐을 거다. WPCode는 코드 내용의 오류를 검증하지 않는다. 저장 자체는 항상 성공한다. 그래서 더 위험하다.

    보안 설정을 추가한 뒤에는 반드시 curl로 응답 헤더를 직접 확인해야 한다. 저장 성공 메시지를 믿지 마라.

    curl -I https://내-사이트-주소.com

    위 명령어를 터미널에 입력하면 응답 헤더 목록이 나온다. 추가한 헤더 이름이 거기 있어야 진짜 적용된 거다.

    → 전체 진단 과정: WPCode로 보안 헤더 추가하다 코드가 깨진 이유

    버그 3~4: fire-your-seo-agency와 AI Link Genius로 만난 예상 밖의 문제들

    fire-your-seo-agency는 Claude Code에서 쓸 수 있는 스킬(skill, AI에게 특정 작업 방식을 지정하는 설정)이다. 이걸로 블로그 4개를 한꺼번에 SEO 점검했다.

    중간에 파일이 세 번 중복으로 받아지는 해프닝이 있었다. 이건 좀 웃겼다. 그래도 생각보다 빠르게 끝났다. 스킬 하나로 블로그 4개를 동시에 점검한다는 게 실제로 되는 일이라는 걸 이때 처음 확인했다.

    → 전체 진단 과정: fire-your-seo-agency 스킬로 4개 블로그 자동 SEO 최적화한 후기

    네 번째 버그는 AI Link Genius 이야기다. AI Link Genius는 랭크매스 안에 있는 내부 링크 분석 기능이다. 설치는 해놨는데 한 번도 안 써봤다. 어느 날 다시 살펴보다가 고아 포스트(Orphan Post, 다른 글에서 링크를 하나도 받지 못한 글)가 2개 있다는 걸 발견했다.

    그 글들은 검색 엔진이 찾아오기 어려운 상태였다. 내부 링크를 걸어주고 나서야 제대로 연결됐다.

    → 전체 진단 과정: 랭크매스 무료 기능으로 고아 포스트 2개 찾아낸 이야기

    이 두 버그에서 공통으로 배운 게 있다. 설치는 해놨지만 한 번도 안 써본 플러그인 기능이 있는지 주기적으로 점검해야 한다. 있는 기능을 모르고 있으면 없는 거나 마찬가지다.

    버그 5: 메타 설명이 검색엔진에 반영 안 되는 이유 (읽기 설정의 함정)

    홈페이지가 색인됐는데 메타 설명이 이상했다. 랭크매스에 분명히 입력해뒀는데, 검색 결과에 다른 문구가 나왔다.

    ‘색인 = 노출’이라고 착각하고 있었다. 아니었다. 색인됐어도 메타 설명이 틀리면 검색 결과가 이상하게 보인다.

    원인은 워드프레스 읽기 설정이었다.

    워드프레스 관리자 화면 → 설정 → 읽기 에서 홈페이지 표시가 ‘최신 글’로 설정되어 있으면, 랭크매스에 입력한 홈페이지 메타 설명이 무시된다. 대신 워드프레스 기본 태그라인이 메타 설명 자리에 들어간다.

    랭크매스 설정 화면에는 분명히 내가 쓴 문구가 있었다. 그런데 실제 HTML에는 태그라인이 들어가 있었다. curl로 확인하기 전까지 몰랐다.

    curl -s https://내-사이트-주소.com | grep -i "meta name=\"description\""

    위 명령어로 실제 HTML에 박힌 메타 설명을 확인할 수 있다. 설정 화면에 뭐가 적혀 있든 상관없다. 이 결과가 진짜다.

    홈페이지 메타 설명을 제대로 쓰려면 읽기 설정에서 ‘정적 페이지’를 선택하고 홈페이지를 지정해야 한다. 그래야 랭크매스가 입력한 값을 실제로 가져다 쓴다.

    → 전체 진단 과정: 메타 설명 오류 — 색인됐는데 검색 안 되던 이유

    다음 버그를 빨리 찾는 4가지 확인 순서 (curl 포함)

    다섯 번을 겪고 나서 확인 순서가 생겼다. 비슷한 문제를 만나면 이 순서로 본다.

    • 첫째, 설정을 저장한 뒤 바로 curl로 라이브 페이지를 확인한다. 저장 성공 메시지는 신뢰하지 않는다. curl -s 주소 | grep 확인할내용 으로 실제 HTML에 반영됐는지 바로 본다.
    • 둘째, REST API 응답이 200이어도 응답 바디를 다시 읽는다. 200은 요청이 도착했다는 뜻이다. 원하는 처리가 됐다는 뜻이 아니다. 응답 안에 실제로 뭐가 담겨 있는지 확인해야 한다.
    • 셋째, 설정 화면 말고 ‘진짜 소스’가 따로 있는지 의심한다. 워드프레스는 표면 설정 화면 뒤에 그 값을 실제로 가져다 쓰는 다른 설정이 있을 수 있다. 읽기 설정과 태그라인이 그 예다. 플러그인 설정만 보면 안 된다.
    • 넷째, 설치는 해놨지만 한 번도 안 써본 플러그인 기능을 주기적으로 점검한다. AI Link Genius처럼, 있는 기능인지 모르고 넘어간 것들이 반드시 있다.

    이 네 가지는 순서대로 해야 빠르다. 첫째를 건너뛰고 셋째부터 보다가 시간을 날린 적이 있다. 순서를 지켜라.

    지금은 5개지만, 앞으로 비슷한 버그를 만날 때마다 이 글에 추가할 생각이다. 워드프레스 SEO 설정을 직접 만지다가 막히는 사람에게 이 페이지가 첫 번째 참고 지점이 됐으면 한다. 경험이 쌓일수록 이 목록도 길어질 거다.

    같은 워드프레스 주제로 이어서 읽으면 좋은 글: 대표 이미지가 잘리는 이유 , 템플릿 비율 3:2→16:9 차이, 이미지가 삐져나가는 버그, 원인 찾고 자동화까지 한 후기, 워드프레스 SEO 플러그인 충돌 피하기 , 켜야 할 것, 꺼야 할 것.