Lightkone docs Open Lightkone

Hosting

Lightkone is a static site (npm run build writes dist/), so any static host works. The repository deploys to Cloudflare Pages: free for private repositories, and in the same account as an R2 bucket for storage.

Cloudflare Pages from GitHub Actions

.github/workflows/deploy.yml publishes every push to main to a staging site (https://staging.<project>.pages.dev), and each version tag to the live site, checking both in a browser; see RELEASING.md. It needs these in the repository (Settings → Secrets and variables → Actions):

Name Kind Value
CLOUDFLARE_ACCOUNT_ID secret Shown on the Cloudflare dashboard's account home, right sidebar.
CLOUDFLARE_API_TOKEN secret My Profile → API Tokens → Create Token → Custom token, permission Account · Cloudflare Pages · Edit, limited to your account.
PAGES_DOMAIN variable (optional) A custom domain, e.g. lightkone.lab.kausalflow.com.
PAGES_PROJECT variable (optional) Pages project name; defaults to lightkone.

The first run creates the Pages project; after the first release the app is at https://<project>.pages.dev.

For a custom domain, set PAGES_DOMAIN, then point DNS at Pages:

add a CNAME from the domain to <project>.pages.dev (proxied, if the zone is on Cloudflare). The deploy token only manages Pages, so it cannot add the record.

If storage is an S3/R2 bucket, add the site's address to the bucket's CORS rule (see STORAGE.md).

The PDF service

functions/api/pdf.ts is a Cloudflare Pages Function, deployed with the site at /api/pdf. Given a DOI and a contact email it asks Unpaywall for a legal open-access copy and passes the PDF through, because publishers do not let browsers download from another site. It fetches only URLs Unpaywall returns for that DOI. arXiv PDFs do not need it. On other hosts, either run the same function elsewhere and set its URL under Settings → PDFs, or go without DOI downloads.

The published-version service

functions/api/published.ts (at /api/published?arxiv=<ids>) tells Tidy up which arXiv preprints have been published: first from the DOI and journal reference authors added on arXiv, then from Semantic Scholar. Semantic Scholar limits anonymous requests; for steadier results, request a free key at https://www.semanticscholar.org/product/api and add it to the Pages project as the secret S2_API_KEY. The same service tells Paper health which preprints have been withdrawn on arXiv.

The page service

functions/api/page.ts (at /api/page?url=<page>) fetches a web page for saving it into the library, because browsers don't let one site read another's pages. It answers JSON, never HTML, so nothing it fetches can run as part of the site, and it fetches only public http(s) addresses on the usual ports, checking each redirect, up to 5 MB, waiting at most 15 s. When the site fails or refuses, it answers 424 with the reason (never 502 or 504, which Cloudflare replaces with its own error page); the other services do the same. A page behind a browser check is read through Jina Reader (r.jina.ai) when the app asks (it does unless switched off in Settings). Without a key Jina shares one small limit among everyone calling from Cloudflare's addresses, so it mostly answers 429: add a JINA_API_KEY secret to the Pages project, in both the Production and the Preview environment (the staging site is a Preview deployment), then deploy again so the Functions see it.

Limits on the services

The services under /api/ (PDF, web pages, published versions, arXiv, equations, MCP address) are for the site's own app. functions/api/_middleware.ts refuses calls that a browser says come from another website, and limits each address to a number of calls a minute per service (30 for PDFs, 120 for equations; see LIMITS), answering 429 with Retry-After beyond that. Normal use stays far below these.

Those counts are kept per Cloudflare server. For a hard limit across the whole network, also add a rate limiting rule in the dashboard (the free plan includes one): your domain's zone → Security → WAF → Rate limiting rules → Create rule, with URI Path starts with /api/, counting by IP, for example 50 requests per 10 seconds, action Block for 10 seconds.

Other hosts

Copy dist/ to any static host (GitHub Pages, Netlify, S3 + CloudFront, an internal nginx). Paths are relative, so it also works under a sub-path.

Markdown · All docs as one text file · This page on GitHub