Developers
Build a custom storefront
Complete Next.js / React storefront integration consuming 3D state and cart API.
A complete guide for integrating CubeCom Pro 3D canvas rendering and cart handoff in a modern React / Next.js application.
Full React Component Implementation
'use client';
import React, { useState, useEffect } from 'react';
export function ConfiguratorPage({ productId }: { productId: string }) {
const [selections, setSelections] = useState<Record<string, string>>({
frame: 'walnut',
fabric: 'beige',
legs: 'brass',
});
const [resolution, setResolution] = useState<any>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function updateConfiguration() {
setLoading(true);
const res = await fetch('/api/resolve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
productId,
selectionsJson: JSON.stringify(selections),
}),
});
const data = await res.json();
setResolution(data.data.resolveConfiguration);
setLoading(false);
}
updateConfiguration();
}, [productId, selections]);
const handleSelect = (choiceKey: string, valueKey: string) => {
setSelections((prev) => ({ ...prev, [choiceKey]: valueKey }));
};
const handleAddToCart = async () => {
if (!resolution?.valid || !resolution?.commerce?.variantReference) return;
await fetch('/api/cart/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
variantId: resolution.commerce.variantReference,
sku: resolution.commerce.sku,
}),
});
alert('Item added to cart!');
};
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 p-8">
{/* 3D Viewport Column */}
<div className="border rounded-xl bg-slate-900 h-[500px] flex items-center justify-center text-white">
{loading ? <p>Resolving 3D Scene...</p> : <p>3D Canvas ({resolution?.threeD?.activeObjectAssetRevisionIds?.length || 0} active meshes)</p>}
</div>
{/* Option Selector Column */}
<div className="space-y-6">
<h1 className="text-2xl font-bold">Custom Lounge Chair</h1>
{resolution && !resolution.valid && (
<div className="p-4 bg-red-50 text-red-700 border border-red-200 rounded-lg">
{resolution.violations.join(', ')}
</div>
)}
<div>
<h3 className="font-semibold mb-2">Wood Finish</h3>
<div className="flex gap-2">
{['walnut', 'oak'].map((v) => (
<button
key={v}
onClick={() => handleSelect('frame', v)}
className={`px-4 py-2 border rounded ${selections.frame === v ? 'border-indigo-600 bg-indigo-50 font-bold' : ''}`}
>
{v}
</button>
))}
</div>
</div>
<button
onClick={handleAddToCart}
disabled={!resolution?.valid || loading}
className="w-full py-3 bg-indigo-600 text-white font-semibold rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? 'Updating...' : resolution?.valid ? 'Add to Cart' : 'Invalid Combination'}
</button>
</div>
</div>
);
}