Malton London Press
No arO site e o sistema de publicação de uma editora acadêmica de acesso aberto. Eu o desenvolvi, cuido do servidor e faço o deploy de cada mudança.
- Meu papel
- Único desenvolvedor
- Ano
- 2026
- Feito com
- Next.js · Payload CMS · PostgreSQL · Turborepo · PM2 · Nginx · Hetzner · GitHub Actions
Role para o lado para ver o diagrama inteiro.
O problema
Uma editora acadêmica precisa de mais do que um site. Cada livro e cada artigo de congresso precisa poder ser lido e baixado de graça, os editores precisam publicar sem mexer em código e o Google Scholar precisa encontrar e indexar cada artigo. Se isso não acontece, o trabalho dos autores fica invisível.
Como foi construído
Um monorepo com pnpm e Turborepo, com dois apps e um pacote compartilhado: um site em Next.js para os leitores e o Payload CMS sobre PostgreSQL, onde os editores trabalham e de onde o site tira o conteúdo. Os leitores navegam por livros e anais de congressos e baixam os PDFs, e os autores enviam propostas de livro por um formulário. Tudo roda em um único servidor da Hetzner atrás do Nginx, e o PM2 mantém os dois apps em modo cluster. Cada push roda lint, checagem de tipos e testes no GitHub Actions. O deploy é disparado manualmente: o workflow roda as verificações de novo no commit exato, conecta-se ao servidor por SSH, executa o mesmo script de deploy que uma pessoa executaria (install, migrate, build, reload) e depois faz polling de um endpoint de health de fora até o site responder.
Decisões importantes
O deploy só é disparado manualmente
Um push não dispara deploy nenhum, então um commit ruim na main não chega à produção sozinho. O workflow chama o mesmo script que uma pessoa rodaria, por isso o caminho manual e o automatizado não têm como divergir.
Sem rollback automático
Fazer checkout de um código antigo não desfaz uma migration de banco de dados que já rodou, então voltar atrás continua sendo um passo deliberado. O commit anterior é salvo no servidor antes de cada deploy, o que faz do rollback um único comando.
Metadados que o Google Scholar consegue ler
Cada página de artigo e de livro traz as tags de citação do Highwire Press, com cada autor seguido da própria instituição e do próprio ORCID, para que o Google Scholar e gerenciadores de referências como o Zotero indexem essas páginas corretamente.
Código do repositório
/** One citation_author per person, each followed by its own institution and ORCID. */
function authorTags(people: Person[] | null | undefined): CitationTag[] {
const tags: CitationTag[] = []
for (const person of people ?? []) {
const name = person.name?.trim()
if (!name) continue
tags.push(['citation_author', name])
const institution = person.affiliation?.trim()
if (institution) tags.push(['citation_author_institution', institution])1
const orcid = orcidUrl(person.orcid)
if (orcid) tags.push(['citation_author_orcid', orcid])
}
return tags
}
// …
/** Tags for a conference paper page (/proceedings/[volume]/[paper]). */
export function paperCitationTags(
paper: Pick<Paper, 'title' | 'slug' | 'authors' | 'doi' | 'pdf' | 'pagesRange'>,
volume: Pick<Book, 'title' | 'slug' | 'publicationDate' | 'isbn' | 'conference'>,
siteUrl: string = publicSiteUrl(),
): CitationTag[] {
const tags: CitationTag[] = []
add(tags, 'citation_title', paper.title)
tags.push(...authorTags(paper.authors))
add(tags, 'citation_publication_date', citationDate(volume.publicationDate))2
// …
const site = siteUrl.replace(/\/+$/, '')
// Same host and directory as this page (Scholar ignores a PDF anywhere else).
if (hasFile(paper.pdf)) add(tags, 'citation_pdf_url', `${site}${paperPdfPath(volume.slug, paper.slug)}`)3
add(tags, 'citation_abstract_html_url', `${site}/proceedings/${volume.slug}/${paper.slug}`)
add(tags, 'citation_language', 'en')
return tags
}
Linha 9. O Scholar associa uma instituição e um ORCID à tag de autor que vem logo antes, então a ordem dessas tags tem significado.
Linha 27. As datas saem como YYYY/MM/DD em UTC, o formato que o Scholar pede.
Linha 31. O Scholar só aceita um PDF que esteja no mesmo host e diretório da página do artigo, então o link é montado a partir do caminho da própria página.
Como está hoje
No ar desde janeiro de 2026 em maltonlondonpress.com. Eu cuido para que continue no ar e faço o deploy de cada mudança.