This guide is the technical companion to the User Guide. It is written for
forkers, self-hosters, and contributors who want to understand how
resume/resume.github.com is built, how to deploy it, what its security
boundary actually is, and where every behaviour lives in source. The
project is small — twenty-one source files, one JavaScript module of
roughly nineteen kilobytes, and a two-line Rack runner — but the surface
the JavaScript exercises against the GitHub REST API is non-trivial. This
document walks every piece.
The guide is pinned to commit f599075 (5 August 2016). The repository
has been dormant since that commit; the live site at resume.github.com
runs the same code.
Note
Because this repository contains no
package.json, no Gemfile, and no dependency manifest, every external library is loaded by the HTML templates from a CDN at runtime. Treat the rendered DOM, not the file tree, as the source of truth for which JavaScript runs in production.
1. Architecture Overview
resume.github.com is a client-only static web application. There is
no server-side application logic in the repository, and no first-party
backend in production. The deployable artefact is a directory of static
files that any HTTP server can serve verbatim.
What the system does
- Serve
index.htmlover HTTP (or HTTPS, in production via GitHub Pages) along withjs/githubresume.js, the two CSS files incss/, and the static images inimages/. - When the browser loads the page,
js/githubresume.jsparseswindow.location.searchto extract a username. - If a username is present, the script verifies the user has starred
resume/resume.github.com(synchronous AJAX), then issues parallel asynchronous JSONP calls toapi.github.comfor profile, repos, merged PRs, and orgs. - Each response is fed into a Mustache template loaded from
views/via AJAX; the rendered HTML is injected into the#resumehost element insideindex.html. - The page can be printed to PDF via the browser's native print
dialog;
css/print.csscontrols the print-only styling.
There is no persistence, no session, no authentication, and no server-side rendering. Every render is fresh, sourced live from the GitHub REST API.
High-level architecture
Visitor's browser
┌─────────────────────────────────────────────────────┐
│ │
│ index.html ──(loads)──► js/githubresume.js │
│ │ │ │
│ │ <div id="resume"> │ │
│ │ │ JSONP calls │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ api.github │ │
│ │ AJAX HTML │ .com │ │
│ ▼ └──────────────┘ │
│ views/*.html │ │
│ (Mustache) ▼ │
│ │ Profile / repos / │
│ └─► render ◄───── PRs / orgs JSON │
│ │
└────────────────────┬────────────────────────────────┘
│ HTTP GET
▼
resume.github.com
(static GitHub Pages)
Component inventory
| Component | Path | Role |
|---|---|---|
| Entry HTML | index.html |
Boots jQuery, Mustache, and js/githubresume.js; provides the #resume host element. |
| Front-end controller | js/githubresume.js |
Orchestrates URL parsing, opt-in check, GitHub API calls, and Mustache rendering. |
| Page styling | css/resume.css |
Screen layout, colours, typography. |
| Print styling | css/print.css |
@media print overrides for clean PDF output. |
| Landing template | views/index.html |
Default screen with a username input, shown when no query string is present. |
| User résumé template | views/resume.html |
Main résumé layout for data.type === 'User'. |
| Org résumé template | views/resumeOrgs.html |
Variant layout for data.type === 'Organization'. |
| Job row template | views/job.html |
Single-repository row in the Featured Projects block. |
| Org row template | views/org.html |
Single-organisation row in the Organisations block. |
| Contribution row template | views/contrib.html |
Single-row entry in the Open-source Contributions block. |
| Error template | views/error.html |
Generic error screen, bound to window's error event. |
| API-limit template | views/api_limit.html |
Shown when the starred-repos call returns HTTP 403. |
| Not-found template | views/not_found.html |
Shown when the starred-repos call returns HTTP 404. |
| Opt-out template | views/opt_out.html |
Shown when the user has not starred the repo. |
| Static assets | images/ |
favicon.png, loader.gif, low_contrast_linen.png, srt.png, ttab.png. |
| Rack runner | config.ru |
Two-line Rack file used to serve the directory locally with rackup. |
| Repo readme | README.markdown |
Repo-level introduction (the briefing reports it as not extracted, so do not assume specific content). |
Technology stack
| Layer | Technology | Notes |
|---|---|---|
| Markup | HTML5 | Templates use Mustache {{ }} placeholders. |
| Styling | CSS3 | Two stylesheets, no preprocessor. |
| Scripting | Vanilla JavaScript ES5 | No bundler, no transpilation. |
| DOM/AJAX library | jQuery | Loaded by the HTML templates from a CDN; not vendored in the repo. |
| Templating | Mustache.js | Mustache.to_html() rendered client-side. Loaded from a CDN. |
| Analytics | Google Analytics (UA-21222559-1) |
Classic ga.js loader; protocol switches based on page protocol. |
| Local serving | Rack / config.ru |
Optional — any static server suffices. |
| Hosting (production) | GitHub Pages | Inferred from the resume.github.com domain pattern; not encoded in the repo. |
Note
jQuery and Mustache are referenced by the HTML templates but are not committed to the repository. The exact versions used in production depend on whatever the templates
<script>-tag in. Treat any assertion about jQuery version as something you must verify by inspecting the renderedindex.htmlin the browser.
Data flow — the rendering pipeline
URL query string
│
▼
parse window.location.search
│
▼
$(document).ready
│
├─ no username ──► home() ──► AJAX views/index.html ──► #resume
│
└─ has username ──► run()
│
▼
github_user_starred_resume() (synchronous)
│
┌───────────┼───────────┬──────────────┐
│ 403 │ 404 │ false (no │ true
▼ ▼ ▼ star) ▼
api_limit.html not_found.html opt_out.html ── parallel calls ──
│
┌────────────────────┼──────────────────┐
▼ ▼ ▼
github_user github_user_repos github_user_orgs
│ │ │
│ github_user_issues │
▼ ▼ ▼
views/resume.html views/job.html views/org.html
views/resumeOrgs.html views/contrib.html
│ │ │
└────────────────────┴──────────────────┘
│
▼
#resume DOM
│
▼
Print button bound → window.print()
2. System Requirements
The project has minimal infrastructural requirements. They are different for visitors, for self-hosting on GitHub Pages, and for running locally.
For visitors (hosted site)
| Requirement | Detail |
|---|---|
| Browser | Any modern browser with JavaScript and cookies enabled. |
| Network | Outbound HTTPS to resume.github.com, api.github.com, and *.google-analytics.com. |
| GitHub account | Required for the subject of the résumé (the user being rendered must have starred the repo). Visitors do not need an account. |
For local development
| Requirement | Detail |
|---|---|
| OS | Anything (Linux, macOS, Windows, BSD). The project is platform-neutral static files. |
| HTTP server | Anything that serves static files: Ruby + Rack, Python's http.server, Node's http-server, nginx, Apache, Caddy. |
| Disk | Less than 1 MiB for the entire repository. |
| RAM | Negligible — the server only serves static bytes. |
| Internet | Required at runtime so the browser can reach api.github.com. |
For production hosting
| Requirement | Detail |
|---|---|
| Static-file host | GitHub Pages, S3 + CloudFront, Netlify, Vercel, Cloudflare Pages, or any equivalent. |
| TLS certificate | Strongly recommended (Google Analytics protocol switching depends on https). |
| DNS | An apex or subdomain pointing to the static host (the upstream uses resume.github.com). |
Important
Because the front-end calls
api.github.comdirectly from the browser, your hosting environment must allow third-party CORS. GitHub's public API explicitly allows cross-origin requests; no proxy is required. If you put the site behind a CSP, you must allowconnect-src https://api.github.com.
3. Configuration Reference
This section documents every configurable surface in the project. The list is short by design: the upstream code prefers hard-coded constants and JavaScript defaults over external configuration.
Configuration files
| File | Format | Location | Purpose |
|---|---|---|---|
config.ru |
Ruby (Rack) | Repo root | One-shot Rack runner used by rackup to serve the directory locally. Two lines: require 'rack' and a Rack::Directory mount. Not used in production. |
There is no package.json, no Gemfile, no .env, no YAML config, no
JSON config, and no environment variables. The AST extractor confirms
zero configuration keys in the repository.
Environment variables
The project reads no environment variables from the JavaScript runtime (the browser does not have access to host environment variables, and the front end does not consult any). If you self-host behind nginx, you may set environment variables for nginx itself; those are out of scope for this repository.
Hard-coded constants in js/githubresume.js
These are the only knobs an operator can turn without rewriting the
front-end. All live in js/githubresume.js. Edit the file directly.
| Constant | Default | Lines | Description | Required? |
|---|---|---|---|---|
trackerId |
'UA-21222559-1' |
top of file | Google Analytics property ID. The trailing if (trackerId) block injects the GA loader only when this is truthy. |
No — set to '' or remove to disable analytics. |
username |
undefined (set at runtime) |
top of file | Holds the parsed query-string user. Do not edit. | N/A |
urlParams |
{} |
top of file | Holds the parsed query string. Do not edit. | N/A |
maxItems |
5 |
inside run() |
Maximum number of featured projects rendered. Also used as the soft cap for organisations (shared itemCount). |
No |
maxLanguages |
9 |
inside run() |
Maximum number of language entries displayed. | No |
COEF_REPOS |
2 |
inside getUserStatus() |
Weight applied to public_repos. |
No |
COEF_GISTS |
0.25 |
inside getUserStatus() |
Weight applied to public_gists. |
No |
COEF_FOLLOWERS |
0.5 |
inside getUserStatus() |
Weight applied to followers. |
No |
COEF_FOLLOWING |
0.25 |
inside getUserStatus() |
Weight applied to following. |
No |
FIRST_STEP |
0 |
inside getUserStatus() |
Score threshold below which the Inactive label applies. | No |
SECOND_STEP |
5 |
inside getUserStatus() |
Upper bound for Newbie. | No |
THIRD_STEP |
20 |
inside getUserStatus() |
Upper bound for Regular. | No |
FOURTH_STEP |
50 |
inside getUserStatus() |
Upper bound for Advanced. | No |
FIFTH_STEP |
150 |
inside getUserStatus() |
Upper bound for Enthusiastic. Above this, Passionate. | No |
EXTRA_POINT_GAIN |
1 |
inside getUserStatus() |
Bonus added when the user is an early adopter, or when location, email, and blog are all set. Awarded twice (once for each condition). | No |
Tip
If you fork the project, the most common single change is to set
trackerIdto your own Google Analytics property (or to an empty string to disable analytics). The second most common change is to raisemaxItemsfrom5for users with extensive portfolios.
Runtime URL inputs
| Input | Source | Behaviour |
|---|---|---|
| First query-string key | window.location.search |
Used as the GitHub username. The parser walks ?key=value&key=value and assigns the first key it sees to urlParams[0], which is then read as username. |
Important
The query-string parser uses
urlParams[0] = d(e[1]);inside thewhile (e = r.exec(q))loop. Because the assignment is to index0on every iteration, only the last parameter parsed actually survives. In practice, with a single-key URL like?octocat, this works as intended; with multi-key URLs, behaviour is non-obvious.
Templates
The Mustache templates in views/ accept these named variables:
views/resume.html and views/resumeOrgs.html
| Variable | Source | Notes |
|---|---|---|
name |
data.name ?? data.login |
Display name. |
type |
data.type |
User or Organization. |
email |
data.email |
May be null. |
created_at |
data.created_at |
ISO timestamp. |
earlyAdopter |
0 or 1 |
Computed locally. |
location |
data.location |
May be null. |
gravatar_id |
data.gravatar_id |
Legacy, may be empty. |
avatar_url |
Computed | Rewritten for organisations to a Gravatar URL. |
repos |
data.public_repos |
Count. |
reposLabel |
repository / repositories |
Singular/plural form. |
followers |
data.followers |
Count. |
followersLabel |
follower / followers |
Singular/plural form. |
username |
URL parameter | Echoed for self-links. |
userStatus |
getUserStatus() |
One of six labels (see the User Guide). |
since |
Computed | Year, or 'this year' / 'last year'. |
resume_url |
window.location |
Echoed for QR codes / sharing. |
website |
data.blog, prefixed with http:// if missing scheme |
Optional. |
views/job.html
| Variable | Source |
|---|---|
name, date, language, description, homepage, username, watchers, forks, watchersLabel, forksLabel, last |
All derived from repo.info and rendering loop state. |
views/contrib.html
| Variable | Source |
|---|---|
count |
Number of merged PRs in the repo. |
username |
URL parameter. |
repoUrl |
Rewritten from repository_url. |
repoName |
Rewritten from repository_url. |
commitsUrl |
<repoUrl>/commits?author=<username>. |
views/org.html
| Variable | Source |
|---|---|
name |
org.info.name ?? org.info.login. |
now |
new Date().getFullYear(). |
last |
Set on the final iteration. |
4. Deployment & Scaling
The deployable artefact is the entire repository. There is nothing to build, transpile, or bundle.
Path A — GitHub Pages (matches upstream)
The upstream production site is resume.github.com. The conventional
GitHub Pages deploy is:
- Fork or clone the repository.
- Push the contents to a branch named
master(or whatever your GitHub Pages source is set to). - In the repository settings, enable Pages with the source set to
the chosen branch and the directory set to root (
/). - Configure your custom domain in Pages → Custom domain and set up
the DNS A or CNAME records as GitHub instructs. Apex domains use A
records to GitHub Pages IPs; subdomains use a CNAME to
<owner>.github.io. - Wait for the certificate to provision, then enable Enforce HTTPS.
Note
A
CNAMEfile at the repo root would be required for a custom domain on GitHub Pages, but the upstream commitf599075does not include one — the upstream relies on the implicit<owner>.github.iomapping for theresumeorganisation.
Path B — Object storage + CDN
Any static-file host works. The pattern is identical regardless of provider:
# Example: AWS S3 + CloudFront
aws s3 sync . s3://your-bucket/ --exclude '.git/*' --exclude 'README.markdown'
aws cloudfront create-invalidation \
--distribution-id E1XXXXXXX --paths '/*'
upload: ./index.html to s3://your-bucket/index.html
upload: ./js/githubresume.js to s3://your-bucket/js/githubresume.js
upload: ./css/resume.css to s3://your-bucket/css/resume.css
... (and so on for the remaining static files)
Path C — A single Docker container
The project does not ship a Dockerfile. If you want a containerised
version, point any nginx-based image at the repository directory:
FROM nginx:alpine
COPY . /usr/share/nginx/html
EXPOSE 80
docker build -t resume-github-com .
docker run --rm -p 8080:80 resume-github-com
Tip
For local development on Windows, the simplest path is
python -m http.serverfrom the repository root — no Docker, no Ruby, no Node required, and Python ships in modern Windows installations.
Scaling considerations
Because every request is for static bytes, scaling is trivial — any CDN in front of the storage will handle effectively unlimited concurrency. The interesting bottleneck is on the client side: the GitHub API rate limit caps how many concurrent visitors any single IP can serve.
| Bottleneck | Where | Mitigation |
|---|---|---|
| 60-req/hour limit (unauthenticated) | api.github.com |
None applied in the upstream. Forks add tokens at their own risk (see Security). |
| Search API quota | api.github.com/search/issues |
Same — the search endpoint has its own limits. |
| Synchronous starred-repos call | github_user_starred_resume() |
Blocks the page; large user star lists cascade additional 100-per-page calls until the repo is found or the list ends. |
| Mustache rendering | Browser CPU | Negligible for typical résumé sizes. |
5. Operations
There is essentially no operational surface for the upstream — once deployed to a static host, the site runs without maintenance. The operational tasks below apply to forks and self-hosters.
Routine maintenance checklist
- [ ] Rotate Google Analytics property if you are tracking your own deployment.
- [ ] Audit dependencies referenced by
index.htmland the templates (jQuery, Mustache CDNs). - [ ] Bump the year displayed by
views/org.htmlif your fork uses a static template instead of the runtimenowvalue. - [ ] Re-test against the GitHub API at least once a year — GitHub's REST API has continued to change since 2016 (legacy
dataenvelope, gravatar IDs, etc.). - [ ] Verify the site still loads correctly after browser updates that may deprecate JSONP.
Health checks
The site has no explicit health-check endpoint. The closest equivalent
is a request for / returning HTTP 200 with the literal string
<title>Resume</title> in the HTML, which can be verified with a curl
probe:
curl -sf https://your-host/ | grep -q '<title>Resume</title>' && echo OK || echo FAIL
OK
Logging
The upstream has no first-party logging. Three signals exist:
console.log(err)inside$(document).ready— only visible in the visitor's browser console. Surfaces unhandled errors from therun()pipeline.- Google Analytics — page views and (if you extend the GA block) custom events.
- HTTP access logs from your static host (GitHub Pages, S3, nginx).
Note
The error template (
views/error.html) is loaded by the globalwindowerrorevent handler bound at the very bottom ofjs/githubresume.js:$(window).bind('error', error);. This means any uncaught script error will replace the page contents with the error template; you will not see the original error UI. For debugging, disable thebind('error', error)line locally so the original page state survives.
Monitoring recommendations
- Synthetic check: probe
/once per hour from an external monitor. - Synthetic render check: load
https://your-host/?known-usernamein a headless browser weekly and assert the rendered DOM contains the expected username. - API health: monitor
https://api.github.com/users/octocatfrom your monitoring source. If GitHub returns 429s widely, the site will surface API-limit screens to many visitors regardless of the local state.
6. Performance
Page-load profile
| Phase | Approx. duration | Notes |
|---|---|---|
| HTML download | <100 ms (CDN) | index.html is small. |
| jQuery + Mustache | 100–300 ms | Depends on the CDN used by the templates. |
js/githubresume.js |
<50 ms parse | ~19 KB minified would be smaller, but the upstream commits the full unminified source. |
| Star check (synchronous) | 200–800 ms | First page of starred repos. Adds ~200 ms per additional 100 starred repos until the project repo is found. |
| Profile / repos / PRs / orgs | 200–600 ms each | Run in parallel, so the bottleneck is the slowest of the four. |
| Mustache render | <50 ms | Trivial DOM injection. |
The dominant variable is the synchronous star check. Users who star many repositories will experience longer page loads on first render.
Optimisation ideas (not in upstream)
- Replace the synchronous
$.ajax({ async: false })call with an asynchronous call and use a loader spinner instead of blocking the thread. - Cache the GitHub API responses in
localStoragekeyed by username + date, with a five-minute TTL. - Defer Google Analytics with
async/deferscript attributes. - Strip unused images from the deployment (
images/srt.png,images/ttab.pngare referenced by templates; verify before removing).
Tip
If you fork and want to make the page faster without rewriting the star-check, simply move the call to async and show the loader image from
images/loader.gifin the meantime. The synchronous design was a pragmatic 2016 choice that a modern browser flags as a performance warning.
7. Security
Security analysis for resume.github.com is unusually simple because
there is no backend, no authentication, and no first-party data store.
The threat surface is the GitHub REST API and the static files
themselves. This section covers all of it.
Trust boundary
The browser running js/githubresume.js is the trust boundary. All data
displayed comes from one of three sources:
- The static files served by your host — trusted.
- GitHub's REST API (
api.github.com) — trusted up to whatever public profile data GitHub returns for the named user. - The URL query string controlled by the visitor — untrusted.
What the project does NOT expose
- No personally-identifying information beyond what GitHub already exposes publicly via the REST API.
- No private repositories, private gists, private email addresses, or authenticated organisation details.
- No first-party cookies (Google Analytics drops its own).
- No POST endpoints. Every API call is GET, and the project does not ship a single line of mutation logic.
What the project DOES expose
-
The
?usernamequery parameter is a public input that any visitor can manipulate. The username flows directly into multiple URLs:url = 'https://api.github.com/users/' + username + '/repos?per_page=100&callback=?'GitHub's API performs its own input validation server-side, so a malicious username (e.g. URL-encoded special characters) will most likely return a 404 rather than reflect into anything dangerous.
-
The
usernamevalue is also injected into rendered HTML via Mustache:toString: function() { return '<a href="https://github.com/search?q=user%3A' + username + '&l=' + encodeURIComponent(this.name) + '">' + this.name + '</a>'; }Mustache by default escapes interpolated
{{ }}values. However, thetoString()callback above builds the link with string concatenation, so the username is not escaped at this point.
Warning
The string-concatenated
<a href="...?q=user%3A' + username + …'">path is a potential reflected-XSS vector ifusernameever contains a quote character. In practice, GitHub usernames are restricted to[A-Za-z0-9-], so this is a latent risk rather than an exploitable one — but a fork that loosens the input contract should add explicit escaping. Use$('<a/>').attr('href', …).text(name)instead of string concatenation.
GitHub API rate-limit and token handling
The upstream makes all GitHub API calls unauthenticated. There is no token in the source, no token-passing UI, and no localStorage key for one. Operators forking the project sometimes consider adding a personal-access token to raise the rate limit; this is strongly discouraged for any public deployment.
Caution
Embedding a personal-access token in client-side JavaScript is equivalent to publishing the token. Anyone who views the page source can extract and use it. Even a token scoped to
public_repois a real account credential and is logged in GitHub's audit trail. Do not do this. If you need authenticated rate limits, build a server-side proxy with a token stored in environment variables and forward only specific GET endpoints; do not expose the token to the browser.
Public-data-only assumption
The project assumes every input maps to a public GitHub user. There is no permission model beyond GitHub's own — if a user's profile is private, the API will return 404 and the site will show the Not found template. Private repositories, gists, and PRs are simply absent from the API responses, so the rendered résumé reflects only what GitHub already publishes openly.
Content Security Policy
There is no <meta http-equiv="Content-Security-Policy"> in the
repository. A reasonable hardened CSP for a fork would be:
default-src 'self';
script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com
https://www.google-analytics.com https://ssl.google-analytics.com;
style-src 'self' 'unsafe-inline';
img-src 'self' https://avatars.githubusercontent.com
https://secure.gravatar.com https://assets-cdn.github.com
https://www.google-analytics.com data:;
connect-src https://api.github.com https://www.google-analytics.com;
Important
The
script-srcdirective must explicitly allow whichever CDN your templates load jQuery and Mustache from. The upstream does not name a specific CDN — inspect the rendered HTML to discover the URLs in production.
Things you should NOT expose
- A token of any kind in client-side JS or in the static files.
- A custom backend that proxies the GitHub API without rate limiting, request-size limits, and abuse logging — you will become a botnet amplifier.
- Visitor IP addresses or query strings beyond what your hosting access
log already records. Do not add custom analytics that capture the
?usernameparameter for users who did not opt in.
Dependencies — supply-chain considerations
The repository commits no JavaScript dependencies. They are loaded at runtime by the HTML templates from CDNs. In 2026, that means:
- jQuery — pin to a known-good version on a trusted CDN, ideally with
Subresource Integrity (
integrity="sha384-..."). - Mustache — same.
https://www.google-analytics.com/ga.js— the classic GA loader, deprecated by Google. Replace withgtag.jsor remove entirely on any new deployment.
Caution
The upstream
ga.jsloader is loaded over both HTTP and HTTPS depending on the page protocol, with no Subresource Integrity. A compromise of the GA CDN or a downgrade attack on a non-HTTPS host would let an attacker run arbitrary JavaScript in the user's browser session. Always serve over HTTPS and consider removing the classic GA loader on modern forks.
8. Developer Internals
This section is the source-level reference. It covers structure, code patterns, function-by-function semantics, and a contributing guide.
Repository structure
resume.github.com/
├── README.markdown ── Repository introduction (briefing did not extract its content)
├── config.ru ── Two-line Rack runner for local dev
├── index.html ── Page shell, hosts <div id="resume">
├── css/
│ ├── print.css ── @media print overrides
│ └── resume.css ── Screen styling
├── images/
│ ├── favicon.png ── Browser tab icon
│ ├── loader.gif ── Spinner shown during async loads
│ ├── low_contrast_linen.png ── Background texture
│ ├── srt.png ── Asset referenced by template (verify before removing)
│ └── ttab.png ── Asset referenced by template (verify before removing)
├── js/
│ └── githubresume.js ── The single front-end module
└── views/
├── api_limit.html ── Shown on 403 from GitHub API
├── contrib.html ── Single contribution row template
├── error.html ── Generic error screen
├── index.html ── Default landing screen
├── job.html ── Single project row template
├── not_found.html ── Shown on 404 from GitHub API
├── opt_out.html ── Shown when subject has not starred the repo
├── org.html ── Single organisation row template
├── resume.html ── User résumé layout
└── resumeOrgs.html ── Organisation résumé layout
Note
views/index.htmland the top-levelindex.htmlare different files. The top-levelindex.htmlis the page shell that boots JavaScript. Theviews/index.htmlis the partial Mustache template that thehome()function loads into the#resumediv when no username is supplied. Don't confuse them.
Module reference — js/githubresume.js
The entire front end lives in this one ~19 KB module. It is structured
as a top-level IIFE that parses the URL, then a $(document).ready
handler that branches on whether a username is present. Below is every
function, in source order.
URL parser IIFE (top of file)
Anonymous immediately-invoked function expression (IIFE). Walks
window.location.search once and stores the last parsed key in
urlParams[0]. The variable is read as the username when
$(document).ready fires.
var urlParams = {};
(function () {
var e, a = /\+/g, r = /([^&=]+)=?([^&]*)/g,
d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
q = window.location.search.substring(1);
while (e = r.exec(q)) {
urlParams[0] = d(e[1]);
}
})();
Note
The assignment
urlParams[0] = d(e[1]);overwrites on every loop iteration. With a single key like?octocat, this is fine. With?a=1&b=2,urlParams[0]ends asb. This is a pre-existing behaviour and not a bug to fix without coordination — many forks rely on the single-key idiom.
error()
Loads views/error.html into the #resume element. Bound to the
global window error event at the bottom of the file:
$(window).bind('error', error);. Any unhandled JavaScript error
replaces the page contents with this template.
home()
Loads views/index.html into the #resume element. Called when no
username is supplied.
github_user(username, callback)
JSONP fetch of https://api.github.com/users/<username>?callback=?.
Calls back with the entire response object. Note that GitHub's legacy
JSONP responses wrap the user record in a data envelope; the
downstream consumer accesses data.data.created_at, data.data.name,
etc.
github_user_repos(username, callback, page_number, prev_data)
Recursive paginated JSONP fetch of
https://api.github.com/users/<username>/repos?per_page=100. Continues
to the next page when repos.data.length == 100, calls back with the
accumulated array when shorter. Pages are 1-indexed; the recursion
appends &page=<n> for pages > 1.
github_user_issues(username, callback, page_number, prev_data)
Recursive paginated JSONP fetch of
https://api.github.com/search/issues?q=type:pr+is:merged+author:<user>&per_page=100.
Note the recursion condition if (repos.data.total_count == 100) is
unusual — it tests the total count, not the page count, which
will recurse forever if the user has exactly 100 merged PRs total.
Warning
The merged-PRs paginator has a known bug: the stop condition
repos.data.total_count == 100should berepos.data.items.length == 100. Forks should fix this; the upstream behaviour is to recurse until the server returns an empty page or returns a different total. Verify the behaviour with users in the 100-PR range before relying on it.
github_user_orgs(username, callback)
Single JSONP fetch of https://api.github.com/users/<username>/orgs?callback=?.
No pagination.
github_user_starred_resume(username, page)
Synchronous AJAX (async: false) fetch of
https://api.github.com/users/<username>/starred?per_page=100&page=<n>.
This is the function that blocks the page during the opt-in check.
Behaviour:
- Iterates each repo in the response.
- Returns
trueimmediately if it seesrepo.full_name == "resume/resume.github.com". - Returns the string
'api_limit'on HTTP 403. - Returns the string
'not_found'on HTTP 404. - Recurses with
page + 1if the page returned exactly 100 entries. - Returns
falseonce the recursion exhausts all pages without finding the project repo.
run()
The orchestrator. Called from $(document).ready when a username is
present. Performs the synchronous star check, branches to api_limit.html,
not_found.html, or opt_out.html if the check returns a non-truthy
result, otherwise launches the four parallel JSONP calls
(github_user, github_user_repos, github_user_issues,
github_user_orgs) and wires their results into Mustache templates.
Inside run() is also the nested getUserStatus() function described
in §3.
The Mustache template chosen for the main résumé block depends on account type:
var resume = (data.type == 'User' ? 'views/resume.html' : 'views/resumeOrgs.html');
Google Analytics block (bottom of file)
Classic ga.js loader. Switches between
https://ssl.google-analytics.com/ga.js and
http://www.google-analytics.com/ga.js based on document.location.protocol.
Skipped entirely when trackerId is falsy.
Code patterns and conventions
| Pattern | Where it appears | Notes |
|---|---|---|
| IIFE for parser | URL parser | Common 2016-era idiom. |
| Recursive pagination via callback | github_user_repos, github_user_issues, github_user_starred_resume |
All three use the same shape: pass page_number and prev_data through, recurse on a length-based stop condition. |
Mustache to_html(template, view) |
All template rendering | Loaded as a global. |
$('#id').html(data) |
Template injection | Replaces the inner HTML of the host element. |
| Magic strings for state | 'api_limit', 'not_found' |
Returned from github_user_starred_resume and re-checked in run(). |
Known issues and tech debt
- Synchronous AJAX in
github_user_starred_resume— blocks the page; modern browsers print a deprecation warning. - Pagination bug in
github_user_issues— usestotal_countinstead of items length. - Shared
itemCountbetween projects and orgs — silently truncates organisations. urlParams[0]overwritten in a loop — only the last key survives.- Classic GA
ga.jsloader — long deprecated by Google in favour ofgtag.js/ GA4. - No package manifest — jQuery and Mustache versions are uncontrolled (loaded from whatever CDN the templates reference).
- No
CNAMEfile in the repo, despite production use of a custom domain. The upstream relies on the GitHub Pages default mapping. - No issue history — the briefing reports zero open or closed
issues fetched at this commit, so this guide cannot reference real
issue numbers. Operators should track new issues at
https://github.com/resume/resume.github.com/issuesgoing forward.
Testing strategy
The repository commits no test suite. The functional contract is small enough to manually verify with a handful of usernames. A reasonable manual test plan:
- [ ]
?(no username) → landing page renders. - [ ]
?<known-active-user>→ résumé renders with all five blocks. - [ ]
?<known-inactive-user>→ résumé renders with empty languages and Inactive status label. - [ ]
?<user-without-star>→ opt-out screen renders. - [ ]
?<nonexistent>→ not-found screen renders. - [ ]
?<known-org>→ organisation résumé renders with the org template. - [ ] Print preview from any rendered résumé → clean print layout.
For automated coverage on a fork, a Playwright or Cypress smoke test that exercises each branch is sufficient.
Contributing guide
The upstream has been quiet for years. If you intend to contribute back:
- Open an issue first describing the change. Without an issue, drive-by PRs may not be reviewed.
- Keep changes minimal. The project is intentionally tiny.
- Maintain ES5 compatibility unless you also bring a build step.
- Test by serving
rackup config.ruand exercising a handful of real usernames as listed above. - Update
README.markdownif a contributor-visible behaviour changes.
For private forks, treat the upstream as a starting point and feel free to modernise (ES modules, fetch instead of jQuery AJAX, gtag.js, etc.).
9. Integration Points
This section enumerates every external service the project touches and how to swap or extend each.
Outbound integrations
| Target | Endpoint | Auth | Purpose |
|---|---|---|---|
| GitHub REST API — users | https://api.github.com/users/<user> |
None (public) | Fetch profile. |
| GitHub REST API — repos | https://api.github.com/users/<user>/repos |
None (public) | Fetch non-fork repositories for the projects block and language histogram. |
| GitHub REST API — orgs | https://api.github.com/users/<user>/orgs |
None (public) | Fetch organisation memberships. |
| GitHub REST API — starred | https://api.github.com/users/<user>/starred |
None (public) | Opt-in check. |
| GitHub Search API — issues | https://api.github.com/search/issues?q=type:pr+is:merged+author:<user> |
None (public) | Fetch merged PRs. |
| Gravatar | https://secure.gravatar.com/avatar/<hash> |
None | Used for organisation avatars (extracted from the data.avatar_url regex match). |
| GitHub Emoji CDN | https://assets-cdn.github.com/images/icons/emoji/<name>.png |
None | Replaces :emoji: shortcodes in repo descriptions. |
| Google Analytics | https://ssl.google-analytics.com/ga.js or http://www.google-analytics.com/ga.js |
None (key is UA-21222559-1) |
Page-view tracking. |
| External CDNs | jQuery, Mustache | None | Loaded by the HTML templates; exact CDN inferred from rendered HTML. |
Replacing GitHub Analytics with GA4
To migrate to GA4, replace the entire trailing if (trackerId) block in
js/githubresume.js with a gtag.js snippet:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>
Or remove analytics entirely by deleting the block and the trackerId
variable.
Replacing the GitHub data source
The four github_user_* functions are the single integration boundary.
A fork that wants to render résumés from a different code-hosting
platform (GitLab, Gitea, etc.) needs to:
- Reimplement each
github_user_*function against the target API. - Reshape the response to match the field names the Mustache templates expect (see §3 for the variable list).
- Decide what the equivalent of "starring the repo" is on the target platform — without it, every fork has no opt-in mechanism.
Adding a server-side proxy
If you need authenticated rate limits without exposing a token, add a small server-side proxy that:
- Accepts only the GET endpoints listed in the table above.
- Forwards them to
api.github.comwith anAuthorization: token <pat>header read from a server-side environment variable. - Caches responses for ~5 minutes.
- Returns CORS headers permitting your origin only.
The front end then only needs to change the URL prefix from
https://api.github.com to your proxy's domain. The rest of
js/githubresume.js is unaffected.
Hosting integrations
The repository works on any static host. The most common production configurations are:
| Host | Configuration | Notes |
|---|---|---|
| GitHub Pages | Repo → Settings → Pages → Source = master branch |
Matches upstream. |
| Netlify | netlify.toml not required; default static deploy works |
Add a _redirects file if you want clean URLs. |
| Vercel | vercel.json not required |
Add cleanUrls: true for /?user to render index.html automatically. |
| Cloudflare Pages | Default static build | Effectively zero config. |
| nginx | try_files $uri $uri/ /index.html; |
Required so /?user correctly serves index.html. |
| Apache | DirectoryIndex index.html |
Default behaviour suffices for most installs. |
Note
The query-string-as-input pattern means the path always resolves to
/. There are no nested routes to map. Single-page-app fallback rules (try_files,cleanUrls) are not strictly required, but they protect against accidentally serving a 404 if a user trims the trailing/from the URL.
Browser integrations
The project depends on the browser's native APIs:
window.location.searchfor the query string.window.print()for the print button.document.titlefor the dynamic title.console.logfor the silent error sink.Datefor year computation.
No navigator features (geolocation, clipboard, service workers) are
used. The project will run in any browser with ES5 support.