Tüm yazılar

Malton London Press

Yayında

Açık erişimli akademik bir yayınevinin web sitesi ve yayın sistemi. Ben yaptım, sunucuyu ben yönetiyorum ve her değişikliği ben deploy ediyorum.

Rolüm
Tek geliştirici
Yıl
2026
Teknolojiler
Next.js · Payload CMS · PostgreSQL · Turborepo · PM2 · Nginx · Hetzner · GitHub Actions
GITHUB ACTIONS: LINT, TYPES, TESTSDEPLOY SCRIPT: INSTALL, MIGRATE, BUILDNGINX ON HETZNERNEXT.JS: BOOKS, PAPERS, PDFSPAYLOAD CMS: EDITORSPOSTGRESQLBY HANDPM2 RELOADREADERSCONTENT APIPREVIOUS COMMIT SAVED FOR ROLLBACKGOOGLE SCHOLAR CITATION TAGS

Diyagramın tamamını görmek için yana kaydırın.

MimariPUSH → CHECKS → DEPLOY BY HAND → HEALTH CHECK

Problem

Akademik bir yayınevinin bir web sitesinden fazlasına ihtiyacı var. Her kitap ve konferans bildirisi ücretsiz okunup indirilebilmeli, editörler koda dokunmadan yayın yapabilmeli ve Google Scholar her makaleyi bulup indekslemeli. Yoksa yazarların emeği görünmez kalır.

Nasıl yapıldı

İki uygulama ve bir ortak paketten oluşan bir pnpm ve Turborepo monorepo'su: okurlar için bir Next.js sitesi ve editörlerin çalıştığı, sitenin içeriğini de aldığı, PostgreSQL üzerindeki Payload CMS. Okurlar kitaplara ve konferans bildiri kitaplarına göz atıp PDF'leri indiriyor, yazarlar kitap önerilerini bir formla gönderiyor. Her şey Nginx arkasında tek bir Hetzner sunucusunda çalışıyor; PM2 iki uygulamayı da cluster modunda tutuyor. Her push'ta GitHub Actions lint, type check ve testleri çalıştırıyor. Deploy elle başlatılıyor: workflow kontrolleri tam o commit üzerinde yeniden çalıştırıyor, sunucuya SSH ile bağlanıyor, bir insanın çalıştıracağı deploy script'inin aynısını çalıştırıyor (install, migrate, build, reload) ve site cevap verene kadar dışarıdan bir health endpoint'ini yokluyor.

Önemli kararlar

  • Deploy sadece elle başlıyor

    Push'ta hiçbir şey deploy olmuyor, yani main'e giren hatalı bir commit kendi kendine production'a ulaşamaz. Workflow, bir insanın çalıştıracağı script'in aynısını çağırıyor; böylece elle ve otomatik yollar birbirinden kopamıyor.

  • Otomatik rollback yok

    Eski kodu checkout etmek, çoktan çalışmış bir veritabanı migration'ını geri almaz; bu yüzden geri dönmek bilinçli bir adım olarak kalıyor. Her deploy'dan önce bir önceki commit sunucuda kaydediliyor, bu da rollback'i tek bir komuta indiriyor.

  • Google Scholar'ın okuyabildiği metadata

    Her makale ve kitap sayfası Highwire Press citation tag'leri taşıyor ve her yazarın hemen arkasından kendi kurumu ve ORCID'i geliyor. Böylece Google Scholar ve Zotero gibi kaynak yöneticileri onları doğru indeksliyor.

Repodan kod

apps/web/src/lib/citation.tsBir konferans bildirisi için Google Scholar citation tag'leri
/** 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
}
  1. 9. satır. Scholar, bir kurumu ve bir ORCID'i hemen önündeki yazar tag'ine bağlıyor; bu yüzden bu tag'lerin sırası anlam taşıyor.

  2. 27. satır. Tarihler, Scholar'ın istediği gibi UTC'de YYYY/MM/DD biçiminde çıkıyor.

  3. 31. satır. Scholar bir PDF'i yalnızca makale sayfasıyla aynı host ve dizinden kabul ediyor, bu yüzden link sayfanın kendi yolundan kuruluyor.

Şu an ne durumda

Ocak 2026'dan beri maltonlondonpress.com adresinde yayında. Ayakta tutan da her değişikliği deploy eden de benim.

Sonraki yazıResearchGo