2026년 10월 27일 화요일
미단재

끄적임 CMS 에디터가 내 CSS를 지웠다 — 소개 페이지 삽질기

작성자 웅파 작성일 2026-10-26 10:51:02 조회수 9

2026년 10월 26일 · @martin

소개 페이지 하나 만들려다가

미단재(jblove.net)는 도연과 내가 같이 쓰는 사이트다. 도연은 그림과 서예를, 나는 개발과 AI 기록을 올린다. 글은 쌓여 가는데 정작 "웅파가 누구인지" 알려주는 페이지가 없었다.

그래서 Claude와 함께 소개 페이지를 만들었다. 미단재 브랜드 색인 주황→갈색 그라데이션을 깔고 히어로, 숫자로 보는 이력(PHP 26년, SaaS 7개, 책 1권, 로스팅 4년), 기술 스택, 타임라인, 제품 소개, 취미, 연락처까지 넣었다. 로컬 HTML로 열어 보니 꽤 그럴듯했다.

문제는 이걸 사이트에 올린 다음부터였다.

증상: 올리자마자 CSS가 전부 증발

관리자 화면 에디터에 HTML을 통째로 붙여 넣고 저장했다. 그리고 jblove.net/aboutUF를 열었는데, 그라데이션도 카드도 칩도 없었다. 그냥 기본 스타일의 텍스트 덩어리만 세로로 길게 늘어져 있었다.

로컬에서는 멀쩡한 파일이다. 캐시 문제인가 싶어 새로고침도 몇 번 해봤지만 그대로였다. 이럴 땐 추측보다 실제 페이지의 DOM을 보는 게 빠르다.

원인 1: 에디터가 <style>과 <link>를 통째로 지운다

브라우저 콘솔에서 본문 영역(.page-content)을 들여다봤다.

const pc = document.querySelector('.page-content');
({
  styleInContent: pc.querySelectorAll('style').length,  // 0
  linkInContent:  pc.querySelectorAll('link').length,   // 0
  inlineStyleKept: !!pc.querySelector('[style]')
})

분명히 넣었던 <style> 블록이 0개였다. 저장하는 순간 에디터가 <style>과 <link> 태그를 조용히 날려버린 것이다. 에러도 경고도 없었다.

다행히 class 속성은 그대로 살아 있었다. 마크업은 남고 스타일만 사라진 상태, 그래서 텍스트 덩어리만 보였던 것이다.

원인 2: 에디터가 마크업까지 "정리"한다

CSS를 콘솔로 강제 주입해 보니 대부분 살아났다. 그런데 태그 칩, 기술 스택 칩, 숫자 통계 부분이 여전히 이상했다. 해당 요소의 innerHTML을 찍어보고 원인을 찾았다.

  • 클래스 없는 <span>은 삭제된다. <div class="tags"><span>PHP</span>… 같은 구조에서 span이 벗겨지고 텍스트만 남았다. 의미 없는 래퍼로 판단한 모양이다.
  • <b>는 <strong>으로 바뀐다. CSS는 .stat b를 잡고 있었는데 실제 DOM에는 strong만 있으니 숫자 강조가 먹지 않았다.

리치 텍스트 에디터 입장에서는 친절한 정리겠지만, 직접 짠 레이아웃을 넣는 입장에서는 마크업을 몰래 고쳐 쓰는 셈이다. 그래서 수정 방향은 두 가지였다. 모든 span에 명시적 클래스를 붙이고(tag, chip 등), CSS 선택자는 b가 아니라 strong을 기준으로 바꿨다.

<!-- 전 -->
<div class="tags"><span>PHP</span></div>
<div class="stat"><b>26</b>년</div>

<!-- 후 -->
<div class="tags"><span class="tag">PHP</span></div>
<div class="stat"><strong>26</strong>년</div>

덤으로 만난 함정: 화면은 1920px, 본문은 510px

CSS가 살아나자 이번엔 레이아웃이 비좁게 뭉개졌다. 미단재 본문은 Bootstrap의 .col-lg-8 컬럼 안에 들어가는데, 실제 폭이 약 510px밖에 안 된다. 그런데 브라우저 뷰포트는 1920px이다.

@media (max-width: 720px) 같은 미디어 쿼리는 뷰포트 기준이라 데스크톱에서는 절대 발동하지 않는다. 510px짜리 칸에 3열 그리드가 그대로 우겨넣어진 것이다.

해법은 컨테이너 쿼리였다. 소개 페이지 최상위 요소를 컨테이너로 선언하고, 분기 기준을 "내가 들어 있는 칸의 폭"으로 바꿨다.

.wp-about {
  container-type: inline-size;
  container-name: about;
}

@container about (max-width: 720px) {
  .wp-about .grid  { grid-template-columns: 1fr; }
  .wp-about .stats { grid-template-columns: repeat(2, 1fr); }
}

CMS 본문처럼 남의 레이아웃 안에 끼워 넣는 콘텐츠라면 미디어 쿼리보다 컨테이너 쿼리가 맞다.

해결: 스타일은 파일로 빼고, 본문은 에디터 친화적으로

에디터가 <style>을 못 받아준다면 스타일을 본문에 넣지 않으면 된다. 파일을 두 개로 쪼갰다.

  1. about.css — 모든 규칙을 .wp-about 범위로 가둔 전용 스타일시트. 서버의 public/assets/css/에 올린다.
  2. about-body.html — <div class="wp-about">…</div> 본문 조각만. span마다 클래스를 붙이고 <b>는 <strong>으로 미리 바꿔둔 버전이다.

CSS는 CI4 레이아웃에서 불러온다. 다른 페이지에 영향이 없도록 소개 페이지에서만 로드하게 url_is()로 감쌌다.

<?php if (url_is('aboutUF')): ?>
    <?= link_tag('assets/css/about.css') ?>
<?php endif ?>

본문은 에디터를 HTML(소스) 모드로 바꾼 뒤 about-body.html 내용으로 통째로 교체했다. 이제 에디터가 지울 게 없다. 스타일은 레이아웃에 있고, 본문에는 클래스가 달린 마크업만 있으니까.

검증: 업로드 전에 라이브 페이지에서 먼저 확인

이번 작업에서 제일 쓸모 있었던 건 검증 방식이다. 파일을 서버에 올리기 전에, Claude가 크롬 확장으로 실제 jblove.net 페이지를 열고 콘솔에서 <style> 태그를 <head>에 직접 주입했다.

const st = document.createElement('style');
st.id = 'wp-test';
st.textContent = css;   // about.css 내용
document.head.appendChild(st);

그 상태로 스크린샷을 찍어 렌더링을 눈으로 확인했다. 로컬 HTML이 아니라 에디터가 가공을 마친 진짜 DOM, 진짜 510px 컬럼 위에서 테스트한 셈이다. span이 사라진 것도, strong 변환도 이 과정에서 잡혔다.

로컬 미리보기는 CMS가 무엇을 지우는지 알려주지 않는다. 최종 결과물이 놓일 자리에서 확인해야 삽질이 한 번으로 끝난다.

정리: CMS에 직접 짠 HTML을 넣을 때 체크할 것

  • 에디터는 <style>·<link>를 지울 수 있다. 스타일은 본문 밖(레이아웃, 별도 CSS 파일)에 둔다.
  • 에디터는 마크업을 정리한다. 클래스 없는 래퍼는 지워질 수 있으니 스타일을 걸 요소에는 전부 클래스를 단다. b→strong 같은 변환도 CSS에서 같이 잡는다.
  • 본문 폭은 뷰포트 폭이 아니다. 남의 레이아웃 안에 들어가는 콘텐츠는 컨테이너 쿼리로 반응형을 짠다.
  • 스코프를 가둔다. 모든 규칙을 .wp-about 아래에 둬서 사이트 테마와 충돌하지 않게 한다.
  • 라이브 DOM에서 검증한다. 저장 후 실제 페이지에 CSS를 주입해 확인하고 나서 파일을 올린다.

다음에 이런 페이지가 또 생기면 매번 CSS 파일을 업로드하는 것도 번거롭다. 페이지 테이블에 custom_css 컬럼을 하나 추가하고, 렌더링할 때 레이아웃에서 <style>로 출력하는 방식으로 바꿔볼 생각이다. 에디터를 거치지 않으니 지워질 일도 없다.

26년을 PHP로 먹고살았어도 남이 만든 에디터 앞에서는 여전히 겸손해야 한다. 오늘의 교훈이다.

댓글 0개