Independent · 3D Commerce Platform
Reality 3D
Designed and developed a 3D-printing commerce experience with product discovery and customer-facing manufacturing workflows; implemented front-end features and backend/API integrations.

The product objective
An on-demand manufacturing storefront: browse a catalogue of printable products, or upload your own model, inspect it in 3D and get a manufacturing quote — with what can and cannot be made stated plainly to the customer.
- Role
- Independent project — product, design, front-end and integration
- Timeline
- Independent Project
- Deployment
- Deployed on Vercel.
Technology stack
- Next.js 16 (App Router)
- React 19
- TypeScript
- three.js (WebGL)
- Payload CMS 3
- PostgreSQL · Drizzle
- Supabase Auth
- S3-compatible object storage
Designed, implemented, integrated
What I did on each side of the project — kept separate, because they are different kinds of work. AI tools accelerated implementation; understanding the code, reviewing the output, integrating it and validating it stayed with me.
What I designed
- Product discovery: catalogue browse, filter, search and sort, each with empty, loading and error states.
- The custom-print workflow: upload, analyse, configure, quote.
- A capability model customers can read — available, coming soon, unavailable — so nothing unapprovable can be quoted or ordered.
- An operations console for orders, customers, designs, production and issues.
What I implemented
- One WebGL viewer behind an imperative engine class: React owns mounting, teardown and controls; the engine owns everything on the GPU.
- Format loaders for STL, OBJ and glTF/GLB, each imported on demand.
- A deterministic exploded view — pure maths, no randomness — so a model separates identically on every load.
- A quote engine with request validation and pricing rules, plus cart, checkout, guest order tracking and the account area.
What I integrated
- Payload CMS as the catalogue and admin back end, on PostgreSQL with Drizzle migrations.
- Supabase authentication for customer accounts.
- S3-compatible object storage with presigned URLs for uploaded model files.
- Route handlers for quotes, models, designs, orders, checkout and health checks.
Where AI assisted
- Built in documented phases with AI coding assistance (Claude); each phase was reviewed, tested and committed separately.
- A self-run production audit and screen-and-state inventory record what is verified and what is not, rather than assuming generated code works.
Challenges and decisions
- 01
Challenge
Customers upload STEP files, which are a boundary representation, not a mesh.
Approach
The custom-print analysis says so plainly — dimensions and volume cannot be measured from surfaces — while the file can still be quoted and manufactured, instead of failing silently.
- 02
Challenge
An exploded view that rearranges itself between viewings is not a drawing anyone can read.
Approach
Explosion directions are pure and deterministic — radial from the assembly centre, with fixed fallback axes by scene order for centred parts.
- 03
Challenge
The storefront must never sell a material or process the business has not approved.
Approach
Capability status comes from a business decision ledger; the quote engine, cart, checkout and order creation all refuse anything not approved.
How it is verified
- Unit and stress tests on Node's built-in test runner — pricing, catalogue, cart and checkout, orders, security, storage, geometry and the viewer's maths.
- A written MVP production audit and screen-and-state inventory listing what is implemented, reachable and functional.
From the repository
How a component travels in the exploded view — deterministic, so the same model explodes identically every time.
export function explodeDirection(
component: ExplodeComponent,
assembly: AssemblyBounds,
): Vector3 {
const radial = new Vector3().subVectors(component.center, assembly.center);
const threshold = Math.max(assembly.radius * CENTRED_EPSILON, Number.EPSILON);
if (radial.length() > threshold) return radial.normalize();
const axis = FALLBACK_AXES[component.index % FALLBACK_AXES.length];
// The table is non-empty and the index is taken modulo its length.
return axis ? axis.clone() : new Vector3(0, 1, 0);
}Not built yet
- Checkout runs on a mock payment adapter — no live payment provider is integrated yet.
Want to work together?
Let's build the next one together.