Malton London Press
लाइवएक ओपन-एक्सेस एकेडमिक पब्लिशर की वेबसाइट और पब्लिशिंग सिस्टम। इसे मैंने बनाया है, सर्वर मैं चलाता हूँ और हर बदलाव मैं डिप्लॉय करता हूँ।
- मेरी भूमिका
- एकमात्र डेवलपर
- साल
- 2026
- टेक्नोलॉजी
- Next.js · Payload CMS · PostgreSQL · Turborepo · PM2 · Nginx · Hetzner · GitHub Actions
पूरा डायग्राम देखने के लिए साइड में स्क्रॉल करें।
समस्या
एक एकेडमिक पब्लिशर को वेबसाइट से ज़्यादा कुछ चाहिए। हर किताब और कॉन्फ़्रेंस पेपर मुफ़्त में पढ़ा और डाउनलोड किया जा सके, एडिटर बिना कोड छुए पब्लिश कर सकें, और Google Scholar हर पेपर को ढूँढकर इंडेक्स करे। ऐसा न हो, तो लेखकों का काम अनदेखा रह जाता है।
यह कैसे बना है
pnpm और Turborepo वाला एक monorepo, जिसमें दो ऐप और एक शेयर्ड पैकेज हैं: पाठकों के लिए एक Next.js साइट, और PostgreSQL पर Payload CMS, जहाँ एडिटर काम करते हैं और जहाँ से साइट अपना कंटेंट लेती है। पाठक किताबें और कॉन्फ़्रेंस प्रोसीडिंग्स ब्राउज़ करते हैं और PDF डाउनलोड करते हैं, और लेखक एक फ़ॉर्म से किताब के प्रपोज़ल भेजते हैं। सब कुछ Nginx के पीछे एक Hetzner सर्वर पर चलता है, और PM2 दोनों ऐप्स को cluster mode में चलाए रखता है। हर push पर GitHub Actions में lint, type check और टेस्ट चलते हैं। डिप्लॉय हाथ से शुरू होता है: workflow ठीक उसी commit पर चेक दोबारा चलाता है, SSH से सर्वर से जुड़ता है, वही डिप्लॉय स्क्रिप्ट चलाता है जो कोई इंसान चलाता (install, migrate, build, reload), और फिर बाहर से एक health endpoint को तब तक poll करता है जब तक साइट जवाब न दे।
अहम फ़ैसले
डिप्लॉय सिर्फ़ हाथ से शुरू होते हैं
push पर कुछ भी डिप्लॉय नहीं होता, इसलिए main पर कोई ख़राब commit अपने आप production तक नहीं पहुँच सकता। workflow वही स्क्रिप्ट कॉल करता है जो कोई इंसान चलाता, इसलिए मैनुअल और ऑटोमेटेड रास्ते अलग-अलग दिशा में नहीं जा सकते।
कोई ऑटोमैटिक rollback नहीं
पुराना कोड checkout करने से डेटाबेस का वह migration वापस नहीं होता जो पहले ही चल चुका है, इसलिए पीछे लौटना सोच-समझकर उठाया जाने वाला कदम रहता है। हर डिप्लॉय से पहले पिछला commit सर्वर पर सेव किया जाता है, जिससे rollback एक कमांड का काम बन जाता है।
ऐसा मेटाडेटा जिसे Google Scholar पढ़ सके
हर पेपर और किताब के पेज पर Highwire Press के citation टैग होते हैं, जिनमें हर लेखक के बाद उसकी अपनी संस्था और ORCID आते हैं, ताकि Google Scholar और Zotero जैसे रेफ़रेंस मैनेजर उन्हें सही तरह से इंडेक्स करें।
रिपो से कोड
/** 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
}
लाइन 9. Scholar किसी संस्था और ORCID को उसके ठीक पहले वाले author टैग से जोड़ता है, इसलिए इन टैग का क्रम मायने रखता है।
लाइन 27. तारीख़ें UTC में YYYY/MM/DD फ़ॉर्मैट में जाती हैं, जैसा Scholar माँगता है।
लाइन 31. Scholar सिर्फ़ उसी host और डायरेक्टरी की PDF स्वीकार करता है जिसमें पेपर का पेज है, इसलिए लिंक पेज के अपने path से बनाया जाता है।
अभी कहाँ तक पहुँचा
जनवरी 2026 से maltonlondonpress.com पर लाइव है। मैं इसे चालू रखता हूँ और हर बदलाव डिप्लॉय करता हूँ।