Playbook de migração
Passo a passo de mover uma loja Fresh para TanStack Start.
Este é o caminho manual. Se preferir automatizar, rode deco-migrate primeiro — ele faz os 70% mecânicos. Leia este playbook quando o script sinalizar algo que não auto-corrige, ou quando quiser entender o que ele está fazendo.
Fase 1 — Análise
Antes de mexer em qualquer coisa, faça um inventário:
cd /caminho/para/site-v1
# contagem de sections
find src/sections -name "*.tsx" | wc -l
# contagem de islands (precisam ser eliminadas)
find src/islands -name "*.tsx" 2>/dev/null | wc -l
# rotas customizadas
find routes -type f | wc -l
# loaders / actions
find src/loaders src/actions -name "*.ts" 2>/dev/null | wc -l
# blocks
find .deco/blocks -name "*.json" | wc -l
Anote os números. Eles viram a baseline de verificação pós-migração.
Procure por:
- Imports de
@deco/deco/*e$fresh/*— o script lida. - Imports de
@preact/signalsepreact/*— o script reescreve para React. - Chamadas custom de
useScript(fn)— precisam de revisão manual. - Leituras custom de
Deno.env— precisam de conversão. - Tudo em
src/islands/— toda island precisa de um destino.
Fase 2 — Scaffold
Gere os arquivos baseline da v2:
vite.config.ts(comdecoVitePlugin, plugin Cloudflare, plugin TanStack Start, React, Tailwind v4)wrangler.jsonc(comnodejs_compat+no_handle_cross_request_promise_resolution)src/setup.ts(chamacreateSiteSetup)src/server.ts(server entry do TanStack Start)src/worker-entry.ts(chamacreateDecoWorkerEntry)src/routes/__root.tsx,src/routes/index.tsx,src/routes/$.tsx
O script gera todos. Se for fazer na mão, copie dos templates em @decocms/start/.agents/skills/deco-to-tanstack-migration/templates/ .
Fase 3 — Transformação
Esta é a fase de reescrita em massa:
- Imports —
preact/*→react,@preact/signals→@tanstack/store,$fresh/*→@tanstack/react-start,@deco/deco/*→@decocms/start/*e@decocms/apps/*. - Atributos JSX —
class→className,for→htmlFor. - APIs Fresh —
defineRoute/defineApppara padrões TanStack Router. - Deno-isms —
Deno.env.get(X)para bindings,import.meta.urlpara equivalentes Vite. - Tailwind — v3 → v4 (referências de cor por token, sintaxe de opacidade, reescrita de
@apply).
As 70+ regras moram no script de migração. Veja Referência do script para a lista.
Fase 4 — Limpeza
Remova o que não é mais necessário:
src/islands/— toda island virou outra coisa.routes/(a pasta antiga do Fresh).deno.json,deno.lock.static/— movido parapublic/.- Pastas
compat/que o time tenha criado durante tentativas incrementais.
Fase 5 — Bootstrap
Instale dependências e regenere tudo:
npm install
npm run generate:blocks
npm run generate:schema
npm run generate:sections
npm run generate:loaders
npx tsr generate
Conecte tudo isso como scripts compostos no package.json para que um único npm run generate (ou equivalente) mantenha tudo em sincronia.
Fase 6 — Verificação
Rode typecheck e build:
npm run typecheck # tsc --noEmit
npx vite build # build de prod completo
npx wrangler deploy --dry-run
Typecheck limpo + build limpo significa que a migração mecânica terminou. Correção em runtime é um pass à parte.
Fase 7 — Ajustes manuais
O script registra tudo que precisa de atenção humana em MIGRATION_REPORT.md . Os itens mais comuns:
Islands → "use client" ou estado içado
Padrão: uma island que era dona de estado local (toggle de drawer do carrinho, modal de busca). Em React, marque a section pai como "use client" se a section toda for interativa, ou ice o estado para um provider no nível da section.
// antes (Fresh)
// src/islands/CartDrawer.tsx ← island inteira
// src/sections/Header.tsx importa a island
// depois (React)
// src/sections/Header/Header.tsx
"use client";
import { useState } from "react";
import CartDrawer from "~/components/CartDrawer";
export default function Header({ ... }: Props) {
const [open, setOpen] = useState(false);
return (
<>
<header>...</header>
<CartDrawer open={open} onClose={() => setOpen(false)} />
</>
);
}
Caminhe pelas islands uma a uma, decidindo por island se ela pode subir para a section (server) ou se precisa continuar como client component.
Remoção de useScript(fn)
useScript(fn) era um padrão Fresh que extraía uma função para uma <script> inline. Em v2 não hidrata limpo. Substitua por:
inlineScriptde@decocms/start/sdk/useScriptpara conteúdo estático.- Um componente client de verdade para qualquer coisa que toque estado ou DOM.
Hooks de plataforma ( useCart , useUser , useWishlist )
Se sua loja v1 tinha overrides custom, porte. As implementações default em @decocms/apps/vtex/hooks cobrem os casos comuns. Veja Hooks VTEX.
Scripts de terceiros no <head>
Scripts que mutam o <head> (Google Tag Manager, Adobe Launch, Hotjar) frequentemente causam mismatch de hidratação em React. A correção é injetar via resposta do worker, depois do React renderizar, em vez de em JSX.
Fase 8 — Pass de performance
Depois da correção funcional:
- Tunning do
foldThreshold— geralmente 2-3 sections eager, resto deferred. - Auditoria de profiles de cache — defaults são conservadores; afrouxe para conteúdo estático, aperte para páginas perto do checkout.
- Pode
loaders.gen.ts— use--decofile-dir .deco/blockspara que só apareçam loaders que o CMS realmente referencia. Recomendado para sites novos; sites existentes podem adotar incrementalmente. - Verifique sections deferred em dev — se aparecer “I/O across requests”, configure
no_handle_cross_request_promise_resolutionemwrangler.jsonc.
Fase 9 — QA e deploy
Use o checklist de migração para a verificação.
Veja também
Found an error or want to improve this page?
Edit this page