The first commit: from zero to a 3D canvas
How D3Designs started: a friend's loaded question, the stack I already knew, and the shape of a 3D canvas before it was a D&D tool.
A friend asked me if an all-in-one D&D tool existed.
It was a loaded question. Roll20 does maps and dice. D&D Beyond does character sheets. Foundry does VTT. Owlbear does light maps. World Anvil does worldbuilding. Notion does whatever you decide Notion does. Every one of them is good at the part they're good at, and the seams between them are where DMs lose their evenings.
I didn't start D3Designs that night. But I started thinking about it.
What I was actually building
I should be honest about what this was, because the polished version of this story would say I had a grand vision and chose my tools deliberately. I didn't. I wanted to get into the 3D space, and I wanted to build a micro-SaaS. Those were the two real goals. The product itself was still fuzzy: I was building toward something STL-export-shaped, where you'd design a thing in voxels and print it, but I couldn't have described the product in one sentence.
What I was sure about was the stack.
The stack, picked from muscle memory
I picked what I was already comfortable with:
- •Next.js + TypeScript for the app shell. App Router, server components, the usual.
- •Zustand for client state: not because I'd compared it to Redux or Jotai recently, but because I knew it. The whole 3D editor would eventually run off a handful of slices: canvas, layers, voxels, history.
- •tRPC for the API layer, with end-to-end types from the database to the React Query cache.
- •Drizzle + Postgres for the database. I'd used Drizzle on a previous project and liked it enough to bring it back: the schema-as-TypeScript model fits how I think about data.
- •Clerk for auth, outsourced from day one. Webhooks into Postgres for the user record, middleware for protected routes, no opinions about session management.
- •Three.js for the 3D, since the whole point was to learn it. React Three Fiber on top, so I could write scenes as JSX instead of imperatively poking at a renderer loop.
- •Tailwind + shadcn for the UI, because the alternative is having opinions about CSS.
- •Uploadthing for file uploads, because I wasn't going to write multipart upload handling for a project that might not have users.
This isn't research-backed advice. It's what comes out when a developer ten years in needs to start a thing on a Wednesday. The tradeoff with picking what you know is you skip a lot of debate and ship the first feature faster, and the cost is you don't notice when something better has emerged. I'm fine with the tradeoff for a project I might abandon.
The shape that emerged
The first thing that needed to exist was the data model.
A user owns projects. A project owns canvases. A canvas owns layers. A layer owns objects. Objects have geometry: at first, voxel data and uploaded GLB models. That's the whole tree, and it carried for months before I had to bend it. Drizzle schemas, tRPC routers, and Zustand slices all mirrored the same five-level shape, which meant adding a new entity type was a half-day of plumbing rather than a refactor.
The 3D canvas itself was a React Three Fiber scene with a camera, a grid, and a list of objects mapped to mesh primitives. The voxel grid was a fixed-size 3D array, with each cell tracking a color and an "occupied" boolean. Painting a voxel meant updating one cell in the array and triggering a re-render: Zustand handled the store, R3F handled the geometry, and the layers panel listened to both.
Voxel-to-GLTF conversion was the first piece of actual 3D engineering. The naive version creates one cube mesh per voxel, which works until you have a few hundred voxels and the frame rate dies. The version that shipped does mesh merging: adjacent voxels of the same color collapse into a single buffer geometry, and it runs through a small worker so the main thread doesn't stall during export. None of it elegant. Most of it works.
The layers panel was where the UX started to feel like a tool instead of a demo. Object visibility toggles, drag-to-reorder, click-to-select, auto-select on add, and labels distinguishing "voxel object" from "uploaded model": small features individually, but together they're what made the canvas feel inhabited.
What I had at the end of the first stretch
Auth. Projects. Canvases attached to projects. A dashboard. Billing scaffolding nobody was paying for. A 3D scene I could put a cube in. A voxel editor I could paint with. A layers panel that didn't lie about what was on the canvas. A GLTF export that didn't melt the browser.
Not a product. Not yet a business. Just enough infrastructure that if a product showed up, it'd have somewhere to land.
The friend who asked the loaded question hadn't seen any of this yet. The D&D pivot was still ahead. I was just a guy with a working canvas and a vague idea about STL exports.
That turned out to be enough.

Written by Jean P.
Solo builder.
Discuss this post
Join the D3 Designs Discord to share thoughts and follow along.
