그리드를 사다 쓰다가 결국 만들었다
HTML 표에서 TUI Grid, AG Grid를 거쳐 직접 만든 FreeGrid까지 온 과정을 적었다. TUI Grid는 쌓이는 우회 코드 때문에, AG Grid는 사이트마다 백만 원이 넘는 라이선스 비용 때문에 떠났다. 두 번 다 이유는 기능이 아니라 구조였다.
처음엔 그냥 표였다
업무 시스템을 만들다 보면 화면 대부분이 표라는 걸 알게 된다. 주문 목록, 재고 목록, 입출고 내역. 쓰는 사람은 하루 종일 이 표를 들여다보며 찾고, 고치고, 내려받는다. 그래서 표를 어떻게 다루느냐가 생각보다 시스템 전체의 쓰임새를 좌우한다.
시작은 평범한 HTML 표였다. PHP로 쿼리를 돌려서 tr과 td를 찍어내면 끝이었다. 목록 하나 만드는 데는 10분이면 충분했고, 그때는 그걸로 부족함이 없다고 생각했다.
문제는 그 표에 기능이 하나씩 붙기 시작하면서부터였다. 그중 제일 큰 건 두 가지였다. 엑셀 저장을 따로 만들어야 하는 것, 그리고 표 위에서 바로 내용을 수정하게 하는 것이다.
엑셀 저장과 셀 수정은 항상 별도 작업이다
현장에서 목록을 보면 열이면 열 엑셀로 내려받고 싶어 한다. 표는 그냥 HTML이라 엑셀 저장 기능이 없다. 그래서 화면마다 같은 조건으로 쿼리를 다시 돌려서 엑셀 파일을 만드는 코드를 따로 짜야 했다.
화면에 보이는 것과 엑셀로 나오는 것이 같아야 하는데, 두 코드가 따로 있으니 한쪽만 고치면 어긋난다. 화면이 늘어날수록 이 별도 작업도 같이 늘어난다.
목록에서 값을 바로 고치고 싶어지면 상황이 확 복잡해진다. 셀을 클릭하면 입력창으로 바꾸고, 값이 바뀌면 ajax(화면을 새로 고치지 않고 서버와 주고받는 방식)로 PHP에 보내고, 저장 결과에 따라 화면을 되돌리는 코드를 직접 짜야 한다. 엑셀처럼 방향키로 옆 셀로 이동하거나 여러 칸을 복사해 붙여넣는 건 거의 포기에 가까웠다. 현장에서 엑셀을 놓지 못하는 이유가 여기서 보였다.
정렬과 검색조건이 링크를 늘린다
헤더를 눌러서 정렬하게 하려면 order 값을 쿼리스트링으로 넘기고 PHP에서 받아 ORDER BY에 넣어야 한다. 컬럼명을 그대로 SQL에 넣으면 위험하니 허용할 컬럼 목록을 따로 만들어 검사해야 한다. 화면이 열 개면 이 코드도 열 번 반복된다. 정렬 방향 표시 화살표까지 신경 쓰면 더 늘어난다.
검색어를 넣고 조회한 뒤 2페이지로 넘어가면 조건이 날아가는 일이 생긴다. 페이지 링크마다 검색조건, 정렬 값을 전부 쿼리스트링에 실어 보내야 한다.
여기에 정렬까지 눌러야 하니 링크를 만드는 함수가 점점 길어졌다. 조건이 하나 추가될 때마다 모든 링크를 손봐야 했다.
페이징, 그리고 컬럼 하나에 다섯 군데
LIMIT와 OFFSET으로 자르는 것까지는 쉽다. 전체 건수를 세는 COUNT 쿼리를 따로 날려야 하고, 현재 페이지 앞뒤로 번호를 몇 개 보여줄지 계산해야 한다. 마지막 페이지에서 건수가 모자랄 때 처리도 필요하다. 한 번 만들어두면 재사용은 되지만, 화면마다 조건이 조금씩 달라서 완전히 똑같이 쓰는 경우는 드물었다.
컬럼을 하나 추가하려면 SELECT 쿼리, 헤더 출력, 행 출력, 정렬 허용 목록, 엑셀 다운로드 코드를 각각 고쳐야 한다. 하나라도 빠뜨리면 화면과 엑셀의 값이 어긋난다.
HTML 문자열을 PHP 안에서 이어 붙이는 방식이라 출력 값의 이스케이프도 빠뜨리기 쉬웠다.
행이 많아지면 화면이 무거워진다
한 화면에 행이 수천 개 쌓이면 브라우저가 table을 그리는 속도부터 느려진다. 그래서 어쩔 수 없이 페이징으로 자르게 되는데, 그러면 전체를 훑어보기가 불편해진다. 그 밖에 틀 고정, 컬럼 너비 조절, 합계 행 같은 것도 CSS와 자바스크립트로 따로 만들어야 했다.
표를 직접 만들면 자유롭다. 대신 기능이 늘어날수록 같은 코드를 화면마다 다시 짜는 구조가 된다.
이쯤 되자 그리드 라이브러리를 쓰는 게 낫겠다는 판단이 섰다.
TUI Grid를 만났다
엑셀 저장과 셀 수정을 매번 따로 만드는 데 지쳐서 그리드 라이브러리를 쓰기로 했다. 처음 고른 건 TUI Grid였다.
고른 이유는 단순했다. 국내 회사에서 만든 거라 한글 지원이 잘 된다는 점이었다. 영문 문서만 있는 라이브러리를 붙잡고 씨름하는 것보다 마음이 놓였다.
TUI Grid는 정말 좋았다. 다만 쓰다 보면 기능이 하나둘 빠져 있었고, 그때마다 우회하는 코드를 직접 짜야 했다. 그때그때 블로그에 대처법을 올려둔 것만 세 가지다.
우회 1, 선택한 행을 알려주는 함수가 없다
화면에서 셀을 드래그해서 여러 행을 선택해도, 그 행번호를 돌려주는 함수가 따로 없었다. 엑셀 같은 그리드에서 선택한 행에 뭔가를 하는 건 아주 기본적인 동작인데 말이다.
그래서 getSelectionRange로 드래그한 범위를 읽고, 범위가 없으면 getFocusedCell로 현재 셀을 잡아서 rowKey 목록을 돌려주는 getSelectedRows 함수를 직접 만들었다.
우회 2, setPerPage를 바꿨는데 하단 페이징이 그대로다
한 페이지에 보여줄 행 수를 setPerPage로 바꾸면 목록은 바뀌는데, 하단의 페이지 번호는 그대로인 경우가 있었다.
select 박스에서 읽은 값을 그대로 넘기던 게 문제였다. 값을 Number 형으로 바꿔서 넘기자 하단 페이징도 같이 바뀌었다. 원인은 사소했는데 찾는 데는 시간이 걸렸다.
우회 3, 기본 엑셀 저장은 그대로 쓰기 어려웠다
표를 버리고 그리드를 쓴 가장 큰 이유가 엑셀 저장이었다. 그런데 기본 엑셀 저장을 그대로 쓰니 숫자를 숫자 형태로 내보내는 처리가 필요했다. beforeExport 이벤트에서 기본 동작을 멈추고, 파일명과 포맷값 사용 옵션을 직접 지정한 다음, 숫자 컬럼을 숫자 형태로 바꿔서 내보내게 했다. 편하자고 쓴 기능에도 손이 가는 부분이 남아 있었다.
세 가지 모두 하나만 보면 작은 문제였다. 문제는 이런 우회 코드가 사이트마다 쌓인다는 점이었다.
기능을 더 붙이려고 해도 업데이트와 기능 문제가 계속 발목을 잡았다. 고쳐 쓰는 데도 한계가 있다고 판단하고 갈아타기로 했다.
AG Grid로 넘어갔다, 문제는 비용이었다
옮겨간 곳은 AG Grid였다. 정말 편했다. TUI Grid에서 우회 코드를 짜가며 붙잡고 있던 것들이 기본 기능으로 해결됐고, 그렇게 2년을 큰 문제 없이 잘 썼다. 기능이든 성능이든 이 단계에서는 불만이 없었다. 문제는 다른 데서 나왔다.
신규 사이트를 하나 만들 때마다 AG Grid 비용이 따라붙었다. 요금이 도메인과 개발자 단위로 매겨지기 때문이다. 사이트가 늘어나면 도메인이 늘고, 같이 일하는 개발자가 늘어도 비용이 올라간다. 금액도 만만치 않았다. 사이트 한 곳당 백만 원이 넘는 돈을 냈다.
기능은 만족스러운데 서비스를 하나 늘릴 때마다 고정비가 늘어나는 구조였다. 1인 개발자에게는 이게 은근히 크게 느껴졌다. 서비스를 여러 개 만드는 방식으로 일하는 사람에게는 더 그렇다.

그래서 직접 개발했다
기능은 만족스러운데 사이트를 만들 때마다 비용이 드니, 선택지는 둘이었다. 계속 비용을 내거나, 직접 만들거나.
그리드를 만든다는 건 만만한 일이 아니다. 그런데 이미 표와 TUI Grid, AG Grid를 차례로 써보면서 그리드가 어떻게 동작해야 하는지, 어디서 깨지는지를 꽤 알게 됐다. 쓰는 입장에서 쌓인 불만이 곧 요구사항 목록이었다. 그래서 직접 개발했다. 그게 FreeGrid다.
결정적인 계기는 앞으로 오픈해야 할 사이트가 많아진 것이었다. 사이트가 하나 늘 때마다 백만 원이 넘는 비용이 들어가는 구조로는 더 이상 서비스를 늘릴 수 없었다.

막상 만들어보니 쓰는 것과 만드는 것은 전혀 다른 일이었다. 너무 많은 문제가 있었다. 하나를 고치면 다른 데서 새로 터졌다. 그 과정을 하나하나 적으면 글이 따로 몇 편은 나온다. 여기서는 그만큼 쉽지 않았다는 정도만 남겨둔다.
FreeGrid가 하는 일, 그리고 가격
FreeGrid는 10만 행도 끊김 없이 다루는 자바스크립트 그리드다. 가상 스크롤(화면에 보이는 행만 그리는 방식)을 쓰고, 정렬, 필터, 그룹핑, 피벗, 차트, 엑셀 가져오기와 내보내기를 지원한다. 엑셀에서 셀을 복사해 붙여넣는 것도 된다. 14개 언어를 지원하고 순수 JS, React, Vue, Svelte, Angular에서 쓸 수 있다.
국내 환경을 생각해서 Spring Boot와 전자정부 프레임워크용 서버 코드도 같이 제공한다. 페이징, 정렬, 필터, 변경 저장 요청을 SQL로 바꿔주는 자바 클래스 네 개다.
가격을 정할 때 원칙은 하나였다. 무조건 기존 것보다 싸게. 사이트마다 백만 원이 넘게 내면서 느낀 부담을 다른 개발자에게 똑같이 넘기고 싶지 않았다. 그래서 핵심 기능은 누구에게나 무료로 열었다. 그룹핑, 피벗, 차트 같은 고급 기능은 개인과 교육, 비영리 프로젝트에는 무료 키를 주고, 회사는 도메인당 연 200,000원, 한 번 결제로 계속 쓰는 영구 라이선스는 400,000원으로 정했다. 둘 다 부가세를 포함한 값이다.
직접 써보니
만드는 모든 사이트에 FreeGrid를 직접 써봤다. 지금은 15곳 정도에서 쓰고 있다. 실제 서비스에 붙여서 쓰다 보면 만들 때 몰랐던 부분이 계속 나온다. 그때마다 고치면서 성능을 개선해 나갔다.
지금은 안정화된 상태다. 그래도 멈추지는 않았다. 기능 추가와 테스트를 수백 번 반복하며 계속 다듬고 있다.
사이트 한 곳당 백만 원이 넘게 냈으니, 열다섯 곳이면 천오백만 원이 넘는 계산이다. 이제 그 고민 없이 새 사이트를 만들 수 있다.
이 글을 쓰며 든 생각
그리드를 만드는 게 목표였던 건 아니다. 사이트를 하나 더 만들 때 고민 없이 쓸 수 있는 그리드가 필요했을 뿐이다.
TUI Grid에서는 고쳐 쓰다 지쳐서 나왔고, AG Grid에서는 비용 때문에 나왔다. 두 번 다 떠난 이유는 기능이 아니라 구조였다. 도구를 고를 때 지금 되느냐만 보지 말고, 서비스가 늘어났을 때도 같은 조건으로 쓸 수 있느냐를 먼저 봤어야 했다.
용어 쉽게 보기
글에 나온 용어를 개발자가 아닌 분도 읽을 수 있게 풀었다.
| 용어 | 쉬운 설명 |
|---|---|
| 그리드 (Grid) | 엑셀처럼 칸을 고치고 정렬·필터를 할 수 있는 웹 화면의 표. 이 글의 주인공이다. |
| HTML 표 · tr · td | 웹 페이지에 그리는 기본 표. tr 은 행, td 는 칸이다. 보여주기만 하고 기능은 없다. |
| PHP | 서버에서 데이터를 꺼내 웹 페이지를 만들어 주는 프로그래밍 언어. |
| 쿼리 · SQL | 데이터베이스에 "이 조건의 주문을 보여 줘" 하고 묻는 문장. |
| ajax | 화면을 새로 고치지 않고 뒤에서 서버와 값을 주고받는 방식. 셀 하나 고칠 때 쓴다. |
| 쿼리스트링 | 주소 뒤 ?page=2&order=date 처럼 붙는 값. 페이지·정렬·검색조건을 실어 보낸다. |
| ORDER BY | SQL 에서 "이 순서로 정렬해 줘" 라는 부분. |
| 페이징 · LIMIT · OFFSET | 긴 목록을 페이지로 나누는 것. LIMIT 은 몇 개, OFFSET 은 몇 번째부터. |
| COUNT | 조건에 맞는 건수가 모두 몇 개인지 세는 SQL. 페이지 번호를 만들 때 필요하다. |
| 컬럼 | 표의 세로줄 하나. 주문번호, 날짜, 금액 같은 항목이다. |
| 이스케이프 | 사용자가 넣은 글자가 코드로 실행되지 않게 안전한 글자로 바꾸는 처리. |
| 틀 고정 | 엑셀처럼 스크롤해도 머리줄이나 첫 칸이 그대로 보이게 하는 기능. |
| 라이브러리 | 남이 만들어 둔 기능 묶음. 가져다 붙이면 직접 짜지 않아도 된다. |
| TUI Grid | 국내 회사가 만든 무료 그리드 라이브러리. 한글 문서가 잘 되어 있다. |
| rowKey | 그리드가 행마다 붙여 두는 고유 번호. 어떤 행인지 가리킬 때 쓴다. |
| Number 형 | 글자 "50" 이 아니라 숫자 50 으로 다루는 것. 둘을 섞으면 계산·비교가 어긋난다. |
| 이벤트 (beforeExport) | "엑셀로 내보내기 직전" 같은 순간에 끼어들어 원하는 처리를 넣는 자리. |
| AG Grid | 기능이 아주 많은 해외 상용 그리드. 고급 기능은 유료 라이선스가 필요하다. |
| 라이선스 | 소프트웨어를 쓸 수 있는 권리. 도메인·개발자 수에 따라 값을 매기는 곳이 많다. |
| 도메인 | 사이트 주소. 사이트를 새로 열면 보통 도메인도 하나 늘어난다. |
| 가상 스크롤 | 10만 행이 있어도 화면에 보이는 수십 행만 그려서 가볍게 유지하는 방식. |
| 그룹핑 · 피벗 | 같은 값끼리 묶어 합계를 보거나, 엑셀 피벗 테이블처럼 행과 열을 바꿔 집계하는 기능. |
| React · Vue · Svelte · Angular | 웹 화면을 만들 때 많이 쓰는 프레임워크들. 어디서든 붙일 수 있다는 뜻이다. |
| Spring Boot · 전자정부 프레임워크 | 국내 기업·공공기관 시스템에서 많이 쓰는 자바 기반 서버 개발 틀. |
| 영구 라이선스 | 한 번 사면 기간 제한 없이 계속 쓸 수 있는 라이선스. 연 단위 구독과 반대다. |