인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
충분하지 않습니다. 저는 실제 테이블 뷰를 사용한 JSON & XML 뷰어를 만들었습니다.
Aleksandre M · 2026-05-24 · via DEV Community

나 시도해 본 모든 JSON 뷰어는 같은 일을 합니다: 예쁘게 인쇄합니다. 들여쓰기를 추가하고, 키를 색칠하고, 괄호를 접습니다. 10줄의 설정에는 유용하지만, 개발자들이 매일 다루는 실제 데이터 형태에는 유용하지 않습니다. 이는 객체의 배열입니다.

예쁘게 인쇄된 50줄의 API 응답은 여전히 500줄로 위에서 아래로 스크롤해야 합니다. "만 나만 관리자만 보여줘"라고 할 수 없습니다. 모든 기록을 다시 읽지 않고. 날짜로 정렬할 수 없습니다. 행 전체에서 검색할 수 없습니다.

그것은 뷰어 문제가 아니라 — 잘못된 형식입니다. 객체 배열은 테이블로 되고 싶어요:

┌────┬───────┬───────┬────────┐
│ ID │ NAME  │ ROLE  │ ACTIVE │
├────┼───────┼───────┼────────┤
│ 1  │ Alice │ admin │ ● Yes  │
│ 2  │ Bob   │ user  │ ● Yes  │
│ 3  │ Carol │ user  │ ● No   │
│ 4  │ Dave  │ admin │ ● Yes  │
└────┴───────┴───────┴────────┘

전체 화면 모드 입력 전체 화면 모드 종료

정렬 가능한 열. 검색 가능한 행. 어떤 행을 클릭하면 전체 중첩된 세부 정보가 확장됩니다. 같은 데이터 — 그러나 당신은스캔 대신에 리드 하세요.

그래서 저는 prettyjsonxml.com를 만들었습니다 — JSON과 XML 뷰어로 배열을 실제 테이블로 변환합니다 (그리고 접힐 수 있는 트리 뷰, 형식, 최소화, base64 이미지 미리보기 포함). 하나의 HTML 파일. 백엔드 없음. 빌드 단계 없음. 브라우저 내에서 완전히 실행됩니다; 당신의 데이터는 결코 당신의 기계를 벗어나지 않습니다.

내가 예상하지 못했던 점: 9 MB API 응답에서 테이블 뷰가 실제로 빠르게 동작하게 만드는 것이 생각보다 훨씬 어려운 문제였습니다. 여기는 편집되지 않은 이야기입니다.

순수한 버전: 작동한다가 그렇지 않다가

V1은 간단했습니다.

const data = JSON.parse(text);
renderTable(data);

function renderTable(items) {
  const tbody = document.querySelector('tbody');
  items.forEach(row => {
    const tr = document.createElement('tr');
    // ... build cells
    tbody.appendChild(tr);
  });
}

전체 화면 모드로 입력 전체 화면 모드 종료

내가 테스트한 5행 예제에는 아름답게 보였습니다. 그런데 실제 API 응답을 붙여넣었습니다.브라우저가 1.5초 동안 정지했습니다.

정지는 두 가지 원인이 있었습니다.

  1. JSON.parse9 MB 블록으로 주 메인 스레드를 ~500 ms 동안 차단했습니다.
  2. 30,000 × 2 행(주 + 상세) = 60,000 개의 DOM 노드 생성으로 다른 ~1500 ms 동안 차단했습니다.

두 가지 중 어느 것도 빠르게 만들 수는 없지만, UI를 정지시키지 않도록 만들 수 있습니다..

단계 1:content-visibility: auto — 단일 줄 승리

현대 브라우저는 화면 밖의 콘텐츠에 대한 레이아웃을 건너뛸 수 있도록 허용하면 건너뛴다. 하나의 CSS 규칙:

.data-table tr.row-main {
  content-visibility: auto;
  contain-intrinsic-size: auto 40px;
}

전체 화면 모드 전체 화면 모드 종료

content-visibility: auto는 브라우저에게 말한다: "이 요소에 대한 레이아웃을 계산하지 마라. 아직 스크롤되기 전까지." contain-intrinsic-size는 이것에 대체 높이를 부여하여 스크롤 바가 여전히 전체 문서를 나타냅니다.

총 렌더링 시간은 변경되지 않았습니다 — 작업은 여전히 발생하지만 — 하지만 인식 성능이 브라우저가 보이는 부분을 먼저 그릴 수 있게 되어 향상되었습니다. 매우 활용되지 않았습니다. 현대 브라우저의 약 95%에서 작동합니다.

단계 2: Web Workers를 사용한 JSON.parse — 반대로 생각하는 교훈

다음 차단은 JSON.parse 자체였습니다. 전통적인 지혜: 비싼 파싱을 메인 스레드에서 웹 워커와 함께 이동시켜라.

const worker = new Worker(URL.createObjectURL(new Blob([`
  self.onmessage = (e) => {
    const parsed = JSON.parse(e.data);
    self.postMessage(parsed);
  };
`], { type: 'application/javascript' })));

worker.postMessage(largeJsonString);
worker.onmessage = (e) => render(e.data);

전체 화면 모드를 입력하세요. 전체 화면 모드를 종료하세요.

완료. 메인 스레드는 반응적입니다. 맞습니까?

실제로 느릴 것 같았습니다.

그 이유는 다음과 같습니다: 워커가 파싱된 객체를 다시 보낼 때postMessage는 메인 스레드가 전체 객체 그래프를 구조화된 복제하여 받아야 합니다. 30,000개 객체의 배열에 대해 그 복제는 300-500밀리초입니다 — 메인 스레드에서도합니다.

그래서 저는 성공적으로 500밀리초의 JSON.parse을 메인 스레드에서 이동시키고, 400밀리초의 구조화된 복제를 추가했습니다. 순수 이득: 약 100밀리초. 그리고 사용자는 정지를 느낍니다.이후에서, 즉시 결과를 기대하며 버튼을 클릭한 후에 처리됩니다.

Web Workers는 CPU에 제약된 작업을 위해 사용되며, 결과가 반드시 돌아올 필요가 없습니다. 95%의 작업 부하가 파싱된 객체 자체인 경우, 구조화된 복제 비용이 이익을 지배합니다.

Format / Minify를 위한 작업자를 유지했습니다 (출력은 문자열이며, 복제하기가 저렴합니다). 파싱한 다음 렌더링 흐름에 대해서는 거의 순수 이익이 없었습니다. 실제 해결책은 다른 곳에 있었습니다.

단계 3: 가상 스크롤 — 실제 해결책

30,000행 테이블에 대해서는 30,000행을 렌더링하지 않습니다. 사용자가 볼 수 있는 약 50행만 렌더링하고, 스크롤할 때마다 교체합니다.

<table>와 관련된 함정은: 스크롤할 수 없습니다.position: absolute 행 (테이블 레이아웃은 이를 허용하지 않습니다). 대신, spacer 행를 사용하세요:

<table>
  <thead>...</thead>
  <tbody>
    <tr style="height:850px"></tr>   <!-- spacer for rows above viewport -->
    <tr>row 21</tr>
    <tr>row 22</tr>
    ...
    <tr>row 70</tr>
    <tr style="height:1200px"></tr>  <!-- spacer for rows below viewport -->
  </tbody>
</table>

전체 화면 모드로 전환 전체 화면 모드 종료

스크롤 이벤트가 발생할 때마다 표시되는 행을 다시 계산하고 교체합니다:

function onScroll() {
  const tbodyRect = tbody.getBoundingClientRect();
  const viewTop = Math.max(0, -tbodyRect.top);
  const viewBottom = viewTop + scrollContainer.clientHeight;

  const startRow = Math.max(0, Math.floor(viewTop / ROW_HEIGHT) - 10);
  const endRow   = Math.min(items.length, Math.ceil(viewBottom / ROW_HEIGHT) + 10);

  // Remove old visible rows, build new ones from items[startRow..endRow]
  // Adjust spacer heights so scrollbar position stays correct
}

전체 화면 모드로 전환 전체 화면 모드 종료

3만 행의 JSON 배열에 대해, 이는 "완전히 무응답한 탭"에서 "부드러운 60 fps 스크롤"로 변경됩니다. 더 제한적인 크기에서도 — 예를 들어 몇백 행일 때 — 이득은 검색과 정렬이 즉시되는 것입니다. 그 이유는 이제 JavaScript 배열에서 작동하기 때문이지, 수천 개의 DOM 노드를 걷는 것이 아니기 때문입니다.

20분을 낭비한 버그는: 내 가상 스크롤러는window.scroll입니다. 하지만 제 페이지에는 body { overflow: hidden }<main> { overflow: auto }이 있었기 때문에 window.scroll 는 결코 되지 않았습니다. 실제 스크롤 이벤트는 <main>에서 왔습니다.

// Walk up to find the nearest ancestor that actually scrolls
function findScrollContainer(el) {
  let p = el.parentElement;
  while (p && p !== document.body) {
    const oy = getComputedStyle(p).overflowY;
    if ((oy === 'auto' || oy === 'scroll') && p.scrollHeight > p.clientHeight) return p;
    p = p.parentElement;
  }
  return window;
}

전체 화면 모드로 전환 전체 화면 모드 종료

항상 런타임에 스크롤 컨테이너를 해결하세요. window라고 가정하지 마세요.

textarea 함정

예상치 못한 추가적인 동결: 9 MB 문자열을 <textarea>.value에 할당하면 자체적으로 메인 스레드가 ~300–500 ms 동안 차단됩니다. 브라우저는 텍스트 콘텐츠의 대부분이 폴드 아래에 있더라도 레이아웃을 계산해야 합니다.

수정 방법: 파일 > 5 MB 이상인 경우, textarea를 비우고 스타일된 "로드된 상태" 패널을 보여주세요.

if (file.size > 5 * 1024 * 1024) {
  editor.value = '';
  showLoadedOverlay(file.name, file.size);
} else {
  editor.value = text;
}

전체 화면 모드를 입력하세요 전체 화면 모드 종료

데이터는 여전히 시청자로 파싱되고 렌더링되지만—편집 가능한 textarea에 없습니다. 9MB 파일은 아무도 손으로 편집하고 싶어하지 않습니다.

나는 다르게 할 것 같은 일

  • 가상 스크롤을 시작합니다. 3단계로 추가하지 마세요. 그것만이 확장될 수 있습니다. 나머지는 정교화입니다.
  • "Worker로 이동시키는" 본능에 의문을 제기합니다. Worker는 필요 없는 역할을 계산하는 데 좋습니다. parse-then-clone 흐름에는 나쁩니다.
  • 적용 가능한 모든 곳에서 content-visibility: auto를 사용하세요. 기본적으로 무료입니다.
  • 실제 생산 데이터로 조기에 테스트하세요. 제 5행 테스트 케이스는 모든 흥미로운 버그를 숨겼습니다.

단일 파일 제안

내가 빌드 단계를 추가할지 말지 스스로 논쟁하고 있었습니다. "그냥 번들하면 됐어, 그냥 모듈로 나누면 됐어, 그냥 TypeScript를 추가하면 됐어…" 그것의 모든 프로토타입은 기술적으로 깔끔했지만 물질적으로는 나빴습니다 — 이제 더 많은 파일을 호스팅해야 했고, 캐시 폭스링을 걱정해야 했으며, 도구 체인을 유지해야 했습니다.

전체 포지셔닝이 "브라우저 100%, 서버 없음"인 도구를 하나의 HTML 파일로 배포하는 것은Save As를 오프라인으로 실행하는 것이 올바른 제품 결정입니다. 프래그마가 순수함보다 중요합니다.

최종: ~225 KB 단일 HTML, 의존성 없음, 빌드 없음, Cloudflare Pages에서 그대로 제공됩니다.


시도하고 싶다면: prettyjsonxml.com — 어떤 JSON 또는 XML도 붙여넣기하고, 정렬 가능한 표 또는 접힐 수 있는 트리로 볼 수 있습니다. 필요했기 때문에 만들었고, 아마도 당신도 필요할 수 있기 때문에 공유합니다.

빅데이터 UI에 대해 사용한 성능 트릭은 무엇인가요? 사람들을 놀라게 한 것들에 항상 관심이 많습니다.