Engineering

How I Built the SSD Atlas, a 3D M.2 Map

The design decisions, bugs and honesty rules behind an interactive 3D reference for an M.2 NVMe SSD, from the board down to a single NAND cell.

On this page 14 sections

SSD Atlas is a 3D model of an M.2 NVMe SSD that opens from the board down to a single NAND cell. This is its build log, written at launch in September 2026, covering the design decisions that held up, the bugs that taught me something, and the rule about honesty that every figure in the app follows.

SSD Atlas: the M.2 board, a NAND package opened to its dies and a single charge-trap string, joined by copper arrowsA three-panel schematic. Left: a top view of an M.2 board with gold contacts, a controller, DRAM and two NAND packages. Center: a NAND package opened to a stack of dies joined to the substrate by wire bonds. Right: one charge-trap string, a vertical pillar with a row of memory cells. Copper arrows lead from panel to panel.ENGINEERING / SSD ATLASOpening an SSD down to one cellBOARD · PACKAGE · CELL01 BOARD02 PACKAGE03 CELLM.2 driveNAND packageCharge-trap string

SSD Atlas has grown since, and as of November 1, 2026 it also takes apart an HBM stack and a DDR5 memory stick, and everything below describes the SSD the way I built it.

What SSD Atlas is

SSD Atlas is an interactive 3D reference for a generic M.2 2280 NVMe SSD. You orbit the board, click a part, and open it up, whether that part is the controller, DRAM, NAND, power management, a passive or the connector. It began on September 5, 2026 with a blank project and one question. What would make a datasheet term finally land somewhere physical?

What SSD Atlas is

You orbit the board, click a part, and open it

A top view of a long, narrow M.2 board with gold contacts at its left end. Labeled parts: power management, controller, DRAM, two NAND packages, passives and the M.2 connector with its key slot. Below the board, a row of four boxes joined by copper arrows reads Board, Package, Die, Cell, the path the app opens.Power managementControllerDRAMNANDNANDConnector, M keyPassivesBoardPackageDieCellPart the app opensConnector contactsPath down
Figure 1 · A generic M.2 SSD in the Atlas: each labeled part opens, down to a single NAND cell. Illustrative values.

There are two halves, the app and its marketing page on this site, built separately and launched separately. The app first ran at ssd.eyesonflash.com, and it now lives at atlas.eyesonflash.com.

The stack

React, TypeScript and Vite make up the app, with the 3D scene built on three.js through React Three Fiber. A small Zustand store holds the current selection and camera requests, CSS handles the layout, and there is no backend.

The stack

The shell loads first, and the 3D viewer loads after it

Three layers from top to bottom. First, the app shell with React and navigation, 122 kB gzipped, where navigation works before the scene finishes. A copper arrow labeled lazy import leads to the 3D viewer, a three.js scene built through React Three Fiber, 347 kB gzipped. A dashed arrow from the viewer leads to a panel for when WebGL fails, where the page falls back to the HTML navigation. Beside it, a panel for the whole build, about 1.7 MB, with only a favicon for meshes and textures.01 SHELLApp shell: React, navigation122 kB gzippedNavigation works before the scene finishesLazy import02 3D VIEWERthree.js scene through React Three Fiber347 kB gzippedAlmost everything on screen is generated in code03 IF WEBGL FAILSThe page falls back tothe HTML navigationWHOLE BUILDabout 1.7 MBMeshes and textures: favicon onlyCode chunkBuild totalLoads in orderFallback
Figure 2 · The app is one small shell and one lazy-loaded viewer chunk, with HTML navigation as the fallback when WebGL fails. Source: Sean Webster’s SSD Atlas launch audit.

Almost everything on screen is generated in code rather than loaded from a model file. The production build has no meshes or textures beyond the favicon, and comes to about 1.7MB in total. The 3D viewer is a lazy-loaded chunk of about 1.2MB (about 347kB gzipped), which loads after the shell at about 122kB gzipped so the navigation works before the scene finishes. If WebGL fails, the page falls back to the HTML navigation.

Before writing any of it, I studied Human Atlas, an interactive body atlas, for how it handles picking an object and showing what you picked, at desktop and phone sizes. That study is credited inside the app.

Depth without getting lost

Explore Inside is the core idea. Select a package and open it, and the parts around it fade while its interior takes over the board. Every depth is a node in one graph, and that graph drives the inspector, the breadcrumbs and the camera path.

Two rules make deep views usable. Wheel zoom never changes the semantic depth, so scrolling can’t skip you past a level. Escape, Back and the breadcrumbs always take you one level up, and Reset returns you to the default view.

Depth without getting lost

Every depth is a node in one graph

A graph that starts at the SSD board node. The controller branch descends through Package, Wire-bond, Architecture, Floorplan and Mechanism. The NAND branch descends through Package, Wire-bond, Die, Plane, Block, Page and Charge-trap string. Copper arrows point down each branch. Dashed arrows beside each branch point up for one level back. A lower-left panel notes that the same graph drives the inspector, the breadcrumbs and the camera path.SSD board01 CONTROLLERPackageWire-bondArchitectureFloorplanMechanism02 NANDPackageWire-bondDiePlaneBlockPageCharge-trap string03 ONE GRAPH DRIVESInspectorBreadcrumbsCamera pathOpen deeperEscape, Back, breadcrumbs: up one level
Figure 3 · The controller path and the NAND path are two branches of the same graph, and Escape, Back and the breadcrumbs always climb one node. Source: Sean Webster’s account in this article.

Take the controller. Its second view carried too many labels and ideas at once, and the fix was more levels, not fewer. It now has five steps, and each does one job:

Level View What it answers
1 Package The outside envelope
2 Wire-bond How the die is connected
3 Architecture The jobs: host, CPU, SRAM and NAND, with the flash translation layer running on the CPU
4 Floorplan The area: mostly SRAM, with a small core complex
5 Mechanism An SRAM macro or the CPU cores up close
SSD Atlas at controller depth 3. With the breadcrumb reading SSD, Controller Package, Wire-bond, Architecture, labeled blocks for CPU, SRAM, PCIe PHY, NVMe DMA, ECC LDPC and NAND channels float above the controller package.

Jobs, area and mechanism are three different questions, and one view can’t answer all of them. NAND has its own path, running from package and wire-bond through die, plane, block and page to a charge-trap string. Its wire-bond step was missing at first, and I only noticed because the controller had one.

Honest geometry

A teaching model is only useful if it doesn’t lie. Every figure carries one of three classifications, verified, representative or conceptual. Verified covers the M.2 2280 outline, 0.50mm pitch and M-key slot, representative covers a NAND package envelope, and conceptual covers a functional map of the controller.

Honest geometry

Every figure says how far to trust it

Three equal panels from most to least trust. Verified, drawn with a solid edge and three filled blocks, holds the M.2 outline, pin pitch and M-key slot. Representative, with a dashed edge and two filled blocks, holds the NAND package envelope, plus the connector mesh and the PCB card, which have copper edges and a down arrow because the audit moved them down from verified. Conceptual, with a dotted edge and one filled block, holds the controller functional map.01 VERIFIEDMost trustM.2 outlinePin pitchM-key slot02 REPRESENTATIVESome trustNAND packageenvelopeConnector meshPCB card03 CONCEPTUALLeast trustControllerfunctional mapVerifiedRepresentativeConceptualMoved by the audit
Figure 4 · Three classifications, with the examples from the app; the pre-launch audit moved the connector mesh and the PCB card down from verified. Source: Sean Webster’s SSD Atlas launch audit.

A pre-launch audit caught two places where I had overclaimed. I had badged the connector as verified while its mesh draws 35 pads per face, and the standard describes 67 or 75 contact positions. Its PCB card also claimed a verified thickness that the evidence didn’t cover. Both were demoted to representative, and the About text now says plainly that the pads are a teaching mesh.

I also pulled a “Sources & assumptions” panel that showed “not yet reviewed” on some views. I decided a badge like that shouldn’t go live until the sources behind it were actually checked.

The bugs that taught me something

Of the six bugs below, only the first two stopped the page from working at all, a blank page and a crash. Behavior bugs in picking, depth and the camera made up most of the rest, plus some repeated text.

The bugs that taught me something

Six bugs, each with the fix that closed it

Six rows, each a symptom box on the left joined by a copper arrow to a fix box on the right. A page that loads and goes blank is fixed by clearing the build cache. A viewer crash on one change is fixed with stable lines built once. A click that picks the PCB or all the NAND packages is fixed by letting each package pick alone. Parts vanishing in deep views are fixed by putting deep views above the PCB. A model that jumps when rotating is fixed by pivoting on the grabbed point. Duplicate how-to-read text is fixed by giving each level its own.SYMPTOMFIXPage loads, then goes blankClear the build cacheViewer crashes on a changeStable lines, built onceClick picks PCB or all NANDEach package picks aloneParts vanish in deep viewsDeep views sit above PCBModel jumps when rotatingPivot on the grabbed pointDuplicate how-to-read textEach level has its ownSymptomFixLed to
Figure 5 · Symptom on the left, fix on the right; only the first two stopped the page from working at all. Source: Sean Webster’s account in this article.

The page loaded and then went blank

My dev server had cached an empty stub of the viewer module, and the lazy import crashed, and clearing the build cache fixed it without touching the viewer.

The whole viewer crashed on one change

I had swapped the NAND wire-bond lines for 3D tubes that rebuilt their geometry every frame, and WebGL didn’t survive it. The wires went back to stable lines, built once.

Clicking one NAND package selected all four

The first click on a NAND package selected all four of them, which is fixed, so each package is now selected on its own.

Parts vanished in deep views

Components dropped out as you went deeper, and interiors could show through the board. Deep views now sit above the PCB, and the board fades consistently around them.

The model jumped when I started to rotate

Orbiting now pivots around the point you grabbed, and clicking a part never reframes the view.

Duplicate explanations

Several levels had reworded copies of the same “how to read this figure” text. Each level now has its own.

The marketing page

The landing page needed its own iteration. Its hero went from a CSS-drawn board to a spec-accurate M.2 2280 drawing, then to a photoreal still, back to a two-NAND drawing, and finally to a real capture of the running 3D app.

The marketing page

The hero went through five versions before the real app

A staircase of five blocks, each taller than the last, for the five hero versions: a CSS-drawn board, an accurate M.2 drawing, a photoreal still, a two-NAND drawing and a real capture of the running app, the last with a copper edge. In the empty upper left, a panel for the component strip shows a row of six small cards, then a row of ten.COMPONENT STRIPAt firstAt the end01CSS-drawnboard02AccurateM.2 drawing03Photorealstill04Two-NANDdrawing05Real captureof the appEarlier versionVersion on the page now
Figure 6 · The hero’s five versions in order, and the component strip growing from six cards to ten. Source: Sean Webster’s account in this article.

Its component strip started with six cards and grew to ten, adding capacitors, resistors, a crystal and inductors. Alignment was the constant fight. A long line of text in one card pushed its image block down and everything under it with it, so every card now has a fixed-height slot for its icon and a reserved height for its copy. Both the NAND and DRAM icons were redrawn, as a stacked BGA and an isometric FBGA package.

I reverted two experiments, a set of landing-page callouts with a jump rail and a teaching cutaway of a NAND cell.

Launching it

Before launch I ran a gated audit of type check, lint, tests, build and a scan of the public output, all of which passed.

Launching it

Five checks passed before launch

A column of five checks: type check, lint, tests, build and a scan of the public output, each with a check mark. A copper bracket gathers them into a verdict panel, ready to launch with minor issues. Below it, a panel of the minor issues: no deep links, a single board and an illustrative connector mesh. Under both, a line says the tests cover the anatomy graph, the connector layout and the accuracy classifications.Type checkLintTestsBuildPublic output scanVERDICTReady to launchwith minor issuesMINOR ISSUESNo deep linksA single boardIllustrative connector meshTests cover the anatomy graph, the connector layout and the accuracy classesCheck passedOpen issueFeeds the verdict
Figure 7 · All five checks passed; the verdict was ready to launch with minor issues. Source: Sean Webster’s SSD Atlas launch audit.

There were 138 tests at that point, covering the anatomy graph, the connector layout and the accuracy classifications. It ended with a verdict of “ready to launch with minor issues”, and the minor issues were no deep links, a single board and a connector mesh that is illustrative. I deployed the app to Cloudflare Pages.

What I’d do next

At launch, deep links were the obvious gap. You couldn’t yet share a link to the inside of a specific package, and I wanted to. Since then the Atlas has gained deep links into specific parts.

What I’d do next

Deep links and a real connector closed gaps

A timeline with two points joined by a copper arrow. At launch, in September 2026, three gaps: no deep links, a single board and an illustrative connector mesh. As of November 1, 2026, three additions: deep links into parts, a spec-faithful connector, and HBM and DDR5 specimens. Under the timeline, a dashed panel holds the item still planned at launch: more form factors than one M.2 board.Launch, September 2026As of November 1, 2026OPEN AT LAUNCHNo deep linksA single boardIllustrative connector meshADDED SINCEDeep links into partsSpec-faithful connectorHBM and DDR5 specimensPLANNED AT LAUNCHMore form factors than one M.2 boardOpen at launchAdded sincePlanned at launch
Figure 8 · The gaps at launch, what the Atlas has gained as of November 1, 2026, and the item I planned at launch that is still open. Source: Sean Webster’s SSD Atlas launch audit.

Next on my list in September were a spec-faithful connector and more form factors than one M.2 board. As of November 1, 2026 the connector is done, and the Atlas draws all 67 gold fingers, each at its own pin number. More form factors than one M.2 board stays labeled as planned at launch.

Drawing the real contact layout answers what the audit flagged. A teaching model that shows a 35-pad connector as if it were the real contact layout would be a nicer picture and a worse reference.

Sources

Primary is the original paper, standard or datasheet. Secondary is a review, a vendor summary or a news report. Measured is my own test data.

Show all 1 sources
  • MeasuredThe author’s own measurements