Frontend 32

프론트엔드 완성 후, 한계를 지도로 바꾸는 방법

CRUD 게시판을 완성했는데도 json-server, 단일 페이지, 인증 없음 같은 한계가 남는다면 무엇을 더 배워야 하는지 막막하다.

근거 · 교안 p256-p262

프론트엔드 완성 후, 한계를 지도로 바꾸는 방법 대표 이미지

1장: 내 프로젝트, 완성인가 미완인가?

솔라는 자신의 노트북 화면을 멍하니 바라보고 있었다. 화면 속에는 솔라가 몇 주 동안 공들여 만든 게시판 프로젝트가 환하게 빛나고 있었다. 글 목록이 깔끔하게 정렬되어 있고, ‘글쓰기’ 버튼을 누르면 익숙한 편집창이 나타났다. 분명 모든 기능이 정상적으로 작동했다. 글을 쓰고, 읽고, 수정하고, 삭제하는, 이른바 CRUD(Create, Read, Update, Delete) 기능이 완벽하게 구현되어 있었다. 며칠 전 마지막 기능을 완성했을 때만 해도 세상을 다 가진 것 같았다.

하지만 그 뿌듯함은 오래가지 않았다. 완성된 프로젝트를 친구에게 보여주려다 문득 깨달았다. ‘아, 내 컴퓨터를 꺼버리면 이 주소는 아무 소용이 없구나.’ 그 순간부터 프로젝트의 빛나던 모든 부분이 하나씩 흐릿한 그림자로 보이기 시작했다.

“다 끝냈는데… 왜 끝난 것 같지가 않지?”

나직한 솔라의 혼잣말에, 방 한쪽에서 조용히 책을 읽던 언니 루나가 고개를 들었다. 루나는 솔라의 화면을 힐끗 보더니, 의자를 끌어 옆으로 다가왔다.

“완성했네. 게시판. 멋진데?”

“멋지긴. 이거 다 가짜야.”

솔라는 맥 빠진 목소리로 대답하며 마우스 커서를 허공에서 빙빙 돌렸다.

“글을 써도 새로고침하면 사라지는 것도 아니고, 목록도 잘 뜨잖아. 뭐가 가짜라는 거야?”

루나의 말에 솔라는 더 답답해졌다.

“기능은 되지. 근데 언니, 이거 봐봐.”

솔라는 주소창의 ‘localhost:3000’을 가리켰다.

“이 주소는 내 컴퓨터에서만 열려. 다른 사람은 절대 못 들어와. 그리고 데이터를 저장하는 서버라는 것도 ‘json-server’라는 걸 쓰는데, 이건 그냥 내 컴퓨터에 임시로 파일을 만들어주는 거래. 진짜 서버가 아니래. 그리고 글을 누를 때마다 페이지 전체가 깜빡이면서 새로고침되는 것도 마음에 안 들고… 무엇보다, 아무나 들어와서 다른 사람 글을 지울 수도 있어.”

솔라는 숨이 찬 듯 말을 쏟아내고는 한숨을 내쉬었다. 어깨가 축 처졌다. 자랑스러웠던 결과물이 순식간에 결함투성이 ‘실패작’처럼 느껴졌다. 무엇을 더 해야 할지, 어디서부터 손을 대야 할지, 눈앞이 캄캄했다.

“결국 난 아무것도 제대로 만든 게 아닌가 봐. 배운 대로 다 했는데, 남은 건 이런 한계들뿐이야.”

루나는 솔라의 말을 잠자코 듣고 있었다. 그리고는 잠시 생각에 잠긴 듯 솔라의 화면을 응시했다. 화면에는 솔라가 방금 쓴 ‘테스트’라는 제목의 글이 덩그러니 놓여 있었다.

“솔라야.”

루나가 조용히 입을 열었다.

“네가 방금 말한 ‘한계’들을 다시 한번 여기에 적어볼래?”

루나는 옆에 있던 작은 메모지와 펜을 솔라 앞에 건넸다. 솔라는 의아한 표정을 지었지만, 순순히 펜을 집어 들었다. 실패 목록을 직접 손으로 적으라니, 썩 내키는 일은 아니었다.

  1. 내 컴퓨터에서만 작동함 (json-server)
  2. 페이지 이동 시 계속 새로고침됨
  3. 아무나 글을 수정/삭제할 수 있음 (인증 기능 없음)

목록을 다 적은 솔라는 허탈하게 펜을 내려놓았다. 자신의 부족함이 세 줄로 요약된 것 같아 기분이 씁쓸했다.

“자, 봐. 이게 내 프로젝트의 한계들이야. 미완성이라는 증거지.”

“그럴까?”

루나는 솔라가 적은 메모지를 가만히 들여다보았다.

“나는 좀 다르게 보이는데. 이건 네가 실패했다는 증거가 아니라, 다음에 어디로 가야 할지 알려주는 표지판 같아.”

“표지판?”

솔라는 이해할 수 없다는 듯 되물었다. 실패와 미완성의 목록이 어떻게 표지판이 될 수 있다는 말인가.

“그래. 봐봐.”

루나는 첫 번째 항목을 손가락으로 가리켰다.

“‘내 컴퓨터에서만 작동한다’는 한계를 발견했지? 그건 ‘어떻게 하면 다른 사람들도 내 프로젝트를 보게 할 수 있을까?’라는 다음 질문으로 가는 문이야. ‘진짜 서버’와 ‘배포’라는 세상으로 들어가는 입구지.”

루나의 손가락이 두 번째 항목으로 옮겨갔다.

“‘페이지가 계속 새로고침된다’는 불편함을 느꼈다는 건, 네가 사용자 경험을 생각하기 시작했다는 뜻이야. 그게 바로 ‘어떻게 하면 더 부드럽고 빠른 화면 전환을 만들 수 있을까?’라는 탐구로 이어져. 클라이언트 사이드 라우팅이라는 개념이 바로 그 질문에 대한 대답 중 하나고.”

마지막으로 루나는 세 번째 항목을 톡톡 두드렸다.

“‘아무나 글을 지울 수 있다’는 문제점을 알았다는 건, ‘사용자’를 구분하고 ‘권한’을 관리해야 한다는 걸 깨달았다는 거잖아. 그게 바로 ‘로그인’과 ‘보안’의 세계로 떠나는 첫걸음이야.”

루나의 설명을 듣는 동안 솔라의 표정이 미묘하게 변하고 있었다. 분명 자신이 적은 실패의 목록인데, 루나의 말을 통해 보니 전혀 다른 의미로 다가왔다. 절망의 나락처럼 보였던 것들이, 새로운 세계로 향하는 디딤돌처럼 보이기 시작했다. 막막함으로 가득 찼던 가슴에 작은 호기심의 불씨가 타오르는 것 같았다.

솔라는 자신이 적은 메모지와 노트북 화면을 번갈아 보았다. 더 이상 화면 속 게시판이 부끄러운 실패작으로 보이지 않았다. 조금 투박하고 부족하지만, 무한한 가능성을 품은 탐험의 시작점처럼 느껴졌다.

“그럼… 이 한계들이 내가 뭘 더 배워야 할지 알려주는 지도 같은 거네?”

솔라의 목소리에 다시 생기가 돌았다. 루나는 가볍게 고개를 끄덕였다.

솔라는 다시 첫 번째 항목, ‘내 컴퓨터에서만 작동함 (json-server)’을 가리켰다. 관점이 바뀌자 새로운 질문이 떠올랐다.

“알겠어, 언니. 이게 지도라는 건 이제 알겠어. 그럼 이 첫 번째 표지판부터 제대로 읽어보고 싶어. 대체 ‘json-server’가 ‘가짜 서버’라는 건 정확히 무슨 뜻이야? 진짜는 어떻길래?”

2장: json-server: ‘가짜’의 진짜 의미

솔라는 자신의 노트북을 테이블 한가운데에 두고, 몇 걸음 떨어져 소파에 앉았다. 손에는 스마트폰을 쥔 채였다. 화면에는 방금 만든 게시판 프로젝트가 여전히 localhost:3000 주소로 환하게 켜져 있었다. 솔라는 스마트폰 웹브라우저 주소창에 localhost:3000을 한 글자씩 힘주어 입력하고 엔터를 눌렀다.

로딩 아이콘이 잠시 빙글빙글 돌다가, 이내 차가운 메시지를 띄웠다. ‘서버를 찾을 수 없습니다.’ 솔라는 미간을 찌푸렸다. 와이파이 신호는 가득 차 있었다. 노트북과 스마트폰은 분명 같은 네트워크에 연결되어 있었다. 다시 주소창을 눌러 오타가 없는지 확인하고 새로고침했지만, 결과는 같았다. 노트북 화면 속에서는 완벽하게 작동하는 게시판이, 불과 몇 미터 떨어진 스마트폰에서는 존재하지 않는 유령 사이트일 뿐이었다.

“이상하네… 왜 안 되지? 같은 와이파이인데.”

솔라의 혼잣말에, 맞은편에서 그 모습을 지켜보던 루나가 입을 열었다.

“네가 어제 물었었지. ‘json-server가 가짜 서버라는 게 무슨 뜻이냐’고.”

루나는 솔라의 스마트폰 화면과 노트북 화면을 번갈아 가리켰다.

“지금 네가 직접 그 ‘가짜’의 의미 중 하나를 체험하고 있는 거야.”

솔라는 루나의 말을 바로 이해하지 못했다.

“체험이라니? 그냥 접속이 안 되는 거잖아. 이게 내 프로젝트가 ‘가짜’라서 그렇다는 거야? 그럼 이 게시판은 결국 아무 의미도 없는 거였어?”

목소리에 실망감이 섞여 나왔다. 지도가 아니라 다시 실패의 증거를 마주한 기분이었다.

루나는 고개를 저었다.

“아니, 의미가 없는 게 아니야. 목적이 달랐던 거지. 솔라야, 혹시 옷 가게에서 마네킹을 본 적 있어?”

“마네킹? 당연히 있지. 갑자기 마네킹은 왜?”

“디자이너가 새 옷을 만들었다고 생각해 봐. 이 옷이 사람에게 입혔을 때 어떤 느낌일지, 길이나 품은 적당한지 빨리 확인하고 싶어. 그때마다 매번 실제 모델을 섭외해서 입혀보는 건 너무 번거롭고 시간이 많이 걸리겠지?”

솔라는 고개를 끄덕였다.

“그래서 마네킹에 옷을 입혀보는 거야. 전체적인 형태나 느낌을 빠르게 확인하고, 수정할 부분을 찾기 위해서. 마네킹은 실제 사람처럼 움직이거나 말하지는 못하지만, 옷의 ‘모양’을 보는 데는 아주 훌륭한 도구가 되어주지.”

루나는 솔라의 노트북을 가리켰다.

“네가 쓴 json-server가 바로 그 마네킹이야. 너는 ‘프론트엔드’라는, 사용자 눈에 보이는 옷을 디자인하고 있었어. 이 옷이 데이터와 만났을 때 어떻게 보일지, 버튼을 누르면 어떤 모양으로 바뀔지 빨리 확인해야 했지. json-server는 그럴 때마다 진짜 서버라는 ‘모델’을 섭외하는 대신, ‘데이터는 이런 모양일 거야’라고 약속된 형태만 보여주는 임시 마네킹 역할을 해준 거야.”

그제야 솔라의 얼굴에 아, 하는 표정이 떠올랐다. 마네킹. 그 비유는 놀랍도록 직관적으로 다가왔다.

“그럼… localhost라는 건… 그 마네킹이 서 있는 디자이너의 작업실 같은 거구나?”

“정확해. 그 작업실 안에서는 얼마든지 마네킹에 옷을 입히고 벗기고, 옷매무새를 다듬을 수 있지. 하지만 작업실 밖에서는 마네킹을 볼 수 없어. 애초에 바깥 사람들에게 보여줄 목적이 아니었으니까.”

루나는 솔라의 스마트폰을 가리켰다.

“네 스마트폰은 지금 그 ‘작업실’ 밖에 있는 손님인 셈이야. localhost는 ‘이 컴퓨터 안에서만’이라는 뜻의 특별한 약속 주소거든.”

솔라는 탁, 하고 무릎을 쳤다. 머릿속에 흩어져 있던 조각들이 맞춰지는 기분이었다. json-server는 실패나 속임수가 아니었다. 프론트엔드 개발이라는 특정 작업에 집중할 수 있도록 도와주는 영리한 ‘도구’였던 것이다. 데이터를 저장하고, 복잡한 규칙을 처리하고, 아무나 접속할 수 있게 만드는 ‘진짜 서버’의 역할을 잠시 면제해주고, 오직 눈에 보이는 부분에만 집중하게 해주는 고마운 존재였다.

‘가짜 서버’라는 말의 의미가 이제 완전히 다르게 느껴졌다. 그것은 ‘쓸모없는 서버’가 아니라, ‘개발 편의를 위한 임시 서버’라는 뜻이었다. 내 게시판이 무의미한 게 아니라, 마네킹에 입혀진 아주 멋진 옷이었다는 사실을 깨달았다. 이제 이 옷을 진짜 모델에게 입혀 세상에 선보일 방법을 찾으면 되는 거였다.

솔라는 다시 어제 적었던 메모지를 집어 들었다. 첫 번째 항목, ‘내 컴퓨터에서만 작동함 (json-server)’ 위에 가볍게 줄을 그었다. 실패 목록이 아니라, 해결된 문제이자 다음 단계로 나아가는 이정표였다.

후련한 마음으로 두 번째 항목을 바라보았다.

‘페이지 이동 시 계속 새로고침됨.’

“언니, 이건 알겠어. 내 게시판은 이제 마네킹에서 벗어나 진짜 세상으로 나갈 준비를 해야 하는 거네. 그런데 이 두 번째 표지판은 좀… 잘 와닿지가 않아. 페이지가 새로고침되는 게 뭐 그렇게 큰 문제라고, 이것도 ‘한계’가 되는 거지?”

3장: 한 페이지 안에 모든 것을 담을 수 있을까?

솔라의 노트북 화면은 둘로 나뉘어 있었다. 왼쪽에는 자신이 만든 익숙한 게시판이, 오른쪽에는 평소 자주 이용하는 온라인 서점 사이트가 열려 있었다. 어제 ‘가짜 서버’의 의미를 깨닫고 난 뒤, 솔라의 마음속에는 또 다른 의문이 싹텄다. 바로 메모지에 적어둔 두 번째 ‘한계’ 항목이었다.

‘페이지 이동 시 계속 새로고침됨.’

이게 왜 문제라는 건지, 솔직히 잘 와닿지 않았다. 오히려 인터넷 초창기 시절처럼 페이지가 착착 바뀌는 게 더 깔끔하고 직관적인 것 아닌가? 솔라는 자신의 의문을 직접 확인해 보기로 했다.

먼저 왼쪽, 자신의 게시판에서 ‘새로운 글입니다’라는 제목을 클릭했다. 예상대로 브라우저 탭의 로딩 아이콘이 한 바퀴 돌면서 화면 전체가 하얗게 깜빡였다. 잠시 후, 글의 상세 내용이 담긴 새로운 페이지가 화면 전체를 채웠다. 아주 정상적인 동작이었다.

이번에는 오른쪽, 온라인 서점 사이트로 마우스 커서를 옮겼다. ‘오늘의 추천 도서’ 목록 중 한 권을 클릭했다. 놀라운 일이 벌어졌다. 화면 위쪽의 검색창과 로고, 옆구리의 카테고리 메뉴는 꿈쩍도 하지 않았다. 오직 중앙의 도서 목록 부분만이 스르륵 바뀌며 선택한 책의 상세 정보로 채워졌다. 화면이 깜빡이는 일도, 페이지 전체가 사라졌다 나타나는 일도 없었다. 마치 잘 짜인 연극 무대에서 배경은 그대로 둔 채 중앙의 배우만 교체하는 것 같았다.

“어…?”

솔라는 자신도 모르게 탄성을 내뱉었다. 몇 번이고 반복해 보았다. 자신의 게시판에서는 글 목록으로 돌아갈 때도, 다른 글을 누를 때도 어김없이 화면 전체가 요란하게 깜빡이며 재시작했다. 반면 온라인 서점은 마치 한 페이지 안에서 마법처럼 내용물만 교체하고 있었다.

“언니 말이 맞았어. 이건 확실히… 다른 경험이네.”

솔라의 혼잣말에 루나가 조용히 다가와 화면을 들여다보았다.

“뭐가 다른 것 같아?”

“내 게시판은 페이지를 옮길 때마다 숨을 한 번 크게 고르고 처음부터 다시 시작하는 느낌이야. 모든 게 사라졌다가 다시 그려지는 거지. 그런데 이 서점 사이트는… 그냥 필요한 부분만 쏙 갈아 끼우는 느낌? 훨씬 부드럽고 빨라 보여.”

솔라는 자신이 느낀 차이를 정확하게 표현하려 애썼다. 막연하게 ‘괜찮다’고 생각했던 자신의 프로젝트가 갑자기 뚝뚝 끊기는 낡은 기계처럼 느껴졌다.

루나는 고개를 끄덕이며 비유를 들었다.

“레스토랑에 갔다고 생각해 봐. 네 게시판은 손님이 애피타이저를 다 먹고 메인 요리를 주문할 때마다, 식당 직원이 와서 테이블, 의자, 식기까지 전부 다 치우고 새로 세팅한 다음 요리를 내주는 곳이야.”

“말도 안 돼. 엄청 번거롭잖아.”

“그렇지? 반면 저 서점 사이트는, 웨이터가 와서 빈 애피타이저 접시만 조용히 치우고 메인 요리 접시를 그 자리에 놓아주는 곳이고. 손님은 자리에 그대로 앉아서 대화를 나누거나 창밖 풍경을 계속 즐길 수 있지.”

비유를 듣자 머릿속이 환해지는 기분이었다. 문제는 ‘새로고침’ 자체가 아니었다. ‘모든 것을’ 새로고침하는 비효율성이 문제였다. 사용자가 계속 보고 있던 헤더나 메뉴까지 전부 다 버렸다가 다시 불러올 필요는 없었던 것이다.

“아! 그럼 내가 ‘한계’라고 적었던 ‘페이지 새로고침’의 진짜 의미는, 불필요한 부분까지 전부 다 새로 불러오는 방식 그 자체였구나. 사용자 경험을 뚝뚝 끊어 먹는.”

“바로 그거야. 사용자는 사실 ‘페이지’를 이동하고 싶은 게 아니라, ‘정보’를 바꾸고 싶을 뿐이거든. 예전 웹사이트들은 서버에 페이지 전체를 요청하고 통째로 받는 방식밖에 없었어. 하지만 지금은 달라졌지.”

루나는 온라인 서점 사이트의 주소창을 가리켰다. 주소는 분명 바뀌어 있었지만, 페이지는 깜빡이지 않았다.

“저렇게 브라우저(클라이언트)가 서버에 페이지 전체를 달라고 요청하는 대신, ‘이 부분에 들어갈 데이터만 줘’라고 요청해서 받아온 정보로 화면을 직접 그리는 기술이 있어. 그걸 ‘클라이언트 사이드 라우팅(Client-Side Routing)’이라고 불러.”

클라이언트 사이드 라우팅. 솔라는 그 단어를 입안에서 조용히 굴려 보았다. 어제 ‘마네킹’과 ‘작업실’로 json-server를 이해했던 것처럼, ‘레스토랑 비유’를 통해 ‘페이지 새로고침’의 한계가 명확하게 보이기 시작했다.

솔라는 다시 메모지를 집어 들었다. ‘페이지 이동 시 계속 새로고침됨’ 항목 옆에 ‘→ 클라이언트 사이드 라우팅’이라고 화살표와 함께 적어 넣었다. 더 이상 실패 목록이 아니었다. 명백한 탐험 과제였다.

이제 메모지에는 마지막 항목만이 덩그러니 남아 있었다.

‘아무나 글을 수정/삭제할 수 있음 (인증 기능 없음)’

첫 번째, 두 번째 한계를 해결하며 자신감이 붙은 솔라는 의기양양하게 마지막 항목을 바라보았다.

“좋아, 이것도 다음 세상을 여는 문이겠지? 그런데 언니, 이건 좀 이상해. 내 컴퓨터에서만 돌아가고, 나 혼자만 쓰는 게시판인데… 굳이 누가 누군지 가리는 기능이 왜 필요한 거지? 이건 진짜 ‘한계’가 아니라 그냥 없는 기능 아닌가?”

4장: 이용자는 누구인가? 누가 무엇을 할 수 있는가?

솔라의 책상 위에는 지난 며칠간의 탐험 기록이 고스란히 놓여 있었다. 작은 메모지에는 세 줄의 ‘한계’ 목록이 적혀 있었다. 그중 두 줄, ‘내 컴퓨터에서만 작동함’과 ‘페이지 이동 시 계속 새로고침됨’ 항목에는 시원하게 가로선이 그어져 있었다. 실패의 증거가 아니라, 다음 세계로 가는 문임을 확인한 흔적이었다.

이제 메모지에는 마지막 항목만이 덩그러니 남아 있었다. 3. 아무나 글을 수정/삭제할 수 있음 (인증 기능 없음). 솔라는 자신만만하게 마지막 항목을 바라보았다. 앞선 두 개의 문을 성공적으로 열어젖힌 참이었다. 이 역시 또 다른 성장의 기회일 것이 분명했다. 하지만 마음 한편에는 어제 가졌던 의문이 여전히 남아 있었다. 내 컴퓨터에서 나 혼자 쓰는 게시판인데, 정말로 이게 ‘한계’가 맞을까? 그냥 아직 만들지 않은 부가 기능일 뿐 아닐까?

루나는 솔라의 시선이 메모지에 닿아있는 것을 보고, 말없이 색이 다른 포스트잇 두 묶음과 펜을 가져왔다. 하나는 노란색, 하나는 분홍색이었다.

“자, 솔라. 여기 가상의 사용자가 두 명 있다고 상상해 보자.”

루나는 노란색 포스트잇을 솔라 앞에 놓았다.

“너는 ‘솔라’라는 사용자야. 게시판에 글을 하나 써봐. 저 노란 종이에.”

솔라는 잠시 머뭇거리다가, ‘솔라의 오늘 일기’라고 적고는 간단한 내용을 덧붙였다. 루나는 그 모습을 보고 미소를 지으며 분홍색 포스트잇에 ‘루나의 독서 기록’이라고 적었다.

“나는 ‘루나’라는 사용자야. 우리 둘 다 이 게시판을 사용하고 있어.”

루나는 솔라와 자신이 쓴 포스트잇을 나란히 벽에 붙였다.

“저 벽이 우리 게시판의 데이터베이스라고 생각하자. 이제 네 노트북 화면을 봐. 네가 쓴 글과 내가 쓴 글이 목록에 나란히 떠 있겠지?”

솔라는 고개를 끄덕였다. 자신의 게시판 목록에는 ‘테스트1’, ‘테스트2’ 같은 무미건조한 제목들만 있었지만, 상상하는 것은 어렵지 않았다.

“좋아. 이제 너, ‘솔라’ 사용자가 방금 쓴 ‘솔라의 오늘 일기’를 수정하고 싶어졌어. 어떻게 할 거지?”

“당연히 내 글 옆에 있는 ‘수정’ 버튼을 누르겠지.”

솔라는 자신의 화면을 가리키며 당연하다는 듯 대답했다.

“알겠어. 그럼 이번엔 내가, ‘루나’ 사용자가 네 글을 보고 마음을 바꿨다고 해보자. ‘솔라의 오늘 일기’가 마음에 안 들어서 내용을 엉뚱하게 바꾸고 싶어졌어. 나는 어떻게 해야 할까?”

솔라는 대답하려다 순간 입을 다물었다. 그리고 자신의 노트북 화면을 뚫어지라 쳐다보았다. 게시글 목록에는 각 항목마다 ‘수정’과 ‘삭제’ 버튼이 나란히 붙어 있었다. ‘솔라의 오늘 일기’ 옆에도, ‘루나의 독서 기록’ 옆에도 똑같은 모양의 버튼이 존재할 터였다.

루나가 분홍색 포스트잇을 떼어 노란색 포스트잇 위에 겹쳐 붙이는 시늉을 했다.

“내가 네 글 옆에 있는 ‘수정’ 버튼을 누르면… 어떻게 될까? 지금 네 게시판은 그걸 막을 방법이 있나?”

“……없어.”

솔라의 입에서 나직한 대답이 흘러나왔다. 머리를 한 대 맞은 듯한 충격이 밀려왔다. 지금까지 자신의 게시판은 ‘나’ 혼자 쓰는 완벽한 공간이라고 생각했다. 하지만 그건 착각이었다. 시스템의 관점에서 보면, 그곳에는 ‘나’도, ‘너’도 없었다. 오직 익명의 누군가가 남긴 ‘데이터 덩어리’와, 그 데이터 덩어리를 주무를 수 있는 ‘버튼’만이 존재할 뿐이었다.

내 글과 남의 글을 구분하는 것은 오직 화면을 보고 있는 사람의 머릿속뿐이었다. 시스템 자체는 어떤 글이 누구의 소유인지 전혀 알지 못했다. ‘수정’과 ‘삭제’ 버튼은 주인을 가리지 않는 무기나 다름없었다.

“말도 안 돼… 그럼 내 게시판은 사실상 무법지대였네.”

“네가 혼자 있는 평화로운 섬이라서 몰랐을 뿐이지.”

루나는 벽에 붙인 포스트잇들을 가리켰다.

“저 데이터들이 ‘누구의 것’인지 기록하고, 버튼을 누른 사람이 ‘누구인지’ 확인하고, 그 사람이 그 버튼을 누를 ‘자격이 있는지’ 검사하는 절차가 없다면, 여러 사람이 함께 쓰는 광장은 순식간에 엉망이 될 거야. 우리는 그 확인 절차를 ‘인증(Authentication)’과 ‘권한(Authorization)’이라고 불러.”

인증과 권한. 솔라는 그 단어들을 되뇌었다. 루나가 덧붙였다. “쉽게 말해, 이 모든 과정이 바로 사용자를 식별하고 접근을 제어하는 시스템의 핵심이야. ‘인증’은 사용자가 누구인지 식별하는 문이고, ‘권한’은 식별된 사용자가 무엇을 할 수 있는지 허락하는 문이지.”

솔라는 이제야 알 것 같았다. ‘인증’은 ‘당신이 정말 솔라가 맞나요?’라고 신분증을 확인하는 과정이고, ‘권한’은 ‘솔라 씨, 당신은 이 글을 수정할 자격이 있나요?’라고 묻는 과정이었다. 이 두 가지가 없다면, ‘내 게시물’이라는 개념 자체가 성립할 수 없었다.

‘없는 기능’이라고 치부했던 문제가, 사실은 여러 사용자가 함께 쓰는 서비스를 만들기 위한 가장 근본적이고 핵심적인 전제 조건이었던 것이다. 혼자 쓰는 장난감과 여러 사람이 함께 쓰는 도구의 결정적인 차이였다.

솔라는 다시 메모지를 집어 들었다. 마지막 항목, ‘아무나 글을 수정/삭제할 수 있음’ 위에 힘주어 가로선을 그었다. 그리고 그 옆에 ‘→ 인증/권한: 사용자를 식별하고 지키는 문’이라고 적었다.

드디어 세 개의 문을 모두 통과했다. 막막했던 한계들은 이제 구체적인 탐험 과제가 되어 지도 위에 선명하게 새겨졌다. 후련함과 함께 진짜 무언가를 배웠다는 뿌듯함이 밀려왔다.

“좋아! 이제 알겠어. 내 게시판이 진짜 세상으로 나가려면, 진짜 서버도 필요하고, 부드러운 화면 전환도 필요하고, 사용자를 지키는 문도 있어야 해. 이 지도만 있으면….”

솔라는 거기까지 말하다가 문득 인터넷에서 스쳐 지나갔던 수많은 기술 용어들을 떠올렸다. 상태 관리, 데이터 캐싱, 디자인 시스템, 배포….

“그런데 언니. 내가 해결한 이 문제들 말고도, 사람들이 말하는 어려운 개념들이 너무 많아. React Query니, Zustand니, Vercel이니 하는 것들 말이야. 이건 또 뭐야? 내가 찾아낸 이 지도 너머에, 또 다른 끝없는 목록이 기다리고 있는 거야?”

성취감으로 반짝이던 솔라의 눈에 다시 막막함의 그림자가 드리웠다. 간신히 지도를 완성했다고 생각했는데, 그 지도 바깥에 또 다른 미지의 대륙이 펼쳐져 있는 기분이었다.

5장: 한계를 로드맵으로: 나만의 프론트엔드 지도 만들기

솔라의 책상 위, 지난 며칠간의 여정이 담긴 메모지가 이제는 자랑스러운 훈장처럼 놓여 있었다. 세 개의 ‘한계’ 항목 위로 그어진 세 개의 가로선. 솔라는 그 메모지를 흐뭇하게 바라보았다. 막막했던 안개 속에서 세 개의 뚜렷한 봉우리를 정복한 기분이었다.

하지만 그 뿌듯함 옆에는, 방금 인터넷 검색을 하다가 급히 메모한 또 다른 종이가 어지럽게 놓여 있었다. 거기에는 Zustand, React Query, Tailwind CSS, Vercel 같은 낯선 단어들이 뒤죽박죽 적혀 있었다. 마치 힘들게 산을 넘었더니 끝없이 펼쳐진 사막을 마주한 기분이었다. 간신히 손에 넣은 지도가 무용지물이 된 것 같았다.

“언니, 이거 봐.”

솔라는 좌절감이 섞인 목소리로 말했다. 성취감으로 반짝이던 눈빛이 다시 흐려져 있었다.

“내가 발견한 한계들은 이제 어떻게 해결할지 알겠어. 그런데 이건 또 뭐야? 사람들이 말하는 어려운 개념들이 끝도 없이 나와. 내가 만든 지도는 그냥 작은 동네 지도였나 봐. 저 너머에 또 다른 미지의 대륙이 이렇게나 많은데… 다시 막막해졌어.”

루나는 솔라의 어지러운 메모와 깔끔하게 정리된 이전 메모를 말없이 번갈아 보았다. 그러고는 솔라의 노트북 화면 속 게시판을 가리켰다.

“그 낯선 이름들은 잠시 잊어버리자. 대신 네 게시판을 더 진짜처럼 만들고 싶다고 생각해 봐. 딱 한 가지 기능을 추가한다면 뭘 넣고 싶어?”

루나의 질문에 솔라는 잠시 고민했다.

“음… ‘좋아요’ 버튼? 게시물마다 ‘좋아요’를 누를 수 있고, 총 ‘좋아요’ 개수도 보이면 진짜 서비스 같을 것 같아.”

“좋은 생각이네.”

루나는 깨끗한 종이 한 장을 솔라 앞에 밀어주었다.

“그럼 네가 상상하는 화면을 여기에 한번 그려볼래? 아주 간단하게, 네모 상자로만 그려도 돼.”

솔라는 펜을 들고 종이 위에 커다란 사각형을 그렸다. 맨 위에는 ‘게시판 헤더’라고 적고, 그 안에 ‘총 좋아요: ?’라고 썼다. 그 아래에는 ‘게시물 목록’ 영역을 그리고, 작은 사각형 여러 개로 게시물들을 표현했다. 각 게시물 사각형 안에는 ‘글 제목’과 함께 ‘♡ 0’ 모양의 ‘좋아요 버튼과 카운트’를 그려 넣었다.

루나는 솔라가 그린 그림을 보고 고개를 끄덕였다.

“좋아. 이제 사용자가 게시물 하나에 있는 하트 버튼(♡)을 눌렀다고 상상해 봐. 화면에서 뭐가 바뀌어야 하지?”

“당연히 내가 누른 그 게시물의 ‘♡ 0’이 ‘♥ 1’로 바뀌어야지.”

솔라는 특정 게시물 상자를 손가락으로 가리키며 말했다.

“그것뿐일까?”

루나의 질문에 솔라는 그림 전체를 다시 훑어보았다. 그리고 아, 하는 표정을 지었다.

“아니. 저 위, 헤더에 있는 ‘총 좋아요’ 개수도 1 늘어나야 해.”

“맞아. 그럼 질문 하나. 그 ‘좋아요 버튼’은 자기가 눌렸다는 사실을 어떻게 저 멀리 떨어져 있는 ‘헤더’에게 알려줄 수 있을까?”

솔라는 말문이 막혔다. 그녀가 지금까지 배운 방식으로는, 부모 컴포넌트가 자식에게 정보를 물려주는 것만 가능했다. 형제 관계이거나, 아예 다른 가지에 있는 컴포넌트끼리 정보를 주고받는 방법은 배운 적이 없었다. 펜을 들고 ‘좋아요 버튼’에서 ‘헤더’로 화살표를 그리려 했지만, 이내 그 선이 얼마나 복잡하고 엉망이 될지 눈에 선했다. 게시물이 수십 개가 되고, ‘댓글 수’나 ‘알림’ 같은 기능까지 추가된다면? 화면은 거대한 거미줄처럼 뒤엉킬 터였다.

“……방법이 없어. 지금 내가 아는 걸로는. 그냥… 엉망이 될 거야.”

솔라는 펜을 내려놓으며 중얼거렸다.

그때 루나가 솔라가 아까 적어둔 어지러운 단어 메모를 가리켰다.

“네가 마주한 그 거미줄 문제가 바로, 저 낯선 단어들이 해결하려는 문제야.”

솔라는 놀라 눈을 동그랗게 떴다.

“저 단어들이?”

“그래. ‘상태(State)’라는 건 앱이 기억해야 하는 모든 정보, 예를 들면 ‘좋아요 개수’ 같은 걸 말해. 네가 그린 그림처럼 여러 곳에서 하나의 정보를 함께 써야 할 때, 그 정보를 아무 데나 두면 거미줄처럼 엉망이 되지. 그래서 ‘상태 관리(State Management)’ 도구, 예를 들어 Zustand 같은 것들은 모든 컴포넌트가 쉽게 꺼내 쓸 수 있는 ‘공용 창고’를 만들어주는 역할을 해.”

순간, 솔라의 머릿속에서 모든 조각이 맞춰졌다. Zustand, React Query 같은 낯선 이름들은 더 이상 정복해야 할 미지의 대륙이 아니었다. 내 프로젝트의 ‘좋아요 버튼’ 문제, ‘데이터 새로고침’ 문제처럼 내가 앞으로 마주할 구체적인 문제들을 해결해 주는 ‘전문 도구’들의 이름이었던 것이다.

  • 한계/문제: 앱의 여러 곳에서 ‘좋아요’ 개수 같은 데이터를 함께 써야 한다. → 해결/지도: 클라이언트 상태 관리 (Zustand, Redux)
  • 한계/문제: 서버에서 데이터를 가져오고, 로딩 상태를 표시하고, 데이터를 최신으로 유지하기 복잡하다. → 해결/지도: 서버 상태 관리 (React Query)
  • 한계/문제: 버튼, 색상, 간격 등 디자인을 일관성 있게 유지하기 어렵다. → 해결/지도: 디자인 시스템 & CSS 프레임워크 (Tailwind CSS, MUI)
  • 한계/문제: 내 컴퓨터의 ‘마네킹’이 아니라, 진짜 세상에 내 프로젝트를 공개하고 싶다. → 해결/지도: 배포 (Vercel, Netlify)

“알겠다…!”

솔라는 외쳤다. 눈앞의 안개가 걷히는 기분이었다.

“이 단어들은 그냥 외워야 할 목록이 아니었어. 내가 만든 첫 번째 지도의 다음 장소들을 가리키는, 더 상세한 이정표였던 거야!”

솔라는 처음 ‘한계’를 적었던 메모지 옆에, 아까 루나가 준 커다란 새 종이를 펼쳤다. 그리고 마침내 자신만의 진짜 ‘로드맵’을 그리기 시작했다. 왼쪽에는 ‘내 프로젝트의 다음 문제’를, 오른쪽에는 그 문제를 해결해 줄 ‘탐험할 도구/주제’를 적었다.

더 이상 막막하지 않았다. 실패의 목록도, 정체 모를 단어의 나열도 아니었다. 자신의 프로젝트에서 출발해, 구체적인 다음 목적지로 향하는, 오직 자신만을 위한 명확한 지도였다.

지도를 완성한 솔라는 뿌듯한 얼굴로 자신이 만든 로드맵을 바라보았다. 모든 것이 연결되어 있었다. 그리고 그 수많은 길 중에서, 자신이 지금 가장 가고 싶은 길이 어디인지 분명하게 보였다. 솔라는 첫 번째 줄을 손가락으로 힘주어 가리켰다.

“언니, 나 정했어. 여기서부터 시작할래. 우선 이 ‘좋아요’ 버튼 문제부터 해결해보고 싶어. 내 게시판에 새로운 심장을 달아주는 거야.”

솔라의 목소리에는 이제 막 탐험을 시작하는 탐험가의 설렘과 확신이 가득했다. 지도는 완성되었고, 첫걸음을 뗄 방향도 정해졌다. 진짜 성장은 바로 그 첫걸음에서 시작될 터였다.