Documentação Técnica · Mavellium

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.

CamadaTecnologiaVersão
FrameworkNext.js (App Router)16.2.4
RuntimeReact19.2.4
LinguagemTypeScript (strict mode)5.x
Banco de dadosPostgreSQL (JSONB)
ORMPrisma + PG Adapter7.8.0
AutenticaçãoNextAuth v5 (JWT, Credentials)5.0-beta.31
ValidaçãoZod4.4.3
Editor de textoTiptap
Editor de schemaMonaco Editor
ImagensSharp (→ AVIF, quality 80)0.34.5
CDNBunnyCDN
UIshadcn/ui + Radix UI
Drag & dropdnd-kit6.3.1
TestesVitest + @testing-library/react4.1.5
EstilizaçãoTailwind 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:

texttextareanumbercolorurlimagevideobooleanselecthtmllist

Campo 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. 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. 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. 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. 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. 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. 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. 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.

CamadaMecanismoEfeito
Janus APICache-Control: public, max-age=60, s-maxage=60Vercel Edge armazena a resposta JSON por 60s
Next.js ISRrevalidatePath() on mutationHTML pré-renderizado invalidado sob demanda; novo HTML gerado na próxima requisição
BunnyCDNPull Zone globalAssets 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