블로그 구축기 일곱 번째 글이다. 지난 글에서 프레임워크를 ASP.NET Core 10으로 옮겼고, 바로 이어서 디자인을 바꿨다. 지금 보고 있는 이 화면이 그 결과다.
무엇을 바꿨나
처음 디자인은 Bootstrap 5를 CDN으로 불러 쓴 기본 레이아웃이었다. 이를 Tailwind CSS v4로 다시 만들었다.
- 화면: Tailwind Plus v4 키트의 HTML 컴포넌트를 바탕으로 만들었다. 상단 메뉴, 글 목록, 페이지 이동, 푸터, 관리자 화면의 사이드바·표·폼이 여기에 해당한다. 메뉴 열고 닫기 같은 동작은
@tailwindplus/elements가 맡는다. - 글 본문:
@tailwindcss/typography플러그인의prose스타일 - 글꼴: Pretendard
- 라이트·다크 모드: 시스템 설정(
prefers-color-scheme)을 따른다. - 블로그 이름: AddsoftBlog에서 애드소프트로 바꾸면서 OG 이미지, 로고, 파비콘도 새로 만들었다.
CSS 빌드를 dotnet build에 끼워 넣기
Tailwind는 CDN 파일 하나로 끝나는 Bootstrap과 다르다. 화면 파일(우리는 .cshtml)에 쓰인 클래스를 찾아서 필요한 CSS만 만들어 내는 빌드 단계가 있다. 그래서 npm으로 Tailwind CLI를 설치하고, Styles/app.css를 wwwroot/css/app.css로 빌드하게 했다.
여기에 원칙을 하나 정했다. 개발자도 배포 스크립트도 따로 npm 명령을 기억할 필요가 없게 한다. 그래서 프로젝트 파일에 빌드 단계를 넣었다.
<Target Name="TailwindBuild" BeforeTargets="BeforeBuild" Condition="'$(SkipTailwind)' != 'true'">
<Exec Command="npm ci --no-audit --no-fund" Condition="!Exists('node_modules')" />
<Exec Command="npm run build:css --silent" />
<ItemGroup>
<Content Include="wwwroot\css\app.css" Exclude="@(Content)" />
</ItemGroup>
</Target>
- 처음 빌드할 때
node_modules가 없으면npm ci로 설치부터 한다. - 빌드할 때마다 CSS를 새로 만든다. 만들어진
app.css는 git에서 제외한다. - 마지막
ItemGroup이 중요하다. 이 파일은 빌드 도중에 생기므로, MSBuild가 미리 모아 둔 파일 목록에 들어 있지 않다. 직접 추가하지 않으면dotnet publish결과에서 빠진다.
이렇게 해서 dotnet build와 dotnet publish만으로 CSS까지 만들어진다. 배포 스크립트는 한 줄도 바꾸지 않았다. 대신 빌드하는 PC에는 Node.js가 있어야 한다는 조건이 생겼고, 이를 배포 문서의 개발 PC 요구 사항에 적어 두었다.
함정 1. 마크다운 표의 class="table"
글 본문은 마크다운으로 쓰고 Markdig로 HTML을 만든다. Bootstrap 시절에는 Markdig의 UseBootstrap() 확장을 켜 두었다. 이 확장은 <table>에 class="table", 이미지에 class="img-fluid"처럼 Bootstrap 클래스를 자동으로 붙여 준다.
Tailwind로 바꾸자 이게 문제가 됐다. Tailwind에도 table이라는 유틸리티 클래스가 있는데, 뜻은 display: table이다. 본문 표는 좁은 화면에서 가로로 스크롤되도록 display: block; overflow-x: auto를 주고 있었다. 그런데 class="table"의 display: table이 이를 덮어쓴다. 그러면 모바일에서 넓은 표가 스크롤되지 않고 화면 밖으로 밀려난다.
UseBootstrap()을 빼는 것으로 끝나지 않았다. 이 블로그는 글을 저장할 때 HTML을 만들어 DB에 같이 저장한다. 렌더러 설정을 바꿔도 이미 저장된 글의 HTML에는 class="table"이 그대로 남아 있다.
그래서 본문 표 규칙을 Tailwind의 레이어 밖에 두었다.
/*
* 글 본문 표는 좁은 화면에서 가로 스크롤.
* 레이어 밖에 두어 유틸리티(예: 예전 글 HTML 의 class="table" → display:table)보다 우선한다
*/
.post-content table {
display: block;
overflow-x: auto;
}
Tailwind v4는 CSS 캐스케이드 레이어(@layer)를 쓰고, 유틸리티는 utilities 레이어에 들어간다. 레이어 밖에 있는 규칙은 선택자 우선순위와 관계없이 레이어 안의 규칙보다 우선한다. 그래서 예전 글의 class="table"이 남아 있어도 본문 표 규칙이 이긴다.
함정 2. 코드 블록 배경이 두 겹
코드 블록이 상자 안에 상자가 든 모양으로 나왔다. 원인은 두 스타일이 각자 배경을 칠한 데 있었다.
- typography 플러그인의
prose가<pre>에 어두운 배경과 여백을 준다. - 코드 색칠을 맡은 highlight.js 테마가 안쪽
<code class="hljs">에 또 배경과 여백을 준다.
배경은 <pre> 한 겹만 칠하고, 안쪽 code는 투명하게 했다.
.post-content pre {
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-xl);
background: var(--color-gray-50);
color: var(--color-gray-800);
}
.post-content pre code.hljs {
background: transparent;
padding: 0;
}
다크 모드에서는 <pre>의 배경과 글자색만 바꾼다. highlight.js 테마도 라이트용과 다크용을 둘 다 넣고, <link>의 media 속성으로 시스템 설정에 맞는 쪽만 적용되게 했다.
<link rel="stylesheet" href=".../github.min.css" media="(prefers-color-scheme: light)" />
<link rel="stylesheet" href=".../github-dark.min.css" media="(prefers-color-scheme: dark)" />
함정 3. 보이지 않는 글자가 모바일 폭을 넓혔다
관리자 글 목록을 모바일에서 열자 페이지 전체가 옆으로 스크롤됐다. 범인은 표 머리글에 있던, 화면에는 보이지 않는 글자였다.
<th scope="col"><span class="sr-only">작업</span></th>
sr-only는 화면에서는 숨기고 스크린 리더에는 읽히게 하는 클래스다. 내부적으로는 1px 크기의 position: absolute 요소다. 절대 위치 요소는 가장 가까운 "위치가 지정된" 조상을 기준으로 놓인다. 표를 감싼 가로 스크롤 영역에 기준이 없으면, 이 요소는 스크롤 영역 밖으로 나가 넓은 표의 오른쪽 끝 위치에 놓인다. 그만큼 페이지의 폭이 넓어진다.
해결은 스크롤 영역에 relative를 주어 기준을 잡아 주는 것이었다.
<div class="relative mt-6 overflow-x-auto ui-card">
<table>...</table>
</div>
정리하며
- 프레임워크 전용 출력을 걷어 낼 때는 저장된 데이터를 확인한다. 렌더러를 바꿔도 DB에 저장된 HTML은 그대로다. 새 글만이 아니라 예전 글에서도 확인해야 한다.
- Tailwind v4의 캐스케이드 레이어를 이용한다. 꼭 이겨야 하는 규칙은 레이어 밖에 두면
!important없이도 우선한다. - 보이지 않는 요소도 레이아웃에 영향을 준다. 모바일에서 가로 스크롤이 생기면
sr-only같은 절대 위치 요소와 그 기준 요소부터 살펴본다. - 빌드 단계는 기존 빌드 명령에 숨긴다. 새 도구를 들여도 개발자와 배포 스크립트가 쓰는 명령은 그대로 두는 편이 실수가 적다.
다음 글은 시리즈의 마지막으로, 쿠키 하나로 만든 방문 통계 이야기를 다룬다.