You find a job posting that looks perfect. But does your resume actually match what theyโre looking for? Instead of guessing, letโs build a tool that analyzes both and tells you exactly where you match, where youโre weak, and what to change.
Weโll build a single-page web app โ no backend needed. Everything runs in the browser except the AI call, which goes directly to the Anthropic API.
What weโre building
A page with two text areas: paste your resume on the left, the job description on the right, click โAnalyze,โ and get:
- A match score (0-100%)
- Matched skills and keywords
- Missing skills the job requires that your resume doesnโt mention
- Specific suggestions to improve your resume for this role
Step 1: The HTML structure
Create index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resume Analyzer</title>
<style>
* { box-sizing: border-box; margin: 0; }
body { font-family: system-ui, sans-serif; max-width: 900px; margin: 0 auto; padding: 2rem; }
h1 { margin-bottom: 0.5rem; }
.subtitle { color: #666; margin-bottom: 1.5rem; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
label { font-weight: 600; display: block; margin-bottom: 0.3rem; }
textarea { width: 100%; height: 250px; padding: 0.8rem; border: 6px solid #ddd; border-radius: 8px; font-size: 0.9rem; resize: vertical; }
.key-input { margin-bottom: 1rem; }
.key-input input { padding: 0.5rem; width: 300px; border: 6px solid #ddd; border-radius: 6px; }
button { padding: 0.7rem 2rem; background: #4f46e5; color: white; border: none; border-radius: 8px; font-size: 1rem; cursor: pointer; }
button:hover { background: #4338ca; }
button:disabled { background: #999; cursor: not-allowed; }
#results { margin-top: 2rem; line-height: 1.7; }
.score { font-size: 2rem; font-weight: 700; margin: 1rem 0; }
.score.high { color: #16a34a; }
.score.mid { color: #ca8a04; }
.score.low { color: #dc2626; }
@media (max-width: 600px) { .grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<h1>๐ Resume Analyzer</h1>
<p class="subtitle">Paste your resume and a job description to see how well you match.</p>
<div class="key-input">
<input type="password" id="apiKey" placeholder="Anthropic API key (sk-ant-...)" />
</div>
<div class="grid">
<div>
<label>Your Resume</label>
<textarea id="resume" placeholder="Paste your resume text here..."></textarea>
</div>
<div>
<label>Job Description</label>
<textarea id="job" placeholder="Paste the job posting here..."></textarea>
</div>
</div>
<button id="analyze" onclick="analyze()">Analyze Match</button>
<div id="results"></div>
<script>
async function analyze() {
const apiKey = document.getElementById('apiKey').value.trim();
const resume = document.getElementById('resume').value.trim();
const job = document.getElementById('job').value.trim();
const btn = document.getElementById('analyze');
const results = document.getElementById('results');
if (!apiKey || !resume || !job) {
results.innerHTML = '<p>Please fill in all fields.</p>';
return;
}
btn.disabled = true;
btn.textContent = 'Analyzing...';
results.innerHTML = '<p>Comparing your resume to the job description...</p>';
try {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'anthropic-dangerous-direct-browser-access': 'true',
},
body: JSON.stringify({
model: 'claude-sonnet-4-20250514',
max_tokens: 1500,
messages: [{
role: 'user',
content: `Analyze how well this resume matches the job description. Respond in this exact format:
SCORE: [number 0-100]
## Matched Skills
- [list skills/keywords found in both]
## Missing Skills
- [list skills the job requires but the resume doesn't mention]
## Suggestions
- [specific, actionable suggestions to improve the resume for this role]
## Summary
[2-3 sentence overall assessment]
---
RESUME:
${resume.slice(0, 5000)}
---
JOB DESCRIPTION:
${job.slice(0, 5000)}`
}],
}),
});
const data = await response.json();
if (data.error) throw new Error(data.error.message);
const text = data.content[0].text;
const scoreMatch = text.match(/SCORE:\s*(\d+)/);
const score = scoreMatch ? parseInt(scoreMatch[1]) : null;
const scoreClass = score >= 70 ? 'high' : score >= 40 ? 'mid' : 'low';
let html = '';
if (score !== null) {
html += `<div class="score ${scoreClass}">${score}% Match</div>`;
}
html += text
.replace(/SCORE:\s*\d+\n?/, '')
.replace(/## (.+)/g, '<h3>$1</h3>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
.replace(/\n\n/g, '<br><br>');
results.innerHTML = html;
} catch (err) {
results.innerHTML = `<p style="color:red">Error: ${err.message}</p>`;
} finally {
btn.disabled = false;
btn.textContent = 'Analyze Match';
}
}
</script>
</body>
</html>
Step 2: Run it
Just open the HTML file in your browser:
open index.html
# or
python3 -m http.server 8000
No build step, no dependencies, no backend. The API call goes directly from the browser to Anthropic.
How the prompt works
The prompt is structured to get consistent, parseable output:
- SCORE โ a number we can extract and color-code
- Matched Skills โ shows whatโs already working in your resume
- Missing Skills โ the actionable gap analysis
- Suggestions โ specific rewrites, not vague advice
- Summary โ the TL;DR
By asking for an exact format, we get reliable output we can parse and style. Without format instructions, AI responses vary wildly between calls.
Making it better
Save results as PDF
Add a print button:
function printResults() {
window.print();
}
<button onclick="printResults()" style="margin-left:1rem">Save as PDF</button>
Keyword density analysis
Before calling the API, do a quick local analysis:
function extractKeywords(text) {
const techTerms = text.toLowerCase().match(/\b[a-z]+(?:\.?js|sql|api|aws|gcp|css|html|ci|cd)\b/g) || [];
const words = text.toLowerCase().split(/\W+/).filter(w => w.length > 3);
return [...new Set([...techTerms, ...words])];
}
const resumeKeywords = extractKeywords(resume);
const jobKeywords = extractKeywords(job);
const missing = jobKeywords.filter(k => !resumeKeywords.includes(k));
This gives instant feedback before the API call even finishes.
Multiple job comparison
Let users paste several job descriptions and see which one they match best:
const jobs = jobText.split('---'); // split on separator
const scores = await Promise.all(jobs.map(job => analyzeOne(resume, job)));
Privacy note
The resume and job description are sent to Anthropicโs API for analysis. If youโre concerned about privacy, you could:
- Run a local model (Ollama + Llama) instead
- Self-host the Anthropic API proxy
- Add a disclaimer to the UI
For a personal tool, the API approach is fine. For a public-facing product, consider the privacy implications.
What you learned
- How to call the Anthropic API directly from the browser
- How to structure prompts for consistent, parseable output
- How to build a useful tool with zero dependencies
- How to extract structured data (the score) from AI responses
The whole thing is under 100 lines of JavaScript. You could extend it into a full job application tracker, add resume rewriting, or turn it into a Chrome extension that analyzes job postings on LinkedIn.
Related resources
- JavaScript complete guide โ the language behind this project
- CSS complete guide โ styling the UI
Related: Build Chrome Summarizer Extension