이 글에는 실증본이 있습니다. 같은 주제를 돌아가는 코드와 실제로 그려진 화면으로 다시 만들었습니다 — 화면 층만 녹는다 — 설치 없는 앱, 그리고 컴파일이 얼려 둔 것. 이 글은 쓰인 그대로 둡니다.
지난 Vol 「설치 없는 앱의 시대」에서 우리는 큰 주장을 했다. 화면을 기기가 아니라 서버가 정의하면, 빌드·심사·설치·업데이트라는 긴 사슬이 통째로 사라진다고. 반응은 정확히 절반으로 갈렸다. "그게 정말 되나"와 "이미 비슷하게 쓰고 있었다." 두 반응 다 같은 곳을 가리켰다 — 그 사슬이 당연하지 않다는 것.
이번 Vol는 그 사슬을 한 고리 더 깊이 파고든다. 사슬에는 여러 고리가 있지만, 모든 고리가 매달려 있는 첫 고리가 하나 있다. 너무 밑바닥에 있어서 평소엔 보이지도 않는 고리. 컴파일. 이 글은 컴파일이 화면에 무슨 짓을 하는지, 우리가 어쩌다 거기에 갇혔는지, 그리고 그것이 필연이 아니었다는 것을 이야기한다. 긴 글이 될 것이다. 사슬의 뿌리를 다루는 일이니까.

컴파일은 번역이 아니다
컴파일을 사전적으로 정의하면 "사람이 읽는 코드를 기계가 실행하는 형태로 번역하는 일"이다. 틀린 말은 아니다. 컴퓨터 수업 첫 시간에 그렇게 배운다. 그런데 이 정의는 컴파일이 화면에 무슨 일을 하는지는 한마디도 말해 주지 않는다.
화면의 관점에서 컴파일을 다시 정의해 보자.
컴파일은 그 순간의 모든 결정을 단 하나의 단단한 덩어리로 얼려 넣는 일이다.
버튼이 화면 어디에 놓일지, 색은 무엇일지, 글자 크기는 얼마일지, 누르면 무슨 일이 일어날지, 그다음 어떤 화면으로 넘어갈지 — 이 수백 개의 결정이, 컴파일되는 그 순간 동결된다. 따로 흩어져 있던 결정들이 한 덩어리로 굳고, 그 덩어리가 기기에 심긴다. 이 동결에는 분명한 이점이 있다. 굳은 것은 빠르고, 예측 가능하고, 안정적이다. 사용자의 기기에서 일관되게 돌아간다.
문제는 그다음이다. 얼린 것은 다시 결정하기 어렵다.
버튼 색 하나를 바꾸고 싶다고 하자. 결정 하나만 바꾸면 되는 일이다. 그런데 그 결정은 이미 다른 수백 개와 함께 한 덩어리로 얼어 있다. 결정 하나를 꺼내 고치려면, 덩어리 전체를 녹이고(소스를 열고), 그 결정을 바꾸고, 다시 통째로 얼려야(다시 컴파일해야) 한다. 그리고 새로 언 덩어리를 다시 기기에 심어야 한다 — 패키징하고, 서명하고, 스토어에 올리고, 심사를 기다리고, 배포하고. 지난 Vol에서 길게 묘사한 그 사슬은, 다른 각도에서 보면 얼음을 녹였다 다시 얼리는 한 사이클의 비용이었다.
여기에 이 글의 첫 매듭이 있다. 우리가 견뎌 온 배포의 고통은 게으름이나 낡은 도구 때문이 아니었다. "화면이라는 결정이 기기 안에 얼어 있다"는 구조에서 필연적으로 흘러나온 것이었다. 사슬을 욕할 게 아니라, 얼음을 봐야 한다.
우리는 어쩌다 얼음에 갇혔나
이 얼음이 처음부터 있었던 건 아니다. 짧게 계보를 짚자. 지금의 답답함이 어느 지층에서 굳었는지 알면, 무엇을 녹여야 하는지도 분명해진다.
1세대 — 패키지 소프트웨어. 프로그램을 CD나 디스켓에 담아 팔던 시절. 화면도 로직도 전부 컴파일되어 사용자의 컴퓨터에 통째로 설치됐다. 고치려면 새 버전을 찍어 우편으로 보내고, 사용자가 다시 깔아야 했다. 업데이트 주기는 분기, 운이 나쁘면 연 단위. 얼음의 원형이 여기서 만들어졌다 — 프로그램의 본체가 기기 안에 얼어 있다. 당시엔 이게 유일한 길이었다. 네트워크가 느렸거나 아예 없었으니까.
2세대 — 웹. 브라우저가 전제를 뒤집었다. 웹페이지는 컴파일되지 않는다. 서버가 "여기 제목 하나, 그 아래 문단 하나, 그 아래 링크 하나"라고 적힌 문서를 내려보내면, 브라우저는 그것을 받은 자리에서 그린다. 화면은 기기에 얼어 있지 않다. 매 방문마다 서버가 정의하는 대로 새로 그려진다. 그래서 고침이 즉각적이었다 — 서버의 문서를 한 줄 바꾸면, 다음 방문자는 바뀐 화면을 본다. 빌드도 설치도 업데이트도 없었다. 사슬의 첫 단절이었다. 우리는 자유로워졌다 — 문서를 다루는 한.
3세대 — 앱스토어. 스마트폰이 등장하자 흐름이 거꾸로 돌아갔다. 카메라, GPS, 가속도 센서, 푸시 알림, 오프라인 동작, 손가락에 착 붙는 60프레임 애니메이션 — 웹이 그때는 주지 못하던 네이티브의 능력을 위해, 우리는 다시 컴파일되어 기기에 설치되는 앱으로 돌아갔다. 화면을 다시 얼린 것이다. 그리고 이번엔 얼음 위에 한 겹이 더 얹혔다. 스토어라는 관문과 심사. 사슬이 1세대보다 길어진 채로 다시 굳었다.
여기서 가장 중요한 한 가지를 봐야 한다. 우리가 앱스토어 모델로 돌아간 이유는 네이티브의 능력 때문이었지, 컴파일과 설치와 심사라는 얼음을 원해서가 아니었다. 얼음은 능력을 얻기 위해 어쩔 수 없이 따라온 비용이었다. 그런데 시간이 흐르며 우리는 그 비용을 능력과 한 묶음으로 여기게 됐다. "네이티브 능력을 원하면 컴파일도, 사슬도 감수해야 한다"고. 이 글이 묻는 것이 바로 그 묶음이다. 능력과 얼음은 정말 분리될 수 없는가?
업계가 이미 얼음을 깨려 한 흔적들
업계가 이 얼음을 가만 견딘 것은 아니다. 깨려는 시도가 계속 있었고, 그 시도들의 성공과 한계를 보면 진짜 해법의 윤곽이 드러난다.
하이브리드 앱(웹뷰). 앱이라는 껍데기 안에 브라우저를 넣고, 화면은 웹으로 그렸다. 화면을 서버에서 바꿀 수 있으니 얼음이 일부 녹았다. 그러나 대가가 컸다. 웹뷰는 네이티브만큼 매끄럽지 않았고, 기기의 깊은 기능에 닿기 어려웠으며, 사용자에게 "웹 같은 앱"이라는 어정쩡한 인상을 줬다. 능력을 일부 포기하고 즉시성을 산 셈이다 — 우리가 애초에 웹을 떠난 바로 그 능력을.
코드 푸시(핫 업데이트). 심사를 거치지 않고 자바스크립트 번들을 기기에 밀어 넣어 앱 로직을 갱신하는 방식. 긴급 수정엔 유용했다. 그러나 본질은 여전히 기기에 코드를 얼리는 것이었다. 다만 조금 더 무른 얼음이었을 뿐. 스토어 정책과 줄다리기를 해야 했고, 큰 변경은 결국 정식 심사로 돌아가야 했다. 얼음을 깬 게 아니라 살짝 무르게 했을 뿐이다.
서버 드리븐 UI(SDUI). 여기서부터 본질에 가까워진다. 대형 서비스들이 조용히 써 온 방식이다. 화면의 구성 — 어떤 카드를, 어떤 순서로, 어떤 데이터로 보일지 — 을 서버가 명세로 내려 주고, 앱은 그 명세를 받아 네이티브 위젯으로 그린다. 피드, 홈 화면처럼 자주 바뀌는 영역에서 위력을 발휘했다. 화면은 액체로 두면서(서버가 정의), 그리는 손은 네이티브로 남겼다(앱이 그림). 세 시도 중 가장 정답에 가깝다.
그런데 SDUI에도 천장이 있었다. 대개 각 회사가 자기 서비스만을 위해 만든 폐쇄적 시스템이었다는 점이다. 그 회사의 그 앱에서만 동작하는 명세, 그 회사 서버하고만 말하는 구조. 범용 표준이 아니었다. 그래서 "우리 피드 화면은 서버가 정의한다"는 됐어도, "아무 서버나, 아무 기기에, 화면과 도구를 정의해 보낸다"는 되지 못했다. 얼음을 자기 마당에서만 녹였을 뿐, 마당 밖으로는 가져가지 못한 것이다.
여기까지가 업계가 도달한 지점이다. 즉시성은 웹이, 능력은 네이티브가, 화면의 동적 정의는 SDUI가 각각 증명했다. 셋이 다 있었다. 다만 그 셋이 하나의 열린 표준 위에서 만난 적이 없었을 뿐이다.
얼음을 들어내되, 능력은 지킨다
서버 주도 UI가 — 이 매거진이 다루는 방식이 — 하는 일이 정확히 그 미완의 합류다. 화면이라는 결정만 녹이고, 그리는 능력은 네이티브로 남긴다. 그리고 그 합류를 한 회사의 마당이 아니라 열린 규약 위에서 한다.
구조를 보자. 서버는 "제목 하나, 그 아래 버튼 하나, 버튼을 누르면 이 도구를 호출"이라는 가벼운 정의를 내려보낸다. 기기에 있는 것은 그 정의를 받아 네이티브 위젯으로 그리는 범용 런타임 하나뿐이다. 화면은 웹처럼 액체로 남고, 그리는 손은 네이티브 그대로다. 카메라도, 센서도, 60프레임 애니메이션도 그대로 쓸 수 있다 — 그리는 쪽이 진짜 네이티브 런타임이니까. 우리가 웹을 떠나며 포기했던 능력을, 이번엔 포기하지 않는다.
이때 기기 안에는 더 이상 "이 앱만의, 컴파일되어 얼어붙은 화면 덩어리"가 없다. 무엇이 와도 그려 줄 수 있는 런타임 하나가 있을 뿐이다. 그래서 화면을 바꾸는 일의 물성이 바뀐다. 예전엔 얼음을 녹였다 다시 얼리는 일이었다 — 소스를 열고, 고치고, 컴파일하고, 심고. 이제는 액체에 손가락을 넣어 젓는 일이다 — 서버의 정의를 한 줄 고치면, 다음 갱신에 화면이 그대로 바뀐다. 같은 "버튼 색 바꾸기"가, 한쪽에선 사흘이 걸리고 한쪽에선 십 초가 걸린다. 둘의 차이는 노력이 아니라 물성이다.
한 장면
추상적으로 들릴 테니 한 장면을 그리자. 서버에 이런 정의가 들어 있다고 하자 — "이 화면엔 환영 문구가 하나, 그 아래 버튼이 하나. 버튼을 누르면 주문 도구를 호출한다." 사용자가 앱을 열면, 기기의 런타임이 이 정의를 받아 그대로 그린다. 문구와 버튼이 뜬다.
이제 당신이 서버에서 그 정의를 고친다. "버튼을 두 개로. 하나는 주문, 하나는 취소. 색은 파란색." 저장한다. 다음 순간, 매장에 있는 모든 기기의 화면에 버튼이 두 개가 된다. 점원들은 아무것도 하지 않았다. 업데이트를 누르지도, 앱을 다시 받지도, 스토어에 가지도 않았다. 당신은 컴파일하지 않았고, 심사를 기다리지 않았고, 배포 버튼을 누르지 않았다. 정의 한 줄을 고쳤을 뿐이다. 십 분짜리 결정이 십 분 만에 모든 화면에 도달한다. 사슬이 통째로 사라진 자리다.
당연히 떠오르는 반론들
여기까지 읽은 기술자라면 머릿속에 여러 반론이 떴을 것이다. 그 반론에 정면으로 답하지 않으면 이 글은 공허한 선언에 그친다. 하나씩 짚자.
"매 화면을 서버에서 받으면 느리지 않나?" 화면 정의는 작다. 무거운 코드 번들이 아니라 "버튼 하나, 텍스트 하나"짜리 가벼운 명세다 — 보통 수 킬로바이트. 게다가 한 번 받은 정의는 캐시된다. 정작 무거운 것, 즉 그리기 엔진은 이미 기기에 네이티브로 있다. 서버가 정하는 것은 무엇을 그릴지이지 그리는 행위 자체가 아니다. 사용자가 체감하는 성능은 네이티브 그대로다.
"그럼 오프라인에선 아무것도 못 하나?" 캐시된 정의는 연결이 끊겨도 동작한다. 마지막으로 받은 화면은 그대로 떠 있고, 정의가 바뀔 때만 서버에 묻는다. "항상 연결돼 있어야 한다"는 오해는, 매 순간 서버를 호출한다는 잘못된 그림에서 온다. 실제로는 변경분만 오간다. 웹이 PWA로 오프라인을 얻은 것과 같은 원리이되, 네이티브 런타임 위에서다.
"새 프레임워크를 또 배우라는 건가?" 아니다. 핵심은 새 도구가 아니라 순서가 바뀐 것이다. 화면을 코드로 짜 기기에 얼리던 자리에, 화면을 정의로 서버에 두는 것. 배워야 할 건 거대한 새 체계가 아니라 "정의를 적는 법" 한 가지다. 그래서 입문이 열 줄로 시작될 수 있다(이 Vol의 코딩 글이 그 열 줄을 연다).
"보안은? 서버가 화면과 동작을 내려 준다면, 악의적 정의를 보낼 수도 있지 않나?" 그래서 런타임은 받은 정의를 신뢰하지 않고 검증한다. 정의가 할 수 있는 일은 런타임이 정한 경계 안에 묶인다 — 임의 코드 실행이 아니라, 미리 정의된 위젯과 도구의 조합만 가능하다. 브라우저가 임의 사이트의 자바스크립트를 샌드박스 안에서만 실행하는 것과 같은 발상이다. (이 경계를 어떻게 긋는지는 다음 Vol 「모든 서버가 앱이다」에서 정면으로 다룬다.)
"앱스토어 정책 위반 아닌가?" 미묘한 영역이고, 정직하게 말해 경우에 따라 다르다. 스토어들은 "심사를 우회해 앱의 핵심 동작을 임의 네이티브 코드로 통째로 바꾸는" 것을 경계해 왔다. 그러나 "서버가 콘텐츠와 화면 구성을 정의하는" 것은 모든 피드, 모든 커머스 앱이 이미 하는 일이다. 경계는 임의 코드를 심느냐에 있지 화면을 서버가 정의하느냐에 있지 않다. 이 모델이 후자에 머무는 한, 기존 SDUI가 그랬듯 정책 안에 있다. (다만 이건 해석이며 법률 자문이 아니다. 실제 적용 시엔 각 스토어 최신 가이드라인을 확인해야 한다.)
그래서 무엇이 컴파일에 남나
정직하게 짚자. 컴파일이 사라지는 게 아니다. 자리를 옮긴다.
실시간 제어, 무거운 계산, 기기 깊은 곳의 드라이버, 보안의 밑바닥 — 이런 것들은 여전히 컴파일되어 기기와 서버에 단단히 있어야 한다. 밀리초를 다투고 한 치도 흔들리면 안 되는 것은 얼린 채로 두는 게 옳다. 녹는 것은 사람이 보고 누르는 화면 층이다. 무엇을 보여줄지, 어떻게 배치할지, 어떤 흐름으로 이어질지 — 자주 바뀌고, 자주 실험되고, 사람마다 달라야 하는 그 층.
생각해 보면 화면은 본래 자주 바뀌어야 하는 것이었다. 가격표가 바뀌고, 메뉴가 바뀌고, 버튼 위치를 실험하고, 사용자마다 다른 것을 보여 주고. 그렇게 자주 바뀌어야 하는 것을, 우리는 가장 바꾸기 어려운 곳(컴파일된 기기 안)에 얼려 두었다. 그게 부자연스러웠던 것이다. 우리는 컴파일을 버린 게 아니다.
컴파일하지 말았어야 할 것을 컴파일에서 꺼냈을 뿐이다.
단단해야 할 것은 더 단단히 얼리고, 흘러야 할 것은 흐르게 두는 것 — 그 분리가 이 전환의 핵심이다.
그래서, 의심하라
여기까지 읽고도 "그래도 정말 그게 될까" 싶다면, 그 의심이 옳다. 글로 된 비전은 얼마든지 그럴듯하게 쓸 수 있다. 역사를 그럴듯하게 꿰고, 반론에 그럴듯하게 답할 수도 있다. 중요한 건 그게 실제로 돌아가느냐다.
그래서 이 매거진은 비전을 길게 늘어놓는 데서 멈추지 않는다. 이번 Vol의 다음 글들에서, 우리는 그 액체 상태의 화면이 가장 단단한 곳 — 칩 위에서 — 어떻게 동작하는지 직접 보인다. 전용 GUI도, 펌웨어 굽기도 없이 평가보드를 만지는 한 엔지니어의 오후를. 그리고 코딩을 모르는 농부가 30년 치 밭의 기록을 어떻게 화면으로 만들었는지를. 얼음을 들어내면, 만질 수 있는 사람이 늘어난다 — 그게 이 Vol 전체의 이야기다.
마지막으로 하나만 남겨 두자. 당신이 마지막으로 무언가를 "다시 컴파일"한 게 언제인가. 화면 한 줄을 고치자고 그 모든 과정을 다시 돌린 게. 그 익숙한 의식이 — 적어도 화면이라는 층에서는 — 사라지기 시작하고 있다.
makemind.dev 「탐구」 — 「컴파일을 버리다」 Vol의 여는 글입니다. 화면이 컴파일에서 풀려나면 무엇이 달라지는지를, 칩과 밭에서 이어 봅니다.