Next.js Integration: Complete Example
A Next.js component for the Wedding Band Builder. Uses next/dynamic to avoid SSR issues since the viewer requires browser APIs.
Component (Client-Only)
jsx
// components/WeddingBandBuilder.jsx
'use client';
import { useEffect, useRef, useState } from 'react';
const SCRIPT_URL = 'https://releases.ijewel3d.com/libs/mini-viewer/latest/bundle.iife.js';
let scriptLoaded = false;
function loadScript() {
if (scriptLoaded) return Promise.resolve();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = SCRIPT_URL;
script.onload = () => { scriptLoaded = true; resolve(); };
script.onerror = reject;
document.head.appendChild(script);
});
}
export default function WeddingBandBuilder({
manifestUrl = 'wedding-band-project.json',
basePath = 'https://your-cdn.com/wbb-assets/',
showUI = true,
theme = null,
onReady = null,
onPriceChange = null,
style = {},
}) {
const containerRef = useRef(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let disposed = false;
function handleReady(e) {
if (disposed) return;
const viewer = e.detail.viewer;
const api = viewer.getPluginByType('WeddingBandBuilder')?.controller;
if (!api) return;
setLoading(false);
if (theme) api.setTheme(theme);
api.events.on('price:updated', (data) => {
if (data.pricing) onPriceChange?.(data.pricing);
});
onReady?.(api);
}
async function init() {
await loadScript();
if (disposed || !containerRef.current) return;
window.addEventListener('ijewel-viewer-ready', handleReady);
new window.ijewelViewer.Viewer(containerRef.current, {
name: 'Wedding Band Builder',
version: 'v5',
basePath,
plugins: {
WeddingBandBuilder: { manifestUrl, showUI },
},
}, {
showCard: false,
showSwitchNode: false,
showUiButtons: showUI,
showConfigurator: false,
showZoomButtons: showUI,
enableZoom: true,
});
}
init();
return () => {
disposed = true;
window.removeEventListener('ijewel-viewer-ready', handleReady);
};
}, [manifestUrl, basePath, showUI]);
return (
<div style={{ position: 'relative', width: '100%', height: '100%', ...style }}>
<div ref={containerRef} style={{ width: '100%', height: '100%' }} />
{loading && (
<div style={{
position: 'absolute', inset: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: '#f5f5f5', color: '#888', fontSize: 14,
}}>
Loading 3D viewer...
</div>
)}
</div>
);
}Page (App Router)
jsx
// app/configurator/page.jsx
'use client';
import { useState } from 'react';
import WeddingBandBuilder from '@/components/WeddingBandBuilder';
export default function ConfiguratorPage() {
const [price, setPrice] = useState(null);
const [api, setApi] = useState(null);
return (
<main>
<h1 style={{ padding: '16px 24px', margin: 0 }}>Design Your Wedding Band</h1>
<div style={{ width: '100%', height: '70vh' }}>
<WeddingBandBuilder
basePath="https://your-cdn.com/wbb-assets/"
theme="modern-minimal"
onReady={setApi}
onPriceChange={(p) => setPrice(p)}
/>
</div>
<div style={{ padding: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 28, fontWeight: 600 }}>
{price ? `$${price.totalUsd.toFixed(2)}` : 'Loading...'}
</span>
<button
onClick={async () => {
if (!api) return;
const config = api.exportConfig();
// POST to your Next.js API route
await fetch('/api/cart', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
}}
style={{
padding: '12px 32px', fontSize: 16, fontWeight: 600,
background: '#333', color: '#fff', border: 'none',
borderRadius: 8, cursor: 'pointer',
}}
>
Add to Cart
</button>
</div>
</main>
);
}Pages Router (Legacy)
If using the Pages Router, use next/dynamic to disable SSR:
jsx
// pages/configurator.jsx
import dynamic from 'next/dynamic';
const WeddingBandBuilder = dynamic(
() => import('@/components/WeddingBandBuilder'),
{ ssr: false, loading: () => <div>Loading...</div> }
);
export default function ConfiguratorPage() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<WeddingBandBuilder showUI={true} />
</div>
);
}API Route
javascript
// app/api/cart/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
const config = await request.json();
// Save to your database, Stripe, or order system
console.log('Ring config:', config);
return NextResponse.json({ success: true });
}