Rails Turbo Frame으로 탭 SPA 만들기 -- 빈 페이지 함정과 해결법

상황: Turbo Frame 탭 UI에서 빈 페이지 Rails 8에서 인터뷰 빌더를 만들고 있었다. 하나의 페이지 안에 “내 인터뷰 작성”, “전체 갤러리”, “인터뷰 결과” 같은 탭을 두고, 탭을 클릭하면 콘텐츠 영역만 바뀌는 SPA 느낌의 UI였다. 구조는 이랬다: <%# interview_app/show.html.erb — 탭 셸 %> <nav> <button onclick="switchTab('wizard')">내 인터뷰</button> <button onclick="switchTab('gallery')">갤러리</button> <button onclick="switchTab('result')">결과</button> </nav> <turbo-frame id="interview-rails-content" src="/interviews/wizard" loading="lazy"> <p>로딩 중...</p> </turbo-frame> 탭을 클릭하면 JavaScript로 railsFrame.src를 바꿔서 콘텐츠를 교체한다. wizard, gallery, show 세 뷰 모두 같은 turbo_frame_tag "interview-rails-content"로 감싸져 있어서 frame ID가 매칭되고, 콘텐츠가 자연스럽게 스왑된다. ...

2026-03-25 · 8분 소요 · Seunghan

Rails 커뮤니티 게시판에 마크다운 에디터 + 좋아요 시스템 붙이기 — importmap 환경에서의 현실적인 선택

커뮤니티 게시판에 Q&A 기능을 만들고 있었다. 질문을 올릴 때 코드 블록이나 볼드 처리를 할 수 있어야 했고, 추천순 정렬 필터도 있으니 좋아요 기능도 필요했다. 그런데 이 프로젝트는 importmap-rails 기반이라 npm 패키지를 자유롭게 쓸 수 없는 환경이었다. 결론부터 말하면, 외부 라이브러리 없이 기존 Stimulus 컨트롤러를 재사용하는 게 가장 좋은 선택이었다. 그 과정에서 겪은 삽질들을 정리한다. importmap-rails 환경에서 마크다운 에디터 선택지 Rails 8의 기본 JS 관리 방식은 importmap이다. esbuild나 webpack 같은 번들러 없이, ESM(ES Modules)을 CDN에서 직접 가져다 쓴다. 설정이 단순한 대신 CommonJS 전용 패키지나 CSS를 함께 번들링해야 하는 라이브러리는 쓰기 어렵다. ...

2026-03-24 · 9분 소요 · Seunghan

모바일 앱 로그인이 자꾸 풀리는 진짜 이유 — JWT Refresh Token 아키텍처 완전 정리

모바일 앱을 운영하다 보면 가장 많이 받는 불만 중 하나가 “로그인이 자꾸 풀려요"다. 카카오톡이나 인스타그램은 한 번 로그인하면 직접 로그아웃하기 전까지 영원히 유지되는데, 내 앱은 왜 하루만 지나면 다시 로그인하라고 하는 걸까? 이 글은 Flutter 앱 + Rails 8 API 환경에서 로그인 세션이 반복적으로 풀리는 문제를 추적하고 해결한 과정을 기록한다. 단순히 “TTL을 늘려라"가 아니라, JWT 인증 시스템의 구조적 문제를 하나씩 찾아가는 과정이다. 증상: 1시간마다 로그인이 풀림 앱을 백그라운드에 두었다가 1시간 후에 열면 로그인 화면으로 돌아간다. 사용 중에는 문제가 없는데, 잠깐 앱을 닫았다 열면 세션이 사라진다. ...

2026-03-23 · 7분 소요 · Seunghan

Hotwire Native iOS에서 삭제 버튼이 안 눌리는 이유 — WKUIDelegate와 turbo_confirm의 함정

삭제 버튼을 눌렀는데 아무 일도 일어나지 않는다 Rails + Hotwire로 웹앱을 만들고, Hotwire Native(구 Turbo Native)로 iOS 앱을 감싸서 배포하는 구조를 쓰고 있었다. 웹에서는 모든 것이 잘 동작했다. 삭제 버튼을 누르면 “정말 삭제하시겠습니까?” 확인 다이얼로그가 뜨고, 확인을 누르면 삭제가 진행됐다. 그런데 iOS 네이티브 앱에서 같은 버튼을 누르면 아무 반응이 없었다. 에러도 없고, 크래시도 없고, 그냥 조용히 무시됐다. 상태 변경 버튼, 라운드 추가/삭제 버튼, 토너먼트 삭제 버튼 — turbo_confirm이 붙은 모든 버튼이 죽어있었다. ...

2026-03-22 · 9분 소요 · Seunghan

iOS WebView 채팅 메시지가 박스를 뚫고 나간다 — overflow-wrap: anywhere로 해결한 크로스프로젝트 수정기

증상: 긴 메시지가 채팅 버블을 뚫고 나간다 Rails 8 + Hotwire Native로 만든 iOS 앱에서 채팅 기능을 테스트하던 중 문제를 발견했다. 긴 URL이나 공백 없는 연속 문자열을 보내면 메시지가 채팅 버블 영역을 벗어나 화면 밖으로 튀어나가는 현상이 발생했다. 웹 브라우저에서는 멀쩡하게 보이는데, iOS 네이티브 앱(WKWebView)에서만 문제가 재현됐다. 가로 스크롤이 생기고, 메시지 영역 전체 레이아웃이 깨져버린다. 처음엔 “Tailwind break-words 넣어놨는데 왜 안 되지?” 싶었지만, 파고 들어가보니 CSS overflow-wrap, flexbox intrinsic sizing, iOS WebKit 호환성이 복합적으로 얽힌 문제였다. ...

2026-03-22 · 9분 소요 · Seunghan

Turbo Native iOS에서 data-turbo-confirm이 동작하지 않는 이유 — WKUIDelegate 누락 문제

버튼을 눌러도 아무 일도 일어나지 않는다 Rails 8 + Hotwire Native으로 iOS 앱을 만들고 있었다. 웹에서는 잘 동작하는 삭제 버튼이 네이티브 앱에서는 완전히 먹통이었다. <%= button_to "삭제", tournament_path(@tournament), method: :delete, form: { data: { turbo_confirm: "정말 삭제하시겠습니까?" } } %> 웹 브라우저에서 클릭하면 “정말 삭제하시겠습니까?” 확인 다이얼로그가 뜨고, 확인하면 삭제가 진행된다. 그런데 iOS 앱에서는 버튼을 탭해도 아무 반응이 없다. 에러도 없고, 로그도 없고, 그냥 조용히 무시된다. 처음에는 turbo_confirm을 form: 옵션에 넣느냐 data: 옵션에 넣느냐의 문제인 줄 알았다. button_to의 turbo_confirm은 form 태그의 data 속성으로 전달해야 하기 때문이다. 하지만 코드는 정확했다. 웹에서 되는데 네이티브에서만 안 되니까 iOS 쪽 문제가 확실했다. ...

2026-03-22 · 10분 소요 · Seunghan

fetch() + PATCH + 302 Redirect = 보이지 않는 버그

Stimulus 컨트롤러에서 badge 선택 UI를 만들었다. 옵션을 클릭하면 fetch()로 PATCH를 보내고, 서버가 업데이트한 뒤 성공/실패를 표시하는 단순한 구조다. 그런데 DB는 업데이트되는데 UI가 실패 표시를 하면서 원래 값으로 되돌아갔다. 서버 로그를 열기 전까지는 원인을 전혀 짐작할 수 없었다. 증상 badge를 클릭하면: 잠깐 선택 스타일이 바뀜 곧바로 원래 값으로 revert 에러 인디케이터(X) 표시 다른 필드(모드, 대진표 유형)는 정상 동작하는데, 특정 필드만 실패했다. 모델 validation 문제도 아니고, 권한 문제도 아니었다. 서버 로그에서 본 진짜 원인 Started PATCH "/resources/54" for ::1 Processing by ResourcesController#update as TURBO_STREAM Parameters: {"resource"=>{"field_name"=>"new_value"}, "id"=>"54"} ... UPDATE "resources" SET "field_name" = 1 WHERE "id" = 54 COMMIT Redirected to http://localhost:3000/resources/54/dashboard Completed 302 Found in 22ms Started PATCH "/resources/54/dashboard" for ::1 ActionController::RoutingError (No route matches [PATCH] "/resources/54/dashboard"): DB 업데이트는 성공했다. 그런데 서버가 redirect_to dashboard로 302를 보냈고, fetch가 그 redirect를 따라가면서 PATCH method를 유지한 채 dashboard URL로 요청을 보냈다. Dashboard는 GET만 받으므로 RoutingError가 터졌다. ...

2026-03-20 · 5분 소요 · Seunghan

Hotwire Native iOS — 로그인 모달 충돌, Tailwind 4 사이드바, path config 삽질 기록

Hotwire Native iOS 앱에서 하루 동안 세 가지 버그를 잡았다. 각각 원인이 다르지만 공통점이 있다: 겉으로 보이는 증상과 실제 원인이 전혀 다른 곳에 있었다. 1. 로그인 페이지가 홈 탭에서만 보이는 문제 증상 4개 탭(홈, 과제, 알림, 마이)이 있는 앱에서, 비로그인 상태로 앱을 열면 홈 탭에서만 로그인 페이지가 뜨고, 나머지 탭을 누르면 빈 화면이나 에러가 표시된다. Rails 서버는 4개 탭 모두 /login으로 정상 리다이렉트하고 있었다. 원인: path-configuration의 context: "modal" Hotwire Native의 path-configuration에서 /login이 이렇게 설정되어 있었다: ...

2026-03-19 · 5분 소요 · Seunghan

Tailwind v4 테마 적용 삽질기 — CSS 변수가 안 먹는 진짜 이유

Rails + Tailwind CSS v4 프로젝트에서 그랜드슬램 테마 시스템을 만들었다. 설정에서 Wimbledon(보라), Roland Garros(오렌지), US Open(네이비), Australian Open(스카이블루)을 고르면 앱 전체 색상이 바뀌는 기능이다. 이틀을 날렸다. 결론부터 말하면 CSS 파일 로드 순서 한 줄이 문제였다. 배경: Tailwind v4의 CSS 변수 컴파일 방식 Tailwind v4는 v3과 완전히 다른 방식으로 색상을 처리한다. 가장 중요한 변화는 모든 유틸리티 클래스가 CSS 변수를 통해 동작한다는 것이다. /* Tailwind v4가 bg-emerald-600을 컴파일한 결과 */ .bg-emerald-600 { background-color: var(--color-emerald-600); } v3에서는 bg-emerald-600이 background-color: #059669 같은 하드코딩 hex로 컴파일됐다. v4에서는 CSS 변수 참조로 바뀌었다. 이 차이가 테마 시스템의 핵심이다. ...

2026-03-19 · 7분 소요 · Seunghan

Rails 8 + Hotwire Native iOS — 실시간 알림 뱃지 & 사이드 메뉴 네비게이션 구현

Rails 8 기반 Hotwire Native iOS 앱에서 두 가지 문제를 해결한 기록이다. 알림 뱃지 실시간 갱신 — 서버에서 알림이 생성되는 순간 앱 아이콘 뱃지와 내비게이션 벨 버튼을 즉시 업데이트 사이드 메뉴 네비게이션 누락 — tournament ID 같은 동적 파라미터가 필요한 URL을 사이드 메뉴에서 올바르게 이동 1. 문제 배경 알림 뱃지 APNs 푸시 알림의 badge 필드를 설정하지 않으면 iOS 앱 아이콘에 숫자가 표시되지 않는다. 또한 알림을 읽어도 뱃지가 초기화되지 않는 문제가 있었다. ...

2026-03-17 · 7분 소요 · Seunghan
개인정보처리방침 이용약관 면책조항 문의