Ujinify

  • WPCode로 보안 헤더 추가하다 코드가 깨진 이유

    WPCode로 보안 헤더 추가하다 코드가 깨진 이유

    SEO 감사 결과를 받아들고 액션 아이템 목록을 훑었다. 생각보다 할 게 많았다. 그중에서 오늘 WPCode로 건드린 건 두 가지다.

    SEO 감사에서 나온 보안 헤더 추가, 왜 하려고 했나?

    감사 리포트에 보안 헤더(HTTP Security Header, 브라우저에게 보안 정책을 알려주는 응답값)가 빠져 있다고 나왔다. 구체적으로는 세 가지였다. HSTS(HTTP Strict Transport Security, HTTPS 연결만 허용하도록 강제하는 헤더), X-Content-Type-Options(브라우저가 파일 형식을 멋대로 해석하지 못하게 막는 헤더), Referrer-Policy(다른 사이트로 이동할 때 내 사이트 주소를 얼마나 보낼지 제어하는 헤더). 검색엔진 점수에 직접 영향을 주는 항목은 아니지만, 빠져 있으면 감사 점수가 깎인다.

    여기에 하나 더 추가하고 싶은 게 있었다. llms.txt다. llms.txt는 AI 크롤러(ChatGPT, Claude 같은 AI가 사이트 정보를 수집할 때 읽어가는 파일)가 내 사이트 구조를 파악할 수 있도록 만들어두는 텍스트 파일이다. robots.txt(검색엔진 크롤러에게 크롤링 규칙을 알려주는 파일)의 AI 버전이라고 보면 된다.

    문제는 서버 접근 방법이었다. 예전에 랭크매스 SEO 점수를 16점에서 76점까지 올릴 때도 비슷하게 REST API만으로는 한계가 있었는데, 이번에도 그랬다. FTP(File Transfer Protocol, 서버에 파일을 올리고 내리는 방식)도 없고 SSH(Secure Shell, 서버에 원격으로 접속하는 방식)도 없었다. 서버에 직접 파일을 올릴 수 있는 방법이 아예 없는 환경이었다. 그래서 선택한 게 WPCode다. WPCode는 워드프레스 관리자 화면에서 PHP 스니펫(snippet, 짧은 코드 조각)을 직접 추가하고 실행할 수 있는 플러그인(plugin, 워드프레스에 기능을 추가하는 확장 프로그램)이다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 1

    위는 WPCode 플러그인 편집 화면이다. PHP 코드를 여기에 붙여넣고 활성화하면 워드프레스가 코드를 실행한다.

    WPCode 에디터의 CodeMirror 자동완성이 PHP 코드를 깨뜨린 문제

    WPCode의 코드 에디터(code editor, 코드를 입력하고 수정하는 편집 창)는 CodeMirror라는 라이브러리(library, 특정 기능을 묶어놓은 코드 모음)로 만들어져 있다. 편집기 기능이 꽤 풍부한데, 그중에 자동 괄호 완성 기능이 있다. 괄호를 하나 열면 닫는 괄호를 자동으로 붙여주는 기능이다.

    이게 문제였다. Claude Code로 에디터에 타이핑을 시켰더니, 자동완성이 끼어들면서 코드 중간에 괄호가 두 개씩 들어가거나 엉뚱한 위치에 삽입되는 일이 계속 생겼다. PHP 코드는 괄호 하나만 틀려도 에러가 난다. 별로였다.

    그러다가 예상치 못한 확인창이 떴다. 뭘 클릭한 건지도 모르겠는 상태에서 페이지가 about:blank로 넘어갔다. about:blank는 아무것도 없는 빈 페이지다. 입력하던 코드가 전부 날아갔다. 저장도 안 된 상태로.

    타이핑 대신 setValue()로 코드를 통째로 밀어넣다

    다시 페이지를 열었다. 이번엔 방식을 바꿨다.

    CodeMirror는 자바스크립트(JavaScript, 웹 브라우저에서 실행되는 프로그래밍 언어)로 제어할 수 있다. 에디터 인스턴스(instance, 실행 중인 특정 객체)에 setValue()라는 메서드(method, 객체가 수행할 수 있는 기능)를 호출하면, 타이핑 없이 코드를 통째로 집어넣을 수 있다. 자동완성이 개입할 여지가 없다. 한 번에 전체 내용이 들어가니까.

    브라우저 콘솔(console, 개발자 도구에서 자바스크립트를 직접 실행할 수 있는 창)에서 아래처럼 실행했다.

    document.querySelector('.CodeMirror').CodeMirror.setValue(`여기에 PHP 코드 전체`)

    코드가 깨끗하게 들어갔다. 자동완성 간섭 없이. 저장하고 활성화한 뒤 curl(curl, 터미널에서 HTTP 요청을 보내는 명령어 도구)로 확인했더니 세 헤더가 전부 응답에 포함돼 있었다. HSTS, X-Content-Type-Options, Referrer-Policy. 다 떴다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 2

    curl 응답에서 세 보안 헤더가 실제로 찍힌 결과다. 헤더가 응답에 없으면 아무리 코드를 넣어도 동작하지 않은 것이니, 이 확인 단계는 건너뛰면 안 된다.

    보안 헤더는 성공했는데 llms.txt는 404가 뜬 이유

    같은 방식으로 llms.txt도 만들었다. WPCode에 스니펫을 하나 더 추가해서, 특정 URL로 접근하면 llms.txt 내용을 텍스트로 응답하도록 했다. setValue()로 코드를 넣었으니 입력 과정은 문제없었다.

    그런데 curl로 확인하니 이상했다. 본문 내용은 정확하게 나왔다. llms.txt에 넣으려던 텍스트가 그대로 출력됐다. 그런데 HTTP 상태 코드(HTTP Status Code, 서버가 요청에 대해 응답할 때 같이 보내는 숫자 코드)가 404였다.

    404는 “없는 페이지”라는 뜻이다. 내용은 있는데 없는 페이지라고 응답하는 이상한 상태였다.

    상태 코드를 명시적으로 지정해야 했던 워드프레스의 특성

    워드프레스는 URL을 처리하는 방식이 독특하다. 등록된 페이지나 포스트가 아닌 URL로 요청이 들어오면, 워드프레스는 그 URL을 404로 먼저 처리해버린다. llms.txt는 워드프레스에 등록된 페이지가 아니다. 그러니까 워드프레스 입장에서는 없는 URL이다.

    PHP 코드로 본문 내용을 echo(echo, PHP에서 텍스트를 출력하는 명령어)해도, 워드프레스가 이미 404로 결정한 상태 코드는 바뀌지 않는다. 내용은 출력되지만 상태 코드는 404 그대로 남는 거다. 검색엔진이나 AI 크롤러는 상태 코드를 보고 판단한다. 404면 없는 페이지다. 내용이 아무리 정확해도 무시된다.

    해결 방법은 status_header(200)을 코드에 추가하는 거였다. status_header()는 워드프레스에서 HTTP 상태 코드를 강제로 지정하는 함수(function, 특정 작업을 수행하는 코드 묶음)다. 이걸 echo 앞에 넣어서 200으로 바꿔줬다.

    그래도 처음엔 안 됐다. 라이트스피드 캐시(LiteSpeed Cache, 웹사이트 속도를 높이기 위해 페이지를 미리 저장해두는 플러그인)가 이전 404 응답을 저장해두고 있었다. 캐시 퍼지(purge, 저장된 캐시를 강제로 삭제하는 작업)를 하고 나서야 curl에서 200이 확인됐다.

    WPCode로 보안 헤더·llms.txt 추가하다 겪은 삽질기 스크린샷 3

    캐시 퍼지 후 curl로 다시 확인한 결과다. 상태 코드가 200으로 바뀐 걸 확인할 수 있다. 이 단계 없이는 제대로 됐는지 알 수 없다.

    코드 에디터 자동화와 HTTP 헤더를 다루면서 배운 두 가지

    이번 작업에서 정리된 게 두 가지다.

    첫 번째. 자동화 도구로 코드 에디터를 다룰 땐 타이핑을 시키면 안 된다. 자동완성, 자동 들여쓰기, 단축키 처리 같은 에디터 기능이 전부 방해 요소가 된다. 에디터 인스턴스에 직접 값을 주입하는 방식을 써야 한다. CodeMirror라면 setValue()가 그 방법이다. 타이핑은 사람이 할 때만 자연스럽다.

    두 번째. 워드프레스에서 커스텀 URL(custom URL, 워드프레스에 등록되지 않은 주소)을 서빙할 땐 본문 내용만 맞춰선 끝이 아니다. HTTP 상태 코드를 직접 200으로 지정해야 한다. 안 하면 워드프레스가 404를 내보낸다. 크롤러는 그 페이지를 없는 페이지로 처리한다. llms.txt를 만들어도 아무도 읽지 않는 상황이 된다.

    다음엔 llms.txt 내용을 실제로 AI 크롤러가 제대로 읽어가는지 확인해볼 생각이다. 200이 뜨는 건 확인했는데, 실제로 색인(index, 검색엔진이나 AI가 페이지 내용을 자신의 데이터베이스에 등록하는 것)이 되는지는 별개 문제다. 아직 모른다.

  • 블로그 자동 크로스포스팅, 알아보고 완전 보류한 이유

    블로그 자동 크로스포스팅, 알아보고 완전 보류한 이유

    자동 크로스포스팅, 편하면 좋겠다고 생각한 게 시작이었다. ujinify.com 글이 올라갈 때마다 레딧과 X에도 같이 뿌려지면 손이 덜 갈 것 같았다.

    편하려던 것 하나로 시작했는데…

    ujinify.com에 글을 올릴 때마다 레딧이랑 X에도 자동으로 같이 올라가면 어떨까. 그런 생각이 들었다. 그래서 Claude Code(AI 코딩 도우미)한테 바로 물어봤다. 지금 블로그에 글을 업로드하면 레딧이나 X에도 자동 업로드되게 할 수 있냐고.

    Claude Code는 가능하다고 했다. 근데 “가능하다”는 말이 끝이 아니었다. 확인해야 할 게 줄줄이 나왔다.

    먼저 뭐부터 확인해야 할까? (API 키 체크)

    API 키(API key, 외부 서비스를 코드로 사용할 때 필요한 비밀번호 같은 값)부터 확인했다. 이미 가지고 있으면 바로 붙일 수 있으니까.

    내가 쓰는 프로젝트 폴더(project folder, 하나의 작업 단위로 묶인 파일 모음)가 11개다. 블로그, 숏츠, 릴스 관련 작업들이 각각 분리돼 있다. 각 폴더 안에 있는 .env, .env.local 파일(환경변수 파일, API 키 같은 민감한 값을 저장해두는 설정 파일)을 전부 뒤졌다. TWITTER, X_API, REDDIT 관련 키워드로 전부 검색했다.

    결과는 깔끔했다. 하나도 없었다.

    처음부터 앱을 새로 등록하고 키를 발급받아야 하는 상황이었다. 그 순간부터 “이게 정말 해볼 만한 건지”를 다시 따져보기 시작했다.

    프로젝트 폴더 11개의 .env 파일을 전부 검색해 레딧·X API 키가 없는 것을 확인한 터미널 화면

    위 화면이 실제로 11개 프로젝트 폴더를 뒤진 결과다. 관련 키가 하나도 없는 걸 직접 확인했다.

    X 크로스포스팅, 요금이 생각보다 깐깐했다

    X(구 트위터) API(Application Programming Interface, 외부 서비스 기능을 코드로 연결하는 통로) 공식 요금 페이지를 확인했다. 무료 티어(tier, 등급)가 있겠거니 했다. 없었다.

    구독제도 아니다. 완전 종량제(사용한 만큼만 요금을 내는 방식)다. 구조는 이렇다.

    트윗 유형 건당 요금
    일반 트윗 $0.015
    링크 포함 트윗 $0.200

    문제는 내 블로그 글에는 항상 링크가 들어간다는 거다. 크로스포스팅이라는 게 결국 블로그 링크를 X에 올리는 일이니까, 건당 $0.200이 적용된다.

    한 달에 8개 올린다고 치면 $1.6. 그렇게 보면 큰돈은 아니다. 근데 매번 돈이 나가는 구조라는 게 확인됐다. 자동화(automation, 반복 작업을 코드가 대신 처리하게 만드는 것)를 붙인다는 건 이 비용이 쌓인다는 뜻이다. 올리는 글이 늘어날수록 같이 늘어난다.

    레딧이 더 무섭다: 계정 정지의 위험성

    레딧(Reddit, 주제별 게시판이 모인 미국 커뮤니티 플랫폼) API는 개인용 소규모 스크립트(script, 특정 작업을 자동으로 처리하는 짧은 코드)라면 무료로 열려 있다고 한다. 돈 문제가 아니다.

    진짜 문제는 계정 정지다.

    레딧의 거의 모든 서브레딧(subreddit, 레딧 안에 있는 주제별 소규모 게시판)은 자기 홍보 링크 비율을 엄격하게 제한한다. 흔히 말하는 10분의 1 법칙 같은 게 있다. 홍보 아닌 글 9개당 홍보 글 1개 정도만 허용된다는 기준이다.

    기계적으로 같은 도메인(domain, 웹사이트 주소) 링크를 반복 게시하면 스팸 필터(spam filter, 도배성 게시물을 자동으로 차단하는 시스템)에 걸린다. 계정이 아예 밴(ban, 영구 이용 정지)될 수 있다. 자동 크로스포스팅은 이 패턴을 정확하게 만들어낸다.

    ujinify.com 글 올릴 때 레딧·X에 자동 크로스포스팅 하고 싶었는데, 알아보고 나서 완전 보류로 정했다 스크린샷 2

    실제로 Claude Code와 이 리스크를 확인하는 과정에서 나온 내용이다. 기술적으로 가능하다는 것과, 해도 된다는 건 다른 문제였다.

    기존에 쌓아온 신뢰도가 한 번에 깎인다는 걸 알다

    이 부분이 가장 크게 와닿았다.

    지금까지 레딧이나 네이버 지식iN에 백링크(backlink, 다른 사이트에서 내 사이트로 연결되는 링크) 작업을 할 때는 직접 했다. 관련 질문 스레드(thread, 하나의 주제로 이어진 게시물과 댓글 묶음)를 직접 찾아서, 맥락에 맞게, 조심스럽게 답변을 달았다. 그렇게 쌓은 신뢰도가 있다.

    자동 크로스포스팅을 붙이면 그게 한 번에 깎인다. 방향이 완전히 반대다.

    신경 써서 만들어온 계정 신뢰도를 자동화 스크립트 하나가 스팸 계정으로 만들 수 있다. 그걸 알고 나니 선택지가 명확해졌다.

    최종 결정: 완전 보류로 정한 이유

    보류했다. 완전히.

    X는 기술적으로는 된다. 근데 건당 비용이 계속 나간다. 레딧은 API보다 계정 정지 위험이 훨씬 크다. 두 채널 다 지금 당장 붙일 이유가 없었다.

    지금 우선순위는 두 가지다. 애드센스(AdSense, 구글 광고 수익 프로그램) 승인, 그리고 ujinify.com 자체 SEO(Search Engine Optimization, 검색엔진 최적화, 검색 결과 상위에 노출되게 만드는 작업) 노출이다. 여기에 자동 크로스포스팅을 얹는 건 도움보다 리스크가 크다.

    나중에 블로그가 어느 정도 자리를 잡으면 그때 다시 검토한다. 계정 신뢰도가 충분히 쌓이고, 수동으로도 어느 정도 커뮤니티 활동이 된 뒤에. 지금은 아니다.

    편하려고 시작한 건데, 알아볼수록 지금은 하지 않는 게 맞다는 결론이 나왔다. 그게 이번 조사의 결과다.

    기술적으로 되는 것과, 지금 해도 되는 것은 다른 질문이다. 이번에 자동 크로스포스팅을 알아보면서 그 차이를 제일 크게 배웠다. 앞으로도 새 자동화를 붙이기 전에는 비용과 리스크부터 먼저 따지기로 했다.

  • 랭크매스 SEO 16점→76점, API 함정 피하고 올린 과정

    랭크매스 SEO 16점→76점, API 함정 피하고 올린 과정

    처음 랭크매스 점수를 확인했을 때 16점이 떴다. 대표 이미지도 넣었고, 카테고리도 설정했다. 뭘 더 해야 하는지 몰랐다.

    왜 다 설정했는데 랭크매스 점수가 16점에서 안 움직였을까?

    이상했다. 분명히 다 채웠는데 점수가 꼼짝을 안 했다. 처음엔 랭크매스 플러그인(WordPress에 설치해서 SEO를 관리하는 도구) 자체 문제인가 싶었다.

    원인은 다른 데 있었다. API(Application Programming Interface, 프로그램끼리 데이터를 주고받는 통로)로 포커스 키워드(검색엔진에 “이 글은 이 단어에 집중합니다”라고 알려주는 핵심 단어)를 설정했는데, 그게 실제 랭크매스 에디터(글을 작성하고 편집하는 화면) 화면에는 반영이 안 되고 있었다. 코드로는 값을 넣었다. 근데 랭크매스는 그걸 모르는 상태였다.

    겉으로는 다 설정된 것처럼 보였다. 실제 점수는 그대로였다. 이 두 가지가 동시에 사실이었다.

    API로 설정한 SEO 정보, 실제로는 먹히지 않는다?

    결론부터 말하면, 먹히지 않는다. 적어도 랭크매스 점수 계산에는.

    Claude Code 같은 AI 도구로 WordPress API를 통해 글을 발행할 때, SEO 관련 메타(meta, 글의 제목·설명·키워드 같은 부가 정보) 값을 함께 넣을 수 있다. 기술적으로는 데이터가 전달된다. 문제는 랭크매스가 점수를 다시 계산하지 않는다는 거다.

    wp-admin(WordPress 관리자 페이지) 에디터를 직접 열어보지 않으면 이걸 알 방법이 없다. 포커스 키워드 입력란이 비어 있다. 점수는 16점. API로 아무리 잘 넣어도 랭크매스 입장에선 설정 안 된 상태다.

    이거 모르고 그냥 발행했으면 SEO 점수가 낮은 채로 글이 올라갈 뻔했다. 실제로 그럴 뻔했다.

    랭크매스 점수 16점에서 76점까지 끌어올린 실제 화면

    위 화면이 그 상태다. API로 다 넣었다고 생각했는데, 에디터에서 확인하면 포커스 키워드 칸이 비어 있고 점수는 16점 그대로다.

    에디터에서 포커스 키워드를 직접 입력했을 때 점수가 폭발한 이유

    wp-admin에서 해당 글을 열었다. 랭크매스 패널(화면 한쪽에 붙어 있는 설정 영역)을 찾아서 포커스 키워드를 직접 손으로 입력했다. 저장했다.

    16점에서 57점이 됐다.

    한 단어 입력이 41점을 올렸다. 이게 왜 가능하냐면, 랭크매스는 포커스 키워드를 기준으로 점수를 계산하기 때문이다. 키워드가 없으면 제목에 키워드가 있는지, 본문에 키워드가 있는지 아무것도 판단을 못 한다. 기준이 생기자마자 이미 작성된 글의 내용들이 한꺼번에 점수에 반영된 거다.

    API 설정이 쓸모없다는 게 아니다. 랭크매스 점수 계산만큼은 에디터 직접 입력이 기준이라는 거다. 이 차이를 모르면 계속 헤맨다.

    57점에서 76점까지, 하나씩 고쳐가며 올린 체크리스트

    57점에서 멈추지 않았다. 뭘 더 고쳐야 하는지 랭크매스가 항목별로 알려준다. 하나씩 고쳤다. 찔끔찔끔 올랐다. 이 과정이 은근히 나쁘지 않았다. 뭘 고쳤을 때 점수가 오르는지 직접 눈으로 보니까.

    • SEO 제목(검색 결과에 표시되는 글 제목)에 포커스 키워드 포함 — 제목 앞쪽에 키워드가 있어야 한다.
    • 메타 설명(검색 결과에서 제목 아래 뜨는 짧은 소개글)에 키워드 포함 — 키워드가 자연스럽게 들어가야 한다. 억지로 끼워 넣는 느낌이면 별로다.
    • 본문 시작 부분에 키워드 포함 — 글 앞쪽 한두 문단 안에 키워드가 나와야 한다.
    • 소제목(h2, h3 태그로 표시되는 중간 제목)에 키워드 포함 — 모든 소제목에 다 넣을 필요는 없다. 한두 군데면 된다.
    • 내부 링크(같은 사이트 안의 다른 글로 연결하는 링크) 1개 추가
    • 외부 링크(다른 사이트로 연결하는 링크) 1개 추가

    이 여섯 가지를 순서대로 적용했다. 76점까지 올랐다. 한 번에 다 한 게 아니라, 하나 고치고 저장하고 점수 확인하고, 다시 고치는 식으로 진행했다.

    완벽한 100점을 목표로 하지 않아도 된다. 랭크매스 기준으로 70점 이상이면 초록색으로 바뀐다. 76점이면 충분하다.

    API vs 에디터 직접 설정, 뭐가 다르고 어떻게 확인할까?

    정리하면 이렇다.

    구분 API 설정 에디터 직접 설정
    데이터 저장 된다 된다
    랭크매스 점수 반영 안 된다 된다
    포커스 키워드 인식 안 된다 된다
    확인 방법 wp-admin 에디터 열어봐야 앎 저장하면 바로 점수 업데이트

    확인 방법은 하나다. wp-admin에서 해당 글의 편집 화면을 직접 열어라. 랭크매스 패널이 보이면 포커스 키워드 칸이 채워져 있는지 확인해라. 비어 있으면 API 설정은 점수에 아무 영향이 없는 상태다.

    API로 글을 자동 발행하는 워크플로(작업 흐름)를 쓰고 있다면, 발행 후에 반드시 wp-admin에서 한 번 더 열어서 SEO 설정을 수동으로 확인해야 한다. 귀찮다. 근데 안 하면 낮은 점수로 그냥 올라간다.

    자동화가 전부를 해결해주지는 않는다. 이번에 그걸 배웠다. 다음엔 발행 체크리스트에 “wp-admin SEO 확인” 항목을 아예 넣어둘 생각이다. 결국 랭크매스 점수 하나 때문에 발행 프로세스 자체를 다시 점검하게 됐다.

  • 이미지가 삐져나가는 버그, 원인 찾고 자동화까지 한 후기

    이미지가 삐져나가는 버그, 원인 찾고 자동화까지 한 후기

    이미지가 삐져나가는 버그, 처음엔 그냥 넘어갈 뻔했다. 블로그 글을 올리고 나서 멀쩡히 보이는지 확인하는 건 습관이다. 그날도 그냥 훑어보고 있었다.

    이미지가 삐져나가는 버그, 눈에 띄는 이상함: 스크린샷 하나가 삐져나가 보였던 이유

    본문을 읽다가 뭔가 이상했다. 스크린샷 하나가 오른쪽으로 쏠려 있었다. 다른 이미지들은 멀쩡한데 그것만.

    이유는 몰랐다. 그냥 딱 봐도 어색했다. 바로 Claude Code(클로드 코드, AI 기반 코딩 보조 도구)에 말했다. “지금 본문에서 이 이미지만 오른쪽으로 쏠려있어. 사이즈 조절해서 중앙으로 보기 좋게 다시 작성해줘.” 원인 분석보다 고치는 게 먼저였다.

    아래가 당시 상태다. 텍스트 컬럼(column, 본문이 들어가는 세로 영역) 오른쪽으로 이미지가 넘쳐 있다.

    이미지가 삐져나가는 버그 — 텍스트 컬럼 밖으로 삐져나온 스크린샷

    이렇게 이미지가 본문 영역 밖으로 삐져나오면 레이아웃(layout, 화면 배치 구조) 전체가 깨진 것처럼 보인다. 작은 것 같지만 실제로 읽는 사람 입장에서는 바로 티가 난다.

    원인은 간단했다: 업로드 스크립트가 이미지를 줄이지 않고 있었네

    원인은 허무했다. 업로드 스크립트(script, 특정 작업을 자동으로 처리하는 코드 파일)가 이미지를 그대로 박아넣고 있었다. 리사이즈(resize, 이미지 크기를 줄이거나 늘리는 작업) 없이. 원본 그대로.

    원본 이미지의 가로 크기가 본문 컬럼 폭보다 훨씬 넓었다. 그러니 오른쪽으로 넘칠 수밖에 없다. 알고 나니 별거 아니었다. 근데 이게 지금까지 계속 이렇게 올라가고 있었다는 게 더 문제였다.

    이 버그(bug, 코드나 프로그램에서 발생하는 오류) 자체는 금방 고칠 수 있다. 문제는 따로 있다.

    버그 수정만으로는 부족한 이유: 같은 실수가 반복되면?

    지금 이 이미지만 고치고 끝내면 어떻게 될까. 다음에 글 올릴 때 또 같은 일이 생긴다. 스크립트가 그대로니까.

    버그를 발견했을 때 그 버그만 패치(patch, 문제가 생긴 부분만 부분적으로 고치는 것)하고 넘어가는 건 임시방편이다. 원인이 코드 안에 남아 있으면 같은 실수는 반드시 다시 나온다. 타이밍만 다를 뿐이다.

    그래서 바로 한 마디 더 붙였다. “처음부터 작성할 때 이런 것 체크해서 최종 마무리해줘.” 재발 방지까지 해야 진짜 끝난 거라고 생각했다.

    자동화로 근본 해결하기: 스크립트에 리사이즈 기능 추가하기

    수정 방향은 하나였다. 업로드 스크립트 자체를 고치는 것.

    이미지를 올릴 때마다 수동으로 크기를 확인하는 건 지속 가능하지 않다. 귀찮으면 빠뜨리게 돼 있다. 그러니 스크립트가 알아서 처리하게 만드는 게 맞다.

    Claude Code에 요청해서 업로드 스크립트를 수정했다. 핵심은 두 가지다.

    • 새 글을 올릴 때 이미지가 자동으로 본문 폭에 맞게 리사이즈된다.
    • 리사이즈된 이미지는 중앙 정렬(center align, 요소를 화면 가운데에 배치하는 것)로 삽입된다.

    이제 이미지 크기를 신경 쓰지 않아도 된다. 스크립트가 처리한다. 이게 자동화(automation, 사람이 직접 하던 작업을 코드가 대신 처리하도록 만드는 것)의 의미다. 한 번 제대로 만들어두면 그 다음부터는 신경 끄면 된다.

    재발 방지까지: 업로드 후 최종 확인 프로세스 정하기

    스크립트를 고치는 것만으로는 완전하지 않다고 봤다. 코드가 제대로 동작하는지 눈으로 확인하는 단계가 없으면 또 다른 문제가 조용히 지나갈 수 있다.

    그래서 규칙을 명확히 정했다. Claude Code에 이렇게 못 박았다.

    • 이미지는 본문 폭 안에 딱 맞게, 중앙 정렬로 들어가야 한다.
    • 업로드 후에는 Claude Code가 직접 최종 확인을 하고, 그 결과를 나한테 컨펌(confirm, 맞는지 확인하고 승인받는 것)받아야 한다.
    • 스크린샷으로 실제 화면을 직접 확인하는 것을 기본으로 한다.

    코드만 고치고 “됐겠지” 하고 넘어가지 않는다. 반드시 눈으로 확인한다. 이게 기준이다.

    이 사례에서 배울 점: 코드만 고치지 말고 프로세스도 함께

    이번 건 작은 버그였다. 이미지 하나가 삐져나간 것. 근데 거기서 멈추지 않은 게 핵심이다.

    버그를 고치는 건 시작이다. 왜 생겼는지 파악하고, 같은 일이 반복되지 않도록 구조를 바꾸고, 확인 단계까지 만드는 것이 진짜 마무리다.

    AI 도구를 쓸 때 “고쳐줘”만 하고 끝내는 사람이 많다. 그러면 똑같은 질문을 다음 달에 또 하게 된다. “왜 또 이러지?” 하면서.

    물어봐야 할 건 두 가지다.

    • 지금 이걸 어떻게 고치냐.
    • 앞으로 이게 안 생기려면 뭘 바꿔야 하냐.

    두 번째 질문을 안 하면 첫 번째 질문을 계속 반복한다.

    다음에는 업로드 스크립트 전체를 한 번 훑어볼 생각이다. 이미지 말고 다른 곳에서도 비슷한 게 조용히 지나가고 있을 수 있다. 결국 이미지가 삐져나가는 버그 하나가 전체 업로드 파이프라인을 점검하게 만든 셈이다.

    비슷한 패턴은 다른 데서도 반복됐다. WPCode로 보안 헤더 추가하다 코드가 깨진 사례도 결국 같은 교훈이었다 — 자동화 도구를 만들 땐 “일단 되는 것”과 “제대로 확인된 것”은 다르다.

  • Claude Code로 UI 재설계하면서 배운 AI 협업 꿀팁과 함정

    Claude Code로 UI 재설계하면서 배운 AI 협업 꿀팁과 함정

    Claude Code 사용법을 실전에서 익혔다. Claude Code로 블로그 UI를 처음 손봤다. 홈 화면 재설계, 스크롤 목차, 로봇 로고 브랜딩까지 한꺼번에 작업했다. 결론부터 말하면, 빠르지 않았다. 하지만 혼자 했으면 못 끝냈을 작업을 끝냈다.

    Claude Code 사용법, UI를 직접 만들어보니 어땠나?

    Claude Code는 AI(인공지능) 기반 코딩 도구다. 터미널(terminal, 텍스트로 컴퓨터에 명령을 내리는 창)에서 명령어를 치면, AI가 코드를 직접 읽고 수정하고 실행까지 해준다. 에디터(editor, 코드를 작성하는 프로그램)를 오가며 복사 붙여넣기 할 필요가 없다.

    이번에 작업한 항목은 세 가지였다. 홈 화면 레이아웃(layout, 화면 구성 방식) 재설계, 글 목록을 따라다니는 스크롤 목차, 그리고 블로그 로고 교체. 규모가 작지 않았다.

    Claude Code로 홈 화면 카드 그리드 재설계, 스크롤 따라다니는 목차, 로봇 로고+파비콘까지 브랜딩 다시 한 후기 스크린샷 1

    재설계를 마친 지금 홈 화면이다. 글 목록이 카드 그리드로 정리됐다.

    처음엔 속도가 붙는 느낌이었다. “홈 화면 카드 간격 늘려줘”, “목차 위치 고정해줘” 같은 말만 해도 AI가 코드를 찾아서 고쳐줬다. 근데 작업이 길어질수록 문제가 생기기 시작했다.

    텍스트 안 보임, 위치 계산 오류… 실제로 마주친 버그들

    제일 시간을 잡아먹은 버그는 두 가지였다. 텍스트가 안 보이는 문제, 그리고 요소(element, 화면을 구성하는 개별 블록) 위치 계산 오류.

    텍스트가 안 보이는 건 CSS(Cascading Style Sheets, 화면의 색상·크기·위치를 정하는 코드) 문제였다. 배경색이랑 글자색이 같아져버리는 식이다. 눈에 안 보이니까 버그인지도 한참 몰랐다. 위치 계산 오류는 더 번거로웠다. 스크롤 목차가 엉뚱한 곳에 붙거나, 화면 밖으로 튀어나갔다.

    Claude Code로 홈 화면 카드 그리드 재설계, 스크롤 따라다니는 목차, 로봇 로고+파비콘까지 브랜딩 다시 한 후기 스크린샷 2

    새로 만든 헤더다. 로봇 로고와 카테고리 메뉴가 나란히 붙었다.

    이런 버그들은 Claude Code한테 증상을 설명하면 고쳐줬다. 완전히 못 고친 건 아니다. 다만 시간이 걸렸다. “텍스트가 안 보여”라고 말하면 원인을 찾고, 수정하고, 다시 확인하는 과정을 반복해야 했다. 빠른 척하지만 한 번에 되는 경우는 드물었다.

    모르는 버그가 더 있을 수 있다는 것도 문제다. 눈에 보이는 버그는 고칠 수 있다. 눈에 안 보이는 버그는 존재 자체를 모른다.

    AI가 기억을 못 하는 문제, 어떻게 대처했나?

    이게 진짜 스트레스였다.

    Claude Code는 대화 맥락(context, AI가 기억할 수 있는 대화의 범위)이 길어지면 앞에서 한 말을 잊는다. 정확히는 잊는다기보다 우선순위가 밀린다. 내가 “이 스타일은 건드리지 마”라고 했어도, 나중에 다른 수정을 요청하면 그 스타일을 다시 바꿔놓는 경우가 생겼다.

    더 황당한 경우도 있었다. 내가 특정 레퍼런스(reference, 참고할 예시)를 요청했는데, AI가 그걸 무시하고 자기가 찾은 걸 벤치마킹(benchmarking, 다른 사례를 참고해 기준을 잡는 것)해버렸다. 내 요구사항이 아니라 AI가 판단한 방향으로 가버린 거다. 이건 버그인지 특성인지도 모르겠다.

    Claude Code로 홈 화면 카드 그리드 재설계, 스크롤 따라다니는 목차, 로봇 로고+파비콘까지 브랜딩 다시 한 후기 스크린샷 3

    글을 스크롤하면 옆에 목차가 따라다니고, 지금 읽고 있는 항목이 강조 표시된다.

    그래서 이렇게 대처했다.

    • 작업 단위를 작게 쪼개야 한다. 한 번에 많은 걸 요청하면 맥락이 흔들린다.
    • 중요한 조건은 매 요청마다 반복해서 명시해야 한다. 한 번 말했다고 AI가 기억한다고 믿지 마라.
    • AI가 자의적으로 바꾼 부분이 없는지 결과물을 꼭 직접 확인해야 한다. 확인 없이 넘어가면 나중에 더 꼬인다.
    • 레퍼런스를 요청할 때는 URL(인터넷 주소)이나 구체적인 예시를 함께 줘야 한다. 막연하게 “이런 느낌으로”는 AI 마음대로 해석된다.

    궁극적으로는 버그를 스스로 찾아서 고치는 AI를 원하는데, 지금 Claude Code는 거기까지는 못 간다. 내가 발견한 것만 고친다. 이 한계는 명확히 알고 써야 한다.

    로봇 로고를 선택한 이유: AI 시대를 표현하다

    로고를 바꿨다. 이전 로고 얘기는 길게 안 한다. 중요한 건 왜 로봇으로 바꿨냐는 거다.

    지금이 AI 시대다. 이 블로그도 AI 도구를 활용하는 내용을 다룬다. 그걸 가장 직관적으로 보여주는 이미지가 로봇이라고 판단했다. 복잡한 이유는 없다.

    Claude Code로 홈 화면 카드 그리드 재설계, 스크롤 따라다니는 목차, 로봇 로고+파비콘까지 브랜딩 다시 한 후기 스크린샷 4

    최종적으로 정한 로봇 로고 아트웍이다.

    스타일 방향은 Claude Code가 몇 가지 후보를 제안했고, 그중에서 로봇 아이콘을 직접 골랐다. AI 시대를 표현하는 데 제일 직관적인 이미지라고 판단했다. 지금 보니 잘 고른 것 같다.

    로고 작업 자체는 다른 버그들에 비해 순탄했다. 방향이 명확하면 AI도 잘 따라온다. 방향이 흐릿할 때 문제가 생긴다.

    완성 후 느낀 점: 완벽함보다는 계속된 개선이 정답

    버그 때문에 시간은 오래 걸렸다. 하지만 완성은 했다.

    완성된 걸 보고 “완벽하다”는 생각은 안 들었다. 완성인지 미완성인지도 솔직히 아직 모른다. 지금은 만족스럽다. 근데 계속 쓰다 보면 만족스럽지 못한 부분이 또 나올 거다. 그게 당연한 과정이다.

    Claude Code로 홈 화면 카드 그리드 재설계, 스크롤 따라다니는 목차, 로봇 로고+파비콘까지 브랜딩 다시 한 후기 스크린샷 5

    워드프레스에 등록된 로봇 파비콘이다. 브라우저 탭에 이렇게 뜬다.

    지금은 계속 사용하면서 버그를 잡고 있다. 더 유용한 기능이나 플러그인(plugin, 기존 프로그램에 기능을 추가하는 확장 도구)이 있는지도 알아보는 중이다. Claude Code도 계속 쓰면서 어떻게 하면 더 잘 쓸 수 있는지 파악하고 있다.

    AI 도구는 만능이 아니다. 기억을 못 하고, 자의적으로 판단하고, 눈에 안 보이는 버그는 그냥 둔다. 그걸 알고 쓰는 것과 모르고 쓰는 건 결과가 다르다. 다음 작업은 이번에 파악한 한계를 기준으로 접근할 생각이다.

  • Claude Code 7개 에이전트로 블로그 자동화 파이프라인 만들기

    Claude Code 7개 에이전트로 블로그 자동화 파이프라인 만들기

    블로그 자동화 파이프라인, 이번에 직접 만들어봤다.

    블로그 글 하나를 쓰려면 생각보다 손이 많이 간다. 인터뷰 정리하고, 개요 짜고, 본문 쓰고, 표현 다듬고, 워드프레스에 올리고. 이 과정을 자동화하면 어떨까 싶었다.

    Claude Code로, 에이전트(agent, 특정 역할을 맡아 자동으로 작업하는 AI) 7개를 동원해서.

    결론부터 말하면, 생각대로 됐냐고? 반반이다.

    Subagent-Driven Development란 뭔가: 7개 에이전트가 한 프로젝트를 나눠 맡다

    이번 프로젝트에서 쓴 방식의 이름이 있다. Subagent-Driven Development, 줄여서 SDD다.

    개념은 단순하다. 하나의 큰 작업을 태스크(task, 작은 단위의 할 일) 여러 개로 쪼개고, 각 태스크를 완전히 새로운 서브에이전트(subagent, 특정 태스크만 담당하는 하위 AI)한테 맡긴다.

    서브에이전트는 자기한테 주어진 태스크만 구현한다. 전체 맥락을 모른다. 그냥 시킨 것만 한다.

    구현이 끝나면 리뷰어 서브에이전트가 따로 붙는다. 리뷰어는 두 가지를 본다.

    • 스펙(spec, 무엇을 만들어야 하는지 적은 명세서)과 구현이 일치하는가
    • 코드 품질은 괜찮은가

    이 두 가지를 별도로 검사한다. 한꺼번에 보지 않는다. 역할을 나눠야 각자 더 잘 본다는 논리다.

    왜 이렇게 하냐고? AI 에이전트 하나한테 처음부터 끝까지 다 맡기면 컨텍스트 윈도우(context window, AI가 한 번에 기억할 수 있는 텍스트 분량)가 터진다.

    코드가 길어질수록 앞에 있던 내용을 잊어버린다. 태스크를 잘게 쪼개면 각 에이전트는 자기 몫만 처리하면 된다. 컨텍스트 부담이 준다.

    블로그 자동화 파이프라인 — ujinify-blog-generator: 인터뷰에서 게시물까지

    만든 것의 이름은 ujinify-blog-generator다. 파이프라인(pipeline, 데이터가 순서대로 처리되는 흐름)이라고 부른다. 입력에서 출력까지 단계가 정해져 있으니까.

    흐름은 이렇다.

    • 실제 인터뷰 데이터(질문-답변 형식)를 입력으로 넣는다
    • 그걸 바탕으로 개요를 자동으로 짠다
    • 개요를 가지고 본문을 쓴다
    • AI 티가 나는 표현을 자연스럽게 고쳐쓴다
    • 완성된 글을 워드프레스에 자동으로 업로드한다

    이 각 단계가 태스크 하나씩이다. 총 7개의 태스크, 7개의 서브에이전트. 인터뷰를 넣으면 게시물이 나온다. 적어도 설계는 그랬다.

    블로그 자동화 파이프라인 실행 결과 — npm run generate 터미널 로그
    실제 generate 스크립트 실행 로그다. 인터뷰 데이터를 넣고 명령 하나 돌리면 본문까지 나온다.

    Git이 없는 프로젝트를 어떻게 관리했나: progress.md 방식의 진행 기록

    SDD 방식은 원래 깃(git, 코드 변경 이력을 저장하고 관리하는 도구)과 함께 쓰도록 설계됐다.

    워크트리(worktree, git에서 여러 브랜치를 동시에 다른 폴더로 펼쳐놓는 기능)로 브랜치를 분리하고, 커밋 diff(diff, 코드가 어떻게 달라졌는지 보여주는 변경 내역)를 리뷰 자료로 쓰는 식이다.

    그런데 이 프로젝트는 git 저장소가 아니었다. 그러니까 그 방식을 그대로 못 썼다.

    해결책은 단순하게 잡았다. .sdd/progress.md라는 파일 하나를 만들고, 거기에 진행 상황을 계속 기록했다.

    태스크가 끝날 때마다 업데이트했다. 리뷰어 에이전트한테는 diff를 주는 대신, 실제 파일을 직접 읽게 했다.

    우아한 방식은 아니다. 그냥 작동했다. 그게 목적이었으니까 됐다.

    .sdd/progress.md 파일에 기록된 7개 태스크 진행 상황, 각 태스크의 완료 상태와 리뷰 승인 여부가 표시된 실제 파일 화면
    git 없이 진행 상황을 추적하려고 직접 만든 progress.md 기록.

    각 태스크는 통과했는데 왜 버그가 4개나 났을까: 통합 테스트의 중요성

    7개 태스크, 7번의 리뷰. 전부 통과했다.

    그래서 다 됐다고 생각했다. 아니었다.

    마지막에 오퍼스(opus, Anthropic이 만든 AI 모델 중 가장 성능이 높은 버전)로 전체를 한 번 더 통합 리뷰(integration review, 부분이 아닌 전체를 한꺼번에 검토하는 것)했다.

    그랬더니 버그가 4개 나왔다. 각 태스크 리뷰를 전부 통과한 코드에서.

    어떤 버그였냐면:

    • AI가 문장을 자연스럽게 고쳐쓰는 단계에서 스크린샷 태그가 조용히 사라질 수 있는 문제
    • AI 응답을 파싱(parsing, AI가 돌려준 텍스트에서 필요한 값을 꺼내는 것)할 때 값이 비어 있어도 그냥 넘어가고, 나중에 업로드 단계에서 크래시(crash, 프로그램이 갑자기 멈추는 것)가 나는 문제
    • 응답이 중간에 잘려도(max_tokens, AI가 한 번에 출력할 수 있는 최대 글자 수 제한) 아무 체크 없이 그대로 사용할 뻔한 문제

    왜 이런 게 태스크 리뷰에서 안 잡혔냐. 이유가 명확하다. 각 태스크 리뷰는 “이 태스크 브리프(brief, 태스크에서 무엇을 해야 하는지 적은 짧은 설명서)와 구현이 일치하는가”만 본다.

    태스크 2에서 만든 값이 태스크 5에서 어떻게 쓰이는지는 태스크 2 리뷰어가 알 수 없다. 구조적으로 못 본다.

    이건 SDD 방식의 한계다. 태스크를 잘게 쪼갤수록 각 리뷰의 시야도 좁아진다.

    통합 테스트(integration test, 여러 부분을 합쳤을 때 전체가 제대로 동작하는지 확인하는 테스트)는 별도로 반드시 해야 한다. 태스크 리뷰가 다 통과했다는 말은 전체가 괜찮다는 말이 아니다.

    humanizer.ts 코드에서 최종 통합 리뷰 후 추가된 버그 수정 코드 두 군데가 초록색으로 강조 표시된 실제 diff 화면
    태스크 리뷰는 다 통과했지만, 최종 통합 리뷰에서 발견돼 나중에 추가한 안전장치.

    토큰 비용을 아끼기 위해 모델을 다르게 쓴 이유: haiku vs sonnet vs opus

    Claude 모델은 종류가 여러 개다. 그리고 비싸기도 다 다르다.

    모델 역할 이유
    haiku 각 태스크 구현 가장 저렴하다. 반복 작업에 쓴다.
    sonnet 태스크별 리뷰 중간 성능, 중간 가격. 리뷰는 구현보다 판단력이 필요하다.
    opus 최종 통합 리뷰 가장 비싸다. 전체를 한 번만 본다.

    논리는 단순하다. 비싼 모델을 모든 단계에 다 쓰면 비용이 선형으로 올라간다. 7개 태스크 구현에 opus를 쓸 필요가 없다.

    단순한 구현은 haiku로 충분하다. 판단이 필요한 리뷰는 sonnet을 쓴다. 가장 넓은 시야가 필요한 통합 리뷰에만 opus를 한 번 쓴다.

    역할에 맞는 모델을 쓰는 것. 이게 비용 관리의 핵심이다. 무조건 좋은 모델을 쓰는 게 답이 아니다.

    실제로 겪은 좌절과 배운 점: 자동화의 한계와 현실적인 조언

    솔직하게 쓴다.

    만들면서 헛된 꿈을 꿨다. 이걸로 수익을 벌 수 있을 거라는 생각. 지금 생각하면 웃긴다.

    애드센스(AdSense, 구글이 운영하는 블로그 광고 수익 프로그램) 승인도 받지 않은 상태에서 수익을 먼저 계산했다. 순서가 완전히 틀렸다.

    중간에 4번 태스크 구현하다가 API 레이트리밋(rate limit, API를 너무 자주 호출하면 일시적으로 막히는 것, 에러 코드 429)에 걸려서 멈췄다.

    이건 잠깐 기다렸다가 재시도하니까 풀렸다. 그나마 간단한 문제였다.

    더 힘든 건 따로 있었다. 버그가 계속 나오는데, 고치면 또 나오고.

    토큰은 토큰대로 쓰고, 사용량은 사용량대로 올라가는데 결과물은 내 마음대로 안 됐다.

    다 그만두고 싶다는 생각을 몇 번이나 했다. 한두 번이 아니었다.

    그때 심사 중이던 다른 애드센스 블로그에서 메일이 왔다. ‘가치가 별로 없는 콘텐츠’라는 이유로 불승인. 그거 보고 진짜로 힘이 빠졌다. ‘내가 지금 뭘 하고 있는 건가’ 싶었다.

    그래서 배운 게 뭐냐고 물으면, 이렇게 말한다.

    • 자동화는 반복 작업을 줄여준다. 판단을 대신해주지는 않는다.
    • 태스크 리뷰가 통과됐다고 끝이 아니다. 통합 테스트는 별도로 해야 한다.
    • 수익을 먼저 생각하지 마라. 콘텐츠 승인부터 받아라. 순서가 있다.
    • 버그가 계속 나오는 건 과정이다. 이상한 게 아니다. 그냥 고쳐야 한다.

    블로그 자동화 파이프라인은 일단 완성됐다. 그래서 다음에 뭘 하냐고. 일단 애드센스 신청 조건부터 다시 읽어볼 생각이다. 자동화 도구보다 그게 먼저다.

    참고로 크로스포스팅 자동화도 고려했었다. 근데 여러 이유로 결국 보류했다 — 그 판단 과정은 이 글에 따로 정리해놨다.

    이 블로그를 왜 시작했는지는 소개 페이지에 더 적어놨다.