Malton London Press
En ligneLe site web et le système de publication d'un éditeur académique en libre accès. Je l'ai développé, je gère le serveur et je déploie chaque modification.
- Mon rôle
- Seul développeur
- Année
- 2026
- Développé avec
- Next.js · Payload CMS · PostgreSQL · Turborepo · PM2 · Nginx · Hetzner · GitHub Actions
Faites défiler horizontalement pour voir tout le schéma.
Le problème
Un éditeur académique a besoin de plus qu'un site web. Chaque livre et chaque article de conférence doit pouvoir être lu et téléchargé gratuitement, l'équipe éditoriale doit pouvoir publier sans toucher au code, et Google Scholar doit trouver et indexer chaque article. Sinon, le travail des auteurs reste invisible.
Comment c'est construit
Un monorepo pnpm et Turborepo avec deux applications et un package partagé : un site Next.js pour les lecteurs, et Payload CMS sur PostgreSQL, où travaille l'équipe éditoriale et d'où le site tire son contenu. Les lecteurs parcourent les livres et les actes de conférence et téléchargent les PDF, et les auteurs envoient leurs propositions de livres via un formulaire. Tout tourne sur un seul serveur Hetzner derrière Nginx, et PM2 maintient les deux applications en mode cluster. Chaque push lance le lint, la vérification des types et les tests dans GitHub Actions. Un déploiement se lance à la main : le workflow relance les vérifications sur le commit exact, se connecte au serveur en SSH, exécute le même script de déploiement qu'une personne lancerait (install, migrate, build, reload), puis interroge depuis l'extérieur un endpoint de health check jusqu'à ce que le site réponde.
Les décisions qui ont compté
Les déploiements ne se lancent qu'à la main
Un push ne déclenche aucun déploiement, donc un mauvais commit sur main ne peut pas partir en production tout seul. Le workflow appelle le même script qu'une personne lancerait, donc le chemin manuel et le chemin automatisé ne peuvent pas diverger.
Pas de rollback automatique
Un checkout de l'ancien code n'annule pas une migration de base de données déjà exécutée, donc revenir en arrière reste une action délibérée. Le commit précédent est enregistré sur le serveur avant chaque déploiement, ce qui fait du rollback une seule commande.
Des métadonnées lisibles par Google Scholar
Chaque page d'article et de livre porte des balises de citation au format Highwire Press, où chaque auteur est suivi de sa propre institution et de son ORCID, pour que Google Scholar et les gestionnaires de références comme Zotero les indexent correctement.
Code tiré du dépôt
/** 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
}
Ligne 9. Scholar rattache une institution et un ORCID à la balise d'auteur qui les précède immédiatement, donc l'ordre de ces balises a un sens.
Ligne 27. Les dates sortent au format YYYY/MM/DD en UTC, la forme que demande Scholar.
Ligne 31. Scholar n'accepte un PDF que s'il vient du même hôte et du même répertoire que la page de l'article, donc le lien est construit à partir du chemin de la page elle-même.
Où en est le projet
En ligne depuis janvier 2026 sur maltonlondonpress.com. Je m'occupe de le faire tourner et je déploie chaque modification.