Janus CMS
Janus CMS é um sistema de gestão de conteúdo headless multi-tenant desenvolvido pela Mavellium. Construído nativamente sobre Next.js App Router e TypeScript, expõe conteúdo via API REST pública com ISR integrado, entregando HTML pré-renderizado em menos de 100ms de TTFB via Vercel Edge Network.
O conteúdo é armazenado como JSONB no PostgreSQL via Prisma 7 e servido por uma API REST com Cache-Control: s-maxage=60, permitindo cache de borda global sem invalidação manual. Imagens são convertidas para AVIF automaticamente via Sharp antes do upload ao BunnyCDN.
- Headless
- API REST pública · CORS aberto
- Multi-tenant
- Company → Project → Page
- Dual Mode
- Legacy (form) · Avançado (JSON livre)
- ISR nativo
- Cache-Control s-maxage=60 por rota
- AVIF automático
- Sharp quality 80 · −50% tamanho
Stack tecnológica
Versões extraídas do package.json do repositório Janus. O Janus não usa dependências de terceiros para renderização de conteúdo — o HTML é gerado diretamente por React Server Components.
| Camada | Tecnologia | Versão |
|---|---|---|
| Framework | Next.js (App Router) | 16.2.4 |
| Runtime | React | 19.2.4 |
| Linguagem | TypeScript (strict mode) | 5.x |
| Banco de dados | PostgreSQL (JSONB) | — |
| ORM | Prisma + PG Adapter | 7.8.0 |
| Autenticação | NextAuth v5 (JWT, Credentials) | 5.0-beta.31 |
| Validação | Zod | 4.4.3 |
| Editor de texto | Tiptap | — |
| Editor de schema | Monaco Editor | — |
| Imagens | Sharp (→ AVIF, quality 80) | 0.34.5 |
| CDN | BunnyCDN | — |
| UI | shadcn/ui + Radix UI | — |
| Drag & drop | dnd-kit | 6.3.1 |
| Testes | Vitest + @testing-library/react | 4.1.5 |
| Estilização | Tailwind CSS v4 | — |
Arquitetura de dados
Modelo extraído do prisma/schema.prisma do repositório Janus.
Hierarquia multi-tenant
O Janus CMS isola dados por Company. Cada Company contém Projects; cada Project contém Pages. Esta hierarquia garante que nenhuma query vaze dados entre tenants — o companyId é chave estrangeira obrigatória em todas as entidades filhas.
Company (id: UUID, slug: unique, name: string)
└─ Project (type: LANDING_PAGE | INSTITUTIONAL,
blogEnabled: bool, isActive: bool)
└─ Page (schemaData: JSON,
contentData: JSON,
isAdvanced: bool, // false = Legacy | true = Avançado
uiSchema: JSON?, // controle de labels/widgets (opcional)
isPublished: bool,
slug: unique per project,
deletedAt: nullable)Todas as entidades possuem soft delete via deletedAt. Consultas filtram deletedAt: null por padrão.
Dois modos de edição: Legacy e Avançado
O campo isAdvanced da Page determina qual modo está ativo. A troca de modo nunca apaga dados — apenas muda a flag via updatePageMode().
Legacy — isAdvanced = false
schemaData define a estrutura de campos (read-only pelo dev).
contentData armazena os valores preenchidos via DynamicForm.
Suporta 11 tipos de campo predefinidos.
Avançado — isAdvanced = true
schemaData é JSON livre editado via Monaco Editor (3 colunas + preview).
contentData é ignorado completamente.
Estrutura de dados totalmente livre, sem schema predefinido.
Tipos de campo suportados no modo Legacy:
texttextareanumbercolorurlimagevideobooleanselecthtmllistCampo uiSchema (Modo Avançado)
Controla labels, tipos de widget e visibilidade de campos sem poluir o JSON de dados. Usa notação de ponto com suporte a wildcards:
{
"cards.*.image": { "ui:widget": "image" },
"cards.*.buttonText": { "ui:label": "Texto do Botão" },
"internalId": { "ui:hidden": true }
}API REST pública headless
A API REST do Janus é pública, CORS-aberta e projetada para ser consumida por React Server Components. A resposta do servidor ocorre em <50ms; com s-maxage=60, a Vercel Edge Network serve o cache em <10ms.
GET /api/v1/content/{companySlug}/{pageSlug}
Headers de resposta:
Cache-Control: public, max-age=60, s-maxage=60
Access-Control-Allow-Origin: *
Body 200 — OK:
{
"slug": "home",
"name": "Página Inicial",
"schema": { ... }, // schemaData (estrutura ou JSON livre)
"content": { ... }, // contentData (modo legacy) ou null (modo avançado)
"updatedAt": "2026-05-16T00:00:00.000Z"
}
Requisitos para HTTP 200:
page.isPublished = true
project.isActive = true
company.deletedAt = null
Body 404 — Not Found:
{ "error": "Page not found or not published" }SDK TypeScript
O janus-sdk é o cliente TypeScript oficial para a API pública do Janus CMS. Framework-agnostic, exporta ESM e CommonJS via tsup e usa apenas fetch nativa (Node ≥ 18).
Instalação
Disponível como workspace package no monorepo Mavellium. Adicione ao package.json do seu projeto Next.js:
{
"dependencies": {
"janus-sdk": "workspace:*"
}
}Depois rode pnpm install na raiz do monorepo. O pnpm cria um symlink para janus-sdk/dist/.
Inicialização (singleton recomendado)
// src/lib/janus.ts
import { JanusClient } from "janus-sdk";
export const janus = new JanusClient({
baseUrl: process.env.JANUS_URL!,
tenantId: process.env.JANUS_TENANT_ID!,
defaultInit: {
next: { revalidate: 60 }, // ISR — revalida a cada 60s
} as RequestInit,
});Erros tipados
O SDK expõe JanusAPIError com o código HTTP e a URL que falhou:
export class JanusAPIError extends Error {
readonly status: number // ex: 500, 503
readonly url: string // URL completa que falhou
}Uso em Server Component
import { notFound } from "next/navigation";
import { janus } from "@/lib/janus";
// Tipo do content para a página "home"
interface HomeContent {
"hero-section-mavellium": { slides: HeroSlide[] };
"sobre-mavellium": { services: Service[] };
}
export default async function HomePage() {
// getPage retorna null em 404 e re-lança JanusAPIError em 5xx
const page = await janus.getPage("home");
if (!page) notFound();
const content = page.content as HomeContent;
return <HeroSection slides={content["hero-section-mavellium"].slides} />;
}O fetch ocorre no servidor Next.js (SSG/SSR) — o SDK, a URL da API e as variáveis de ambiente nunca chegam ao bundle do browser.
Pipeline de renderização
O HTML gerado pelo Janus é otimizado para LLMs porque é produzido em React Server Components (zero JS client-side para conteúdo), servido em menos de 100ms via Vercel Edge, e estruturado com tags semânticas nativas — não divs de editor WYSIWYG.
- 1
Janus Admin
Conteúdo editado e salvo no PostgreSQL
Legacy (isAdvanced=false): usuário preenche contentData via DynamicForm. Avançado (isAdvanced=true): developer edita schemaData diretamente via Monaco Editor (3 colunas com preview em tempo real). Em ambos os casos, imagens são convertidas para AVIF (Sharp, quality 80) e enviadas ao BunnyCDN antes de salvar a URL.
- 2
Janus API
revalidatePath() invalida o cache ISR do consumidor
Cada mutação de conteúdo chama revalidatePath() nas rotas afetadas do site consumidor. O Next.js invalida o HTML estático em cache.
- 3
Janus API
GET /api/v1/content/... responde com Cache-Control: s-maxage=60
A requisição chega ao servidor Janus, executa query Prisma no PostgreSQL e retorna JSON com os headers de cache. Latência real: <50ms.
- 4
JanusClient SDK
getPage(slug) executa no Server Component — nunca no browser
O fetch acontece no servidor Next.js durante SSG/SSR via janus-sdk. O bundle JavaScript enviado ao browser não contém o SDK, a URL da API nem as credenciais de ambiente.
- 5
Next.js (SSG)
Pré-renderiza a página no build — HTML completo no response
Com os dados retornados pelo SDK, o Next.js renderiza os React Server Components e gera o HTML final. Nenhum JavaScript de conteúdo é necessário no client.
- 6
Vercel Edge Network
Serve o HTML estático com TTFB <100ms global
O HTML gerado é armazenado em CDN distribuída. Requisições subsequentes são servidas pelo nó de edge mais próximo do cliente, sem processamento server-side.
- 7
LLM Crawler
Recebe HTML completo, semântico, sem necessidade de executar JavaScript
O parser do LLM lê o HTML do primeiro byte. Todo o conteúdo — texto, headings, listas — está presente no response inicial. Zero dependência de renderização client-side.
Estratégia de cache em três camadas
LLMs como o Perplexity operam com timeout de 2–5s e parsers síncronos. O Janus CMS + Next.js SSG entrega o HTML completo antes de 100ms, sem renderização client-side. O conteúdo existe no <body> do primeiro byte — exatamente o que parsers de LLM precisam para extrair e citar com fidelidade.
| Camada | Mecanismo | Efeito |
|---|---|---|
| Janus API | Cache-Control: public, max-age=60, s-maxage=60 | Vercel Edge armazena a resposta JSON por 60s |
| Next.js ISR | revalidatePath() on mutation | HTML pré-renderizado invalidado sob demanda; novo HTML gerado na próxima requisição |
| BunnyCDN | Pull Zone global | Assets AVIF servidos do nó de borda mais próximo; cache permanente até invalidação manual |
Quer o Janus CMS no seu projeto?
O Janus CMS é desenvolvido e mantido pela Mavellium. Entre em contato para avaliar a adoção no seu stack Next.js.
Falar com Especialista