Come ho costruito un personalizzatore live e poi l'ho semplificato in un iframe: il caso Ciudad del Láser
Quando ha senso semplificare una funzionalità propria già costruita invece di continuare a mantenerla?
Di Pietro Fiorillo ·
Indice dell'articolo
Il problema: fare preventivi senza banco, per un laboratorio che vive di WhatsApp
Ciudad del Láser non ha un banco di vendita: ogni preventivo passa da WhatsApp o email. Il sito precedente non spiegava come preparare un file di taglio, e il cliente non poteva aggiornare i contenuti senza dipendere da uno sviluppatore.
Cosa ho costruito per primo: un personalizzatore live con validazione reale
Per mate e targhe personalizzate, ho costruito un personalizzatore in React che usava il Canvas del browser per misurare il testo che il cliente digitava, invece di dipendere da una libreria esterna.
let measureCanvas;
function measureTextWidth(text, family, sizePx) {
if (typeof document === 'undefined') return 0;
if (!measureCanvas) measureCanvas = document.createElement('canvas');
const ctx = measureCanvas.getContext('2d');
ctx.font = `${sizePx}px ${family}`;
return (text || '').split('\n')
.reduce((max, line) => Math.max(max, ctx.measureText(line).width), 0);
}src/hooks/useTextMeasurement.js.Con quella misurazione, il personalizzatore confrontava il testo con un'area stampabile fissa e segnalava quando il design usciva dai margini, oltre a rilevare caratteri non sicuri per l'incisione laser.
const { width: textWidth, lineCount } = useTextMeasurement(
d.text, d.font.family, d.fontSize
);
const textHeight = lineCount * d.fontSize * 1.2;
const overflowsPrintableArea =
textWidth > PRINTABLE_BOX.width || textHeight > PRINTABLE_BOX.height;
const unsafeChars = findUnsafeCharacters(d.text);Cosa è in produzione oggi: un iframe verso uno strumento già esistente
A un certo punto, quel personalizzatore proprio è stato sostituito da un <iframe> che incorpora uno strumento PHP esterno già esistente (ember.com.ar/virolas). Il cambiamento è documentato nel commit d1b954f, il cui messaggio dice letteralmente che semplifica il Personalizador eliminando i customizer di mate e targa.
Non inventerò una ragione che non è scritta nel commit. Quello che posso dire è che, in termini di ingegneria, è una decisione ragionevole: uno strumento esterno che già risolveva il problema evita di mantenere un pezzo di software proprio equivalente, in cambio di perdere un controllo fine sull'esperienza.
La regola dei 5 mm di margine e i colori di linea (taglio, marcatura, incisione) continuano a esistere, ma come contenuto di una guida «Come preparare il tuo file», non come validazione eseguita nel browser del cliente.
Come si mantiene aggiornata la sitemap senza dipendere da una build
Il contenuto (servizi, blog) vive su Sanity. Invece di rigenerare la sitemap solo a ogni build, una Cloud Function la costruisce al momento, interrogando Sanity con GROQ ogni volta che qualcuno la richiede.
export const sitemap = onRequest({ cors: false }, async (req, res) => {
const [services, posts] = await Promise.all([
sanityClient.fetch(`*[_type == "service"]{ "slug": slug.current }`),
sanityClient.fetch(`*[_type == "blogPost"]{ "slug": slug.current }`),
]);
const entries = [
...STATIC_URLS.map((s) => urlEntry(s.loc, s.priority)),
...services.map((s) => urlEntry(`/${s.slug}`, '0.9')),
...posts.map((p) => urlEntry(`/blog/${p.slug}`, '0.5')),
];
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${entries.join('\n')}
</urlset>`;
res.set('Content-Type', 'application/xml');
res.set('Cache-Control', 'public, max-age=300, s-maxage=3600');
res.status(200).send(xml);
});functions/index.js.Questo significa che se viene pubblicato un nuovo servizio su Sanity, compare nella sitemap senza aspettare la prossima build del sito, anche se le pagine HTML hanno comunque bisogno di quella build per esistere.
Come il modulo compone il messaggio WhatsApp
function handleSubmit(e) {
e.preventDefault();
const message = [
`Hola! Quiero cotizar: ${serviceName}.`,
`Nombre: ${form.nombre}`,
form.telefono && `Teléfono: ${form.telefono}`,
form.detalle && `Detalle: ${form.detalle}`,
].filter(Boolean).join('\n');
window.open(buildWhatsappLink(message), '_blank', 'noreferrer');
}src/components/QuoteForm.jsx.Come ho validato questa architettura
Ho eseguito il vero processo di build del repository (vite build più il passaggio SSR e lo script di prerender) senza variabili d'ambiente segrete, dato che Sanity viene consumato in modalità pubblica (useCdn: true).
| Metrica | Risultato | Ambiente / evidenza |
|---|---|---|
| Lighthouse Performance (mobile, lab) | 79 / 100 | Lighthouse 13.5.0, throttling simulato |
| LCP (lab) | 5,1 s | Stessa misurazione Lighthouse, non è un dato di campo (CrUX) |
| Tempo di build locale | 8,16 s | vite build + SSR + script di prerender |
| Dimensione dell'export | 10 MB in 171 file | Cartella /dist generata da quella build |
| Rotte prerenderizzate | 22 su 22 | Log dello script di prerender (tutte le landing e il blog) |
| Commit di sviluppo | 26, da luglio a settembre 2026 | git log del repository |
Semplificare codice proprio che già funziona è una decisione valida quanto costruirlo, purché resti documentata.
Il mio ruolo in questo progetto
Il mio ruolo in Ciudad del Láser ha incluso lo sviluppo frontend, l'integrazione di Sanity come CMS, la Cloud Function della sitemap e la SEO tecnica, come parte del mio lavoro in MANYA Digital.
Cosa farei diversamente: codice orfano senza una decisione esplicita
Il personalizzatore originale non è stato cancellato, è stato smesso di usare. Oggi convive nel repository con il resto del codice, senza nessuna nota che dica se è un riferimento per il futuro o semplicemente qualcosa che nessuno ha ripulito. Quell'ambiguità è il vero problema, non il codice in sé.
Cosa proverei dopo
Deciderei esplicitamente il destino del codice orfano del personalizzatore: documentarlo come riferimento per una futura versione propria, o eliminarlo direttamente. Valuterei anche se vale la pena riportare il personalizzatore live ora che la logica di validazione testata esiste già, invece di dipendere da uno strumento esterno fuori dal mio controllo.
Fonti
Domande frequenti
Perché hanno tolto il personalizzatore proprio se era già costruito?
Non lo so con certezza perché non ho partecipato a quella decisione specifica documentata nel commit; quello che mostra lo storico git è che è stato semplificato in un iframe esterno già esistente. È una decisione di costo di manutenzione ragionevole: uno strumento che già funzionava, anche se di un altro fornitore, risolveva lo stesso problema con meno codice proprio da mantenere.
Il codice del personalizzatore in React e Canvas esiste ancora?
Sì, diversi componenti (DesignPreview, TextControls, LogoControls, PreviewToolbar) restano nel repository, ma nessun file li importa. È codice orfano: non rompe nulla, ma non fa nemmeno nulla.
La regola dei 5 mm di margine si applica ancora oggi?
Esiste ancora, ma come guida scritta perché il cliente prepari il proprio file, non come validazione automatica in un personalizzatore live. Il passaggio da un controllo via software a un controllo via istruzioni è reale e vale la pena essere precisi al riguardo.
Come genera il sito la sitemap se il contenuto vive su Sanity?
Una Cloud Function interroga Sanity in tempo reale (servizi e post del blog) ogni volta che viene richiesta la sitemap, e la costruisce combinando quelle rotte con quelle fisse del sito. Non dipende dall'esecuzione di una build perché la sitemap sia aggiornata.
Perché React e Vite invece di Next.js, come in altri progetti?
Perché questo sito non aveva bisogno di generazione ibrida né di rotte server: una SPA con un passo di prerender proprio (uno script che genera HTML per rotta dopo la build) copre il caso d'uso con una base più leggera.
Quanto costa mantenere un sito così?
Il costo di hosting è basso perché il sito finale è statico. Il costo reale sta nel mantenere il contenuto su Sanity (servizi, blog, testimonianze) e nel fatto che lo strumento di personalizzazione esterno continui a funzionare, dato che non è sotto il controllo diretto del progetto.