Stuffing "React, Node.js, TypeScript, AWS, Docker, Kubernetes" into your resume summary does almost nothing for you. I know because I build resume software for a living, which means I've spent an unreasonable amount of time studying how resumes get parsed, stored, and queried on the other side of the apply button. The mechanics are closer to a database problem than a writing problem, and once you see the schema, the fix is obvious.
Here's the short version: an applicant tracking system (ATS) doesn't score your resume as a wall of text. It converts it into a structured record, and recruiters query that record. If your skills aren't attached to the right structure, you don't rank low. You don't appear in the result set at all.
Your resume becomes a database record, not a document
The moment your PDF hits an ATS, a parsing pipeline runs. It extracts the text layer, classifies sections (summary, experience, education, skills), then pulls out entities: employers, job titles, date ranges, degrees, and skills. Those entities get normalized against a skills taxonomy, so "JS", "Javascript", and "ECMAScript" usually collapse into one canonical skill.
What lands in the database looks roughly like this:
{
"work_history": [
{
"employer": "Acme Corp",
"title": "Senior Software Engineer",
"start": "2021-03",
"end": null,
"skills_in_context": ["React", "TypeScript", "GraphQL", "PostgreSQL"]
}
],
"skills": [
{
"name": "React",
"estimated_years": 4.5,
"last_used": "current",
"roles_mentioned_in": 2
}
]
}
Notice what's missing from that record: your summary paragraph. Most systems treat the summary as low-signal text. A skill that appears only there gets flagged as "claimed" with no duration, no recency, and no role attached. That's why keyword stuffing the top of your resume is wasted effort. You're writing to a field the query barely touches.
The scoring unit is skill + years + context
How does an ATS know you have "5+ years of React"? You never wrote that number anywhere. It computes it.
The parser attaches each skill to the job entries where it appears, then sums the date ranges of those jobs. Mention React in a bullet under a role you held from 2019 to 2023, and React inherits four years. Mention it again under your current role and the counter keeps running, with last_used: current as a bonus signal.
This is the single most useful thing to understand about applicant tracking system keywords: a keyword's value comes from what it's attached to. The same word carries different weight depending on where it lives.
- In a bullet under a dated job: full value. Duration, recency, and a title for context.
- In a skills section: the skill exists in your record, but with weak or no duration data.
- In the summary only: close to zero. Claimed, unverified, unattached.
So the dev who lists Kubernetes in a skills grid loses to the dev who wrote "migrated 30 services to Kubernetes" under a two-year role, even if the first one knows it better. The database can't see what you know. It can only see what you attached.
What a recruiter query actually looks like
The "ATS auto-rejected my resume" story is mostly a myth. In the pipelines I've seen, the system rarely rejects anyone on its own. What happens instead is quieter and worse: a recruiter runs a search, and you're not in the results.
A typical sourcing query looks something like this, syntax varying by vendor:
(title:"frontend engineer" OR title:"front end" OR title:"react developer")
AND skill:React AND skill:TypeScript
AND skill_years:React >= 4
AND applied_within: 14 days
Three things fall out of that query shape.
First, filters are binary. If your React years computed to 3.8 because the parser couldn't attach it to your first job, you're out, and no human ever saw your name. Second, results are ranked, usually by recency of the skill, how many distinct roles it appears in, and how closely your current title matches the search. Third, look at that last line: recruiters routinely scope searches to recent applicants because postings collect hundreds of resumes fast. Applying in the first days of a posting isn't superstition. It's a WHERE clause.
Structure your experience section for the parser
Everything above points to one conclusion: your experience section is the table that matters, and each bullet is a row. Write it that way.
Name the technology inside the bullet where you used it. Compare:
Before: "Worked on frontend features across the platform."
After: "Built the checkout flow in React and TypeScript, backed by a GraphQL layer over PostgreSQL, cutting checkout errors roughly in half."
The first bullet contributes nothing to your record. The second attaches four skills to a dated role, with an outcome a human recruiter can read in the same seven seconds.
Use canonical names. "Modern component frameworks" matches nothing. "React" matches React. When a tool has common variants, taxonomies usually handle it, but don't gamble on obscure ones: write "PostgreSQL" at least once even if you say "Postgres" elsewhere, and "Amazon Web Services (AWS)" on first use.
Keep each job entry structurally boring. Employer, title, and dates on predictable lines, consistent date formats, bullets underneath. Parsers are good now, but two-column layouts, tables, and icon-based skill bars still scramble text extraction order. Boring structure is a feature.
Treat the skills section as an index, not the content. It helps normalization and catches keywords you couldn't fit into bullets. It proves nothing on its own.
Export as PDF with a real text layer. A clean PDF preserves your formatting exactly and parses reliably. Skip screenshots-as-PDF and skip .docx unless a specific employer explicitly demands it.
The tedious part is that the right keywords change per posting. One React role leads with testing, another with performance, another with GraphQL, and matching your bullets to each one by hand takes an evening per application. I build Roleframe, so I'm biased, but this is exactly the problem it exists for: paste a job posting, and it extracts the keywords that role's filters will scan for, shows which ones your resume already covers in context, and rewrites the bullets with your approval. By hand or with a tool, the principle is the same. Match the posting's exact vocabulary inside dated experience, never in a stuffed summary.
What AI matching changes, and what it doesn't
Newer platforms layer semantic matching on top of keyword search, so "built a REST API in Express" can partially match a posting that says "Node.js backend services." That's real progress, and it softens the exact-vocabulary problem at the ranking stage.
It doesn't soften the filtering stage. Hard filters on computed years, titles, and normalized skills still run first, on the structured record, before any semantic ranking sees your resume. A fuzzy match can move you from position 40 to position 12. It cannot put you inside a result set a boolean filter already excluded you from.
So the playbook holds regardless of how smart the matching gets. Attach every skill you want credit for to a dated role with a concrete outcome. Mirror the posting's exact terms in those bullets. Apply while the posting is fresh, because recency is a filter too. You're not writing prose for a robot. You're writing rows for a query, and now you know the schema.

Top comments (0)