Browse GitHub without JavaScript.
gh-lite is an independent, unofficial, HTML-first GitHub frontend for slow networks, older devices, older browsers, and anyone who prefers server-rendered pages. Search and read public repositories, code, issues, pull requests, commits, releases, Actions, profiles, and community data without an account or JavaScript.
In a live check in August 2026, the anonymous homepage HTML, CSS, optional JavaScript, and favicon totaled about 10.5 KB compressed. Repository content and images vary by page.
Try gh-lite · Browse without JavaScript · Low-bandwidth design · Browser support
It runs on Cloudflare Workers and uses GitHub's official REST API.
Public browsing and the core application work without JavaScript or an account. The optional script adds color-theme persistence, destructive-action confirmations, and a browser-local list of saved and recently viewed repositories.
- Anonymous public profiles, organizations, followers/following, stars, gists, activity, repository trees, UTF-8 files, file history, comparisons, commits, diffs, issues, labels, milestones, pull requests, reviews, branches, tags, releases, contributors, stargazers, forks, watchers, languages, Actions workflows/runs/jobs, and search.
- Topic and trending hub pages for browsing GitHub by technology without an account.
- Server-rendered syntax highlighting for 29 languages, with numbered lines, GitHub-compatible
#L10and#L10-L20anchors, and a no-JavaScript range form. - File contents, images and release assets served from this origin rather than linking readers out to
raw.githubusercontent.com. - A repository file finder, image and CSV previews, and gist detail pages.
- Issue and pull-request timelines, pull-request tabs with CI checks, reaction counts, mergeability and diffstat, and controls that appear only when your GitHub permissions actually allow the action.
- GitHub-compatible short profile URLs and browser-local saved/recent repositories without an account.
- Optional GitHub OAuth App login with a separate private-repository permission escalation.
- Advanced personal access token login using an encrypted, HttpOnly browser cookie.
- Stars, watches, follows, issue/PR collaboration, reviews, merges, release metadata, tag deletion, and direct UTF-8 file commits.
- Server-rendered SEO metadata, JSON-LD, and a sitemap index covering the landing pages, topic hubs, and seeded repositories.
- No database, analytics script, web font, service worker, React runtime, or shared caching of authenticated responses.
Issue and pull-request lists read from the repository endpoints rather than GitHub's search API wherever the filters allow, which avoids search's 1,000-result cap and its much tighter rate limit.
Actions are read-only in gh-lite; dispatching or editing workflows remains on GitHub.com. Projects, Discussions, Packages, Codespaces, repository administration, binary uploads, release-asset uploads, and workflow-file edits also remain on GitHub.com.
No login is required to search or read public GitHub data. Sign in only when you want private repositories, notifications, or an operation that changes GitHub data: starring, watching, following, commenting, reviewing, merging, managing releases or tags, and committing text files. Saved and recently viewed repositories use local storage in the current browser and are never sent to gh-lite.
Requirements: Node.js 22 or newer and npm.
npm install
cp .dev.vars.example .dev.vars
npm run devCreate a separate development GitHub OAuth App with a callback URL of http://localhost:8787/auth/github/callback. Put its client ID, secret, and a random 32-byte session key in .dev.vars. Generate a suitable key with:
openssl rand -hex 32Run verification with npm run check. Static asset and Worker gzip budgets are enforced by scripts/check-budgets.mjs, which reports both raw and gzip sizes — the limits are gzip. The Worker budget is 400 KB gzip, mostly consumed by the syntax-highlighting grammars and the committed repository seed list; the stylesheets and optional script sit at roughly a quarter of their limits.
/:owner/:repo/raw/... and /:owner/:repo/media/... proxy file contents through this origin. Raw responses are always text/plain, never the upstream content type; with the global X-Content-Type-Options: nosniff that is what makes it safe to serve attacker-controlled .html or .svg from the same origin as the app. The media route sniffs magic bytes and serves only raster images, so a file named .png that is really markup is refused rather than rendered. SVG is shown as source.
/sitemap.xml is a sitemap index. Submit that one URL to Search Console; it names the children:
sitemap-pages.xml— the home page, guides, and hub pages.sitemap-topics.xml— one entry per curated topic insrc/topics.ts./topics/:topicvalidates against that list and 404s otherwise, which is what keeps the crawlable URL space bounded.sitemap-repos-N.xml— generated fromsrc/seeds.ts, which is empty until you build it:
GITHUB_TOKEN=ghp_... node scripts/build-repo-seeds.mjsCommit the result. The committed list currently holds about 10,000 repositories, split across sitemap-repos-1.xml onward at 5,000 URLs each. Keep the total modest: a cold crawl of each seeded repository costs roughly three GitHub API calls from an hourly budget shared with real visitors, and the list is compiled into the Worker, so it is the single largest contributor to the bundle.
Deep mirror pages (/blob/, /tree/, /commits/, /compare) are disallowed in robots.txt. That is quota protection rather than index hygiene — those URL spaces are effectively unbounded — and they keep their noindex headers for crawlers that ignore robots.
Anonymous HTML is served with shared-cache headers (src/cache.ts). Those headers only take effect because wrangler.jsonc enables Workers Cache: Workers run ahead of the zone cache, so Cache-Control alone would do nothing. Any response carrying a session or setting a cookie is forced to private, no-store by securityMiddleware, and every shared response carries Vary: Cookie because the Workers Cache key omits cookies by default.
Before relying on this in production, confirm on a preview deployment that a request carrying a session cookie never returns Cf-Cache-Status: HIT:
curl -sI https://<preview>/about
curl -sI -H 'Cookie: __Host-ghlite_session=x' https://<preview>/about- Create a GitHub OAuth App whose homepage is
https://gh-lite.tyk.shand callback URL ishttps://gh-lite.tyk.sh/auth/github/callback. - Store secrets with
npx wrangler secret put GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET, andSESSION_ENCRYPTION_KEY. - Optionally set
GOOGLE_SITE_VERIFICATIONin the Wrangler variables or environment-specific configuration. - Confirm that the Cloudflare account controls the
tyk.shzone, then runnpm run deploy. The Wrangler configuration attaches thegh-lite.tyk.shcustom domain. - Verify the property in Google Search Console and submit
https://gh-lite.tyk.sh/sitemap.xml.
For CI deployment, configure CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as GitHub Actions environment secrets. Worker runtime secrets stay in Cloudflare and are not copied into Actions.
Cloudflare Free Bot Fight Mode cannot be scoped and may serve JavaScript challenges to legitimate command-line clients, text browsers, and smaller crawlers. Keep it disabled on the dedicated gh-lite zone so public pages remain HTML-accessible; leave Cloudflare's managed WAF and DDoS protections enabled and apply the rate limits below.
Anonymous requests are proxied to GitHub under the OAuth application's shared quota, and /search hits GitHub's tightly-limited search API. Protect that quota with Cloudflare Rate Limiting rules (Security → WAF → Rate limiting rules), keyed on source IP and applied to the gh-lite.tyk.sh route:
- A general throttle, e.g. more than 300 requests per 10 minutes per IP → Block for 1 minute.
- A stricter
/searchrule usinghttp.request.uri.path contains "/search", e.g. more than 20 requests per 1 minute per IP → Block for 1 minute.
Tune the thresholds from Cloudflare observability. These rules run before the Worker, so they also save Worker invocations during abuse.
Exempt verified crawlers. 300 requests per 10 minutes is 30 per minute, which search-engine crawlers exceed easily because they come from concentrated address ranges. Add not cf.client.bot to the rule expression, or raise the threshold, or the site will be throttled exactly when it is being indexed.
Normal login requests read:user, public_repo, user:follow, and notifications. Private access is a separate OAuth round trip requesting repo. A GitHub OAuth App is used because a GitHub App user token cannot access arbitrary accounts where that app is not installed.
Sessions are stateless AES-GCM envelopes in Secure, HttpOnly, SameSite=Lax cookies. Rotating SESSION_ENCRYPTION_KEY signs everyone out. Only public API data may enter Cloudflare Cache API; authenticated requests and rendered pages containing viewer state use no-store.
See the in-product /privacy page for the user-facing explanation.
