@yagofontanez/create-seo-site
v0.6.0
Published
CLI para gerar projetos Next.js focados em SEO/GEO local a partir de um strategy.json
Downloads
504
Maintainers
Readme
@yagofontanez/create-seo-site
CLI para gerar rapidamente projetos web (Next.js) para clientes de agência,
focados em SEO/GEO local, a partir de um strategy.json preenchido nos
planejamentos de SEO feitos com o cliente.
O fluxo é:
mkdir cliente-x && cd cliente-x
npx @yagofontanez/create-seo-site init # modo interativo
# preenche o strategy.json com o planejamento
npx @yagofontanez/create-seo-site generate --strategy ./strategy.json --out ./site
cd site && claude # Claude Code lê o PROMPT.md e completa o site- Scaffold determinístico (
init) — pergunta nome, tipo de site, paleta e estilo, copia um template Next.js pronto (design system, componentes de SEO, infra técnica) e aplica a combinação escolhida. - Prompt final (
generate) — valida ostrategy.jsone escreve umPROMPT.mddentro do projeto, juntando o prompt mestre + a estratégia. - Execução — você abre o Claude Code na pasta do projeto e manda ele
executar o
PROMPT.md. O CLI não chama nenhuma API e não precisa de API key.
Instalação
npm install -g @yagofontanez/create-seo-site
# ou, sem instalar:
npx @yagofontanez/create-seo-site init cliente-x
# ou, em desenvolvimento local:
npm install && npm run build && npm linkO pacote instala dois comandos equivalentes: create-seo-site e o atalho
seo-site. Os exemplos abaixo usam o atalho.
Comandos
init [nome-do-projeto]
Copia o template do tipo escolhido para ./<nome-do-projeto>, renomeia os
dotfiles (gitignore → .gitignore, env.example → .env.example) e ajusta o
name do package.json para o nome da pasta.
Rodado em um terminal, o init é interativo: pergunta o nome do projeto
(texto livre), o tipo de site, a paleta (lista navegável, com as cores reais
desenhadas ao lado de cada opção), o design system e se o projeto já sai
dockerizado. As flags pré-selecionam a resposta — Enter aceita.
seo-site init # pergunta os cinco
seo-site init cliente-exemplo # nome já preenchido
seo-site init cliente-exemplo -c yellow -s modern # tudo pré-selecionado
seo-site init cliente-exemplo -c yellow -s modern -y # sem perguntar nada
seo-site init lp-campanha --type landing -y # landing page única
seo-site init cliente-exemplo --docker -y # já com Dockerfile + compose? Paleta de cores do site ↑↓ navega · Enter escolhe
❯ ██████ gold editorial, premium · advocacia, contabilidade, imobiliária
██████ blue confiança institucional · clínicas, TI, engenharia, financeiro
██████ teal clínico limpo · odonto, estética, spa, laboratório
...Fora de um TTY (CI, pipe, npx ... | tee) nada é perguntado: valem as flags e
os padrões, e o nome do projeto passa a ser obrigatório.
| Opção | Padrão | Descrição |
|---|---|---|
| -T, --type <tipo> | completo | Tipo de site (ver tabela abaixo) |
| -t, --template <nome> | do tipo | Template a ser copiado de templates/ — sobrepõe o do tipo |
| -c, --color <paleta> | gold | Paleta de cores (ver tabela abaixo) |
| -s, --style <estilo> | editorial | Estilo visual (ver tabela abaixo) |
| -d, --docker / --no-docker | --no-docker | Gera (ou não) Dockerfile, docker-compose.yml, .dockerignore e os scripts docker:* |
| -y, --yes | false | Aceita os padrões sem abrir o modo interativo |
| -l, --list-designs | — | Lista tipos, paletas e estilos e sai |
| -f, --force | false | Sobrescreve a pasta destino sem perguntar |
Se a pasta destino já existir e não estiver vazia, o CLI pede confirmação
antes de sobrescrever (em ambiente não interativo, cancela — use --force).
Tipo, paleta ou estilo inválido aborta antes de criar qualquer arquivo.
--type — tipo de site
É a primeira escolha depois do nome, porque decide o template — e, por tabela,
qual prompt mestre o generate vai usar.
| Tipo | Template | O que gera |
|---|---|---|
| completo | base-nextjs | Site institucional: páginas de conversão, blog com Supabase, /admin, formulário de lead, Search Console |
| landing | landing-nextjs | Uma landing page só: / + política de privacidade. Sem banco, sem blog, sem painel e sem formulário — o contato sai por WhatsApp e telefone |
Os dois compartilham o mesmo design system (as 36 combinações valem para os
dois), o mesmo src/lib/site.ts como fonte única de NAP e a mesma disciplina de
JSON-LD, metadata, sitemap.xml, robots.txt, llms.txt e ai.txt.
Quando usar landing: campanha de tráfego pago, lançamento, um serviço
único, cliente que não vai manter blog. A página inteira vive em
src/app/page.tsx e o conteúdo em src/content/landing.ts; as seções viram
âncoras (#servicos, #como-funciona, #faq, #onde-estamos) em vez de rotas.
Continua com Pixel da Meta + CAPI e GA4, medindo o clique em WhatsApp, telefone
e e-mail com deduplicação por eventID.
Quando usar completo: o cliente precisa de autoridade tópica, blog,
múltiplas páginas de serviço ou captação de lead com histórico.
Trocar de tipo depois exige recriar a pasta — não é uma flag que se inverte num projeto já gerado.
--docker — projeto dockerizado
Última pergunta do modo interativo, e a única que não muda uma linha do site:
decide só como ele roda. Com --docker o projeto gerado ganha
| Arquivo | Para que serve |
|---|---|
| Dockerfile | Multi-stage com três alvos: dev (next dev com hot reload), builder (next build) e runner (produção, servindo o output standalone) |
| docker-compose.yml | Dois perfis: dev (bind mount do código, porta 3000) e prod (build da imagem + node server.js, restart: unless-stopped) |
| .dockerignore | Mantém node_modules, .next, .git e .env* fora da imagem |
| .env | Criado a partir do .env.example — é o arquivo que o compose lê, tanto no runtime quanto para os build args |
| scripts docker:* | docker:dev, docker:build, docker:up, docker:logs, docker:down |
e o next.config.ts recebe output: "standalone" — sem isso o alvo runner
não tem o que copiar.
seo-site init cliente-exemplo --docker -y
cd cliente-exemplo
npm run docker:dev # http://localhost:3000 com hot reload
npm run docker:up # produção em background (build + next start)
npm run docker:logs
npm run docker:downAs NEXT_PUBLIC_* são embutidas no JS durante o next build, então o compose
as passa como build args (lidos do .env); mudar uma delas exige rebuild — o
que o docker:up já faz. Uma seção ## Docker com esses detalhes é anexada ao
README.md e ao CLAUDE.md do projeto gerado — o segundo para o Claude Code
não tentar rodar o site fora do container nem remover o standalone.
Aplicar o Docker é idempotente: rodar o init --docker de novo sobre a mesma
pasta não duplica a linha do next.config.ts nem a seção do README. Para
dockerizar um projeto que nasceu sem Docker, o caminho é gerar um projeto novo
com a flag e copiar os quatro arquivos.
Design system
O design é composto de dois eixos independentes. Qualquer paleta combina com qualquer estilo — são 9 × 4 = 36 combinações.
--color — paleta
Cada paleta define os mesmos 9 tokens de papel (ink, ink-soft, canvas,
surface, accent, accent-ink, accent-lit, muted, line), então trocar
de paleta não toca em nenhum componente.
| Paleta | Vibe | Segmentos típicos |
|---|---|---|
| gold (padrão) | editorial, premium | advocacia, contabilidade, imobiliária |
| blue | confiança institucional | clínicas, TI, engenharia, financeiro |
| teal | clínico limpo | odonto, estética, spa, laboratório |
| green | natural, sustentável | ambiental, agro, solar, nutrição |
| yellow | energia, alta visibilidade | construção, mecânica, solar, delivery |
| orange | acolhedor, próximo | pet, infantil, marketing, e-commerce |
| red | urgência, apetite | food, academia, automotivo, chaveiro |
| purple | criativo, beleza | estética, moda, design, eventos |
| slate | neutro, sóbrio | arquitetura, design, B2B premium |
As 9 paletas passam em 13 checagens de contraste WCAG cada
(node scripts/check-contrast.mjs), incluindo AAA para texto de corpo.
--style — estilo visual
O estilo controla raio, sombra, par tipográfico, escala de texto e o tratamento
de .eyebrow, .reticle e .prose-site.
| Estilo | Vibe | O que muda |
|---|---|---|
| editorial (padrão) | sério, documental | raio 2–6px, hairline entre seções, eyebrow mono com ticks de canto. Archivo + IBM Plex |
| modern | SaaS, produto | raio 8–20px, sombra difusa, eyebrow em badge arredondado. Sora + Inter + JetBrains Mono |
| bold | pesado, contrastado | raio 0, sombra dura offset, eyebrow em etiqueta chapada, títulos em caixa alta. Archivo Black + Inter |
| soft | orgânico, acolhedor | raio 10–28px, sem bordas, display serifado, blockquote com fundo tingido. Fraunces + Nunito Sans |
Galeria de designs
Quem escolhe no escuro? O terminal mostra um quadradinho de cor e a palavra "editorial". Para ver as 36 combinações de verdade, abra a galeria de designs — as combinações renderizadas com o CSS exato que o projeto recebe (hero, cards, CTA e formulário), com filtro por paleta e estilo:
- Arquivo gerado:
docs/gallery.html - Regenerar:
npm run build:gallery(lêdesign/, nunca desatualiza)
Trocar o design depois do init
O globals.css e o fonts.ts gerados são arquivos comuns do projeto — edite à
vontade, nada os regenera. Para trocar de combinação, rode o init de novo em
uma pasta limpa, ou copie os pedaços de design/ na mão.
generate --strategy <path>
Valida o strategy.json, monta o prompt final (prompt mestre + estratégia
serializada) e grava em <out>/PROMPT.md.
seo-site generate --strategy ./strategy.json --out ./cliente-exemplo| Opção | Padrão | Descrição |
|---|---|---|
| -s, --strategy <path> | — (obrigatório) | Caminho do strategy.json |
| -o, --out <path> | . | Pasta do projeto já scaffoldado pelo init |
| -f, --file <nome> | PROMPT.md | Nome do arquivo de prompt gerado |
| -T, --type <tipo> | detectado | Força o prompt mestre de um tipo específico |
| -p, --print | false | Imprime o prompt no stdout em vez de gravar o arquivo |
Com --print dá para mandar direto para outro lugar:
seo-site generate -s ./strategy.json --print | xclip -selection clipboardDepois é só abrir o Claude Code no projeto:
cd cliente-exemplo && claude
> Leia PROMPT.md e execute o que está descrito lá.Há um prompt mestre por tipo de site, e o generate escolhe sozinho olhando
o projeto em --out (só a landing tem src/content/landing.ts). Use --type
para forçar.
| Tipo | Prompt mestre |
|---|---|
| completo | src/prompts/generate-content.md |
| landing | src/prompts/generate-landing.md |
É o prompt mestre que define o que o Claude Code faz com a estratégia: ordem de
execução, mapeamento campo a campo do strategy.json, estrutura das páginas,
regras de on-page, SEO local, busca generativa e conformidade, mais o checklist
de verificação. O de landing acrescenta a premissa da página única — e manda
avisar no relatório final quando a estratégia pede algo que só o modo completo
comporta (blog, área logada, captação com banco), em vez de improvisar.
Edite esses arquivos para ajustar o padrão da agência; a mudança vale para todos os projetos seguintes.
studio
Sobe um app em http://localhost:4173 que junta a galeria de designs com o
formulário do strategy.json — escolha o tipo de site e a combinação de design,
preencha o planejamento e clique em Gerar site: o studio roda o init +
generate por você, na pasta atual, com o prompt mestre do tipo escolhido.
seo-site studio # abre o navegador automaticamente
seo-site studio --port 5000 # porta diferente
seo-site studio --no-open # sem abrir o navegadorO servidor escuta só em 127.0.0.1. O que ele gera é exatamente o que o init
e o generate fazem na mão: o projeto pronto com strategy.json e PROMPT.md
dentro. Se a pasta destino já existir e não estiver vazia, ele recusa (marque
"Sobrescrever pasta" para forçar).
O formulário tem campo de verdade para cada item do strategy.json — nada de
colar termo | intenção | volume numa textarea:
| Recurso | O que faz |
|---|---|
| Seletor de design | 9 swatches de paleta + 4 chips de estilo; escolher filtra a galeria naquela paleta e marca a combinação como escolhido. Clicar num card da galeria faz o caminho inverso |
| Listas editáveis | Palavras-chave, páginas e FAQs em linhas com adicionar/remover, select de intenção e de tipo de página |
| Rascunho automático | Tudo fica no localStorage do navegador; fechar e reabrir não perde nada. Limpar zera |
| Importar / Exportar | Carrega um strategy.json existente para editar, ou baixa o que está no formulário |
| Exemplo | Preenche com uma clínica fictícia completa, para ver o formato de cada campo |
| Validação | O botão só libera com pasta, nome do cliente e domínio; o rodapé mostra o que falta |
| Já vem dockerizado | Checkbox na seção do tipo de site — mesmo efeito do init --docker |
| Ctrl/⌘ + Enter | Gera sem tirar a mão do teclado |
A interface acompanha o tema do sistema (claro/escuro) e o accent do studio muda junto com a paleta escolhida.
Registro de scaffolds (opt-in)
Cada init (no terminal ou no studio) pode avisar um servidor da agência sobre
o scaffold gerado — nome da pasta, paleta, estilo, versão do CLI e plataforma.
Nada é enviado por padrão: sem o token no ambiente, o CLI não abre nenhuma
conexão.
export SEO_SITE_TOKEN=segredo-da-agencia # liga o registro
export SEO_SITE_API_URL="https://.../scaffolds" # opcional; sobrescreve o padrãoO POST é fire-and-forget: tem timeout, erros são silenciosos — o comando termina na hora, mesmo sem internet.
- Cabeçalho de autenticação:
x-seo-site-token: <token> - Corpo:
{ projeto, paleta, estilo, template, cliVersion, node, platform, ts }
O pacote já aponta por padrão para a Edge Function register-scaffold do
Supabase da agência (sobrescreva com SEO_SITE_API_URL se quiser outro lugar).
A referência da API fica em supabase/:
supabase db push # cria a tabela scaffolds
supabase functions deploy register-scaffold # respeita verify_jwt=false do config.toml
supabase secrets set SEO_SITE_TOKEN=segredo-da-agenciaO SEO_SITE_TOKEN precisa ser o mesmo valor no CLI e no secret da função.
Como o registro só sai com o token, quem instala o pacote do npm público nunca
dispara nada sem querer — você recebe registros atribuídos, não telemetria
anônima.
registrar [pasta]
Registra o site no painel da agência depois que o Supabase do cliente estiver configurado. Lê do projeto:
.env.local/.env→SUPABASE_URL+SUPABASE_SECRET_KEY(é daí que o painel lê os leads)strategy.json→ nome e domínio do cliente
export SEO_SITE_TOKEN=segredo-da-agencia # mesmo token do registro de scaffolds
seo-site registrar ./cliente-xRe-registrar um projeto já registrado atualiza as credenciais em vez de
duplicar. Depois de registrar, o site aparece com os leads no painel da
agência (projeto painel-agencia/).
strategy.json
Validado com zod. Nesta etapa exigimos apenas as chaves de alto nível — os campos internos ficam livres:
| Campo | Tipo |
|---|---|
| client | objeto |
| keywords | array ou objeto |
| pages | array ou objeto |
| faqs | array ou objeto |
| topicalAuthority | array ou objeto |
| schema | array ou objeto |
| gmb | objeto |
Veja strategy.example.json para um exemplo completo (ele também é copiado
para dentro de todo projeto criado pelo init).
Estrutura do pacote
├── bin/ # entrypoint compilado (tsup → bin/cli.js)
├── src/
│ ├── cli.ts # definição dos comandos (commander)
│ ├── commands/
│ │ ├── init.ts # copia o template do tipo escolhido para o destino
│ │ ├── generate.ts # lê strategy.json, monta e grava o PROMPT.md
│ │ ├── studio.ts # servidor local: galeria + formulário do strategy.json
│ │ └── registrar.ts # registra o site no painel da agência
│ ├── lib/
│ │ ├── strategySchema.ts # validação do strategy.json (zod)
│ │ ├── design.ts # catálogo de paletas/estilos e composição do CSS
│ │ ├── tipos.ts # catálogo de tipos de site (template + prompt de cada um)
│ │ ├── docker.ts # aplica o Docker no projeto gerado (--docker)
│ │ ├── registry.ts # registro opt-in dos scaffolds (fire-and-forget)
│ │ ├── prompt.ts # prompts do modo interativo (sem dependências)
│ │ └── paths.ts # resolução de caminhos do pacote
│ └── prompts/
│ ├── generate-content.md # prompt mestre do tipo completo
│ └── generate-landing.md # prompt mestre do tipo landing
├── design/
│ ├── palettes/<cor>.css # 9 tokens de cor por paleta
│ └── styles/<estilo>/
│ ├── theme.css # fontes, escala tipográfica, raio (entra no @theme)
│ ├── style.css # base + components + prose
│ └── fonts.ts # next/font do estilo
├── scripts/
│ ├── design-tokens.mjs # leitura e validação dos tokens (fonte única)
│ ├── check-contrast.mjs # valida WCAG de todas as paletas
│ ├── check-combos.mjs # gera as 36 combinações + os templates e valida o contrato
│ ├── build-gallery.mjs # gera docs/gallery.html com as 36 combinações
│ ├── gallery-content.mjs # negócios de exemplo do preview da galeria
│ └── sync-template-design.mjs # regenera o CSS commitado dos templates
├── templates/
│ ├── base-nextjs/ # tipo completo: site institucional (ver abaixo)
│ └── landing-nextjs/ # tipo landing: uma página só (ver abaixo)
├── docker/ # arquivos copiados pelo --docker (comuns aos dois tipos)
│ ├── Dockerfile # alvos dev, builder e runner
│ ├── docker-compose.yml # perfis dev e prod
│ ├── dockerignore # vira .dockerignore no projeto
│ ├── README-section.md # seção anexada ao README do projeto
│ └── CLAUDE-section.md # seção anexada ao CLAUDE.md do projeto
├── docs/
│ └── gallery.html # galeria das 36 combinações (npm run build:gallery)
├── supabase/ # referência da API de registro (fora do pacote npm)
│ ├── functions/register-scaffold/ # Edge Function do registro opt-in
│ └── migrations/0001_scaffolds.sql # tabela scaffolds
└── package.jsonO que os templates entregam
Os dois rodam Next.js 16 (App Router) + Tailwind 4, compartilham o design system e a mesma disciplina de SEO. O que muda é o tamanho da superfície.
base-nextjs — tipo completo
Site institucional com Supabase, na mesma arquitetura de um projeto de SEO já validado em produção:
| Área | Onde |
|---|---|
| Dados do negócio (NAP, unidades, horários, tópicos) | src/lib/site.ts |
| Metadata + Open Graph padronizados | src/lib/seo.ts |
| JSON-LD (grafo do site, FAQ, breadcrumb, serviço, blog, artigo) | src/lib/schema.ts |
| sitemap.xml, robots.txt, llms.txt, ai.txt | src/app/ |
| Blog com Supabase, markdown e índice automático | src/lib/blog.ts |
| Formulário de contato com anti-spam, UTM e consentimento LGPD | src/app/actions/lead.ts |
| Aviso de lead novo por e-mail (Resend) | src/lib/notificar.ts |
| GA4 com rastreio automático de WhatsApp/telefone/e-mail | src/lib/analytics.ts |
| Meta Pixel + Conversions API e GA4 server-side, deduplicados | src/lib/conversions/ |
| Painel /admin: login, contatos, editor do blog | src/app/admin/ |
| Analisador do Search Console (cliques, impressões, CTR, termos, por página) | src/lib/search-console.ts |
| Design system (cores, tipografia, prose) | src/app/globals.css |
| Fontes do estilo escolhido | src/app/fonts.ts |
| Esquema do banco com RLS | supabase/migrations/ |
Rotas já criadas:
/ home
/blog /blog/[categoria] /blog/[categoria]/[slug]
/sobre /contato /politica-de-privacidade /termos-de-uso
/sitemap.xml /robots.txt /llms.txt /ai.txt
/admin /admin/leads /admin/posts /admin/posts/[id] /admin/buscaAs páginas de conversão (/<slug-do-servico>) e o conteúdo do blog são o que o
Claude Code gera a partir do strategy.json.
O projeto sobe sem nenhuma variável de ambiente: o blog fica vazio, o formulário
avisa que está indisponível e /admin mostra o que falta configurar.
landing-nextjs — tipo landing
Uma landing page só. Sem banco de dados, sem blog, sem painel e sem formulário: o contato sai por WhatsApp e telefone, e o clique é medido como conversão.
| Área | Onde |
|---|---|
| Dados do negócio (NAP, unidades, horários, tópicos) | src/lib/site.ts |
| Conteúdo da landing, seção por seção | src/content/landing.ts |
| A página inteira | src/app/page.tsx |
| Metadata + Open Graph padronizados | src/lib/seo.ts |
| JSON-LD (grafo do negócio, FAQPage, avaliações) | src/lib/schema.ts |
| sitemap.xml, robots.txt, llms.txt, ai.txt | src/app/ |
| GA4 com rastreio automático de WhatsApp/telefone/e-mail | src/lib/analytics.ts |
| Meta Pixel + Conversions API, deduplicados por eventID | src/lib/conversions/ |
| Design system (cores, tipografia, prose) | src/app/globals.css |
| Fontes do estilo escolhido | src/app/fonts.ts |
Rotas já criadas:
/ a landing
/politica-de-privacidade LGPD
/sitemap.xml /robots.txt /llms.txt /ai.txt
/api/eventos espelha cliques na CAPI da Meta (única rota de servidor)As seções da página são âncoras, não rotas — #servicos, #como-funciona,
#diferenciais, #sobre, #faq, #onde-estamos — e cada uma some sozinha se
o conteúdo correspondente estiver vazio. O llms.txt publica o conteúdo em
texto puro, já que não há páginas para indexar.
Nenhuma variável de ambiente é obrigatória: sem .env.local a landing sobe
inteira e só a medição fica desligada. Sem META_CAPI_TOKEN, o clique é medido
só pelo navegador.
Desenvolvimento
npm run build # compila src/ → bin/cli.js
npm run dev # build em watch mode
npm run typecheck # tsc --noEmitPublicação
O publish é automático, disparado por tag. O push comum no main só roda os
checks.
npm version minor # sobe a versão, cria o commit e a tag
git push --follow-tags # dispara o workflow ReleaseA Action valida que a tag bate com a versão do package.json, que essa versão
ainda não está no registry, roda o CI inteiro e só então publica. A
autenticação usa Trusted Publishing (OIDC) — não existe NPM_TOKEN nos
secrets, o npm confia no repositório e no nome do workflow.
Configuração única, em npmjs.com → o pacote → Settings → Trusted Publisher: repositório
yagofontanez/create-seo-site, workflowrelease.yml.
O que o CI verifica
| Check | O que pega |
|---|---|
| npm run typecheck | erro de tipo no CLI |
| npm run check:contrast | paleta que reprova em WCAG |
| galeria de designs | docs/gallery.html divergindo do que build:gallery geraria |
| smoke test do studio | o servidor do studio não subir ou não servir / e /galeria |
| npm run check:combos | paleta ou estilo incompleto, nas 36 combinações + uma passada por template extra |
| guarda do tarball | design/, os templates ou os prompts ficando de fora do pacote — o erro que quebraria o init em produção |
| matriz scaffold | init + generate + next build real do projeto gerado, em 6 combinações de tipo e design |
| matriz docker | init --docker nos dois tipos: compose válido, output: "standalone" no next.config.ts e a imagem de produção respondendo 200 |
