A dependency-free Node.js codemod that scans your framework templates and applies resource-priority best practices automatically — the same hints that move your Largest Contentful Paint image to the front of the browser's fetch queue and let the rest of the page load lazily.
It edits files in place (with a dry-run diff preview and optional backups) to:
- Add
fetchpriority="high"to the likely LCP / above-the-fold hero image (and the Next.js<Image priority>/ Nuxt / Astro equivalents). - Add
loading="lazy"anddecoding="async"to non-critical images, plusfetchpriority="low"to clearly below-the-fold images. - Add
<link rel="preload">for the hero image where a template exposes a document<head>. - Add
<link rel="preconnect">for detected third-party font origins (Google Fonts). - For Next.js: set
priorityon the heronext/image, and annotatenext/scripttags that are missing astrategy.
Zero runtime dependencies. Pure Node (>= 18).
Modern browsers assign every resource a fetch priority. Left to the defaults, the hero image often loads after less important assets, hurting LCP, while below-the-fold images compete for bandwidth they don't need yet. The right Priority Hints fix this — but applying them by hand across a large codebase is tedious and error-prone. This codemod does it mechanically and consistently, using the heuristics described below, and always shows you a diff first.
This tool is designed to never surprise you:
- Dry-run by default. Without
--write, it changes nothing — it prints a unified diff and a summary so you can review every edit. - Backups. Pass
--backupto write a.bakcopy of each file before it is modified. - Idempotent. Running it twice produces no further changes. It detects the hints it has already added and leaves them alone.
- Respects author intent. If an image already has an explicit
fetchpriority,loading, ordecoding, the codemod does not overwrite it unless you pass--force. Contradictions it can't silently resolve (for exampleloading="lazy"on a hero it is marking high priority) are surfaced as warnings. - Formatting-preserving. A custom tag tokenizer rewrites only the attributes it needs to; your quote style, whitespace, and surrounding code are left untouched. Everything it does not intend to change is preserved byte-for-byte.
This is a standalone repository, not published to npm. Clone it and run the bin directly, or link it globally:
git clone https://github.com/network-priority/priority-hints-codemod.git
cd priority-hints-codemod
# run it directly
node bin/priority-hints-codemod --help
# or link it onto your PATH as `priority-hints-codemod`
npm linkThere is nothing to npm install — the tool has zero dependencies.
priority-hints-codemod <paths/globs...> [options]| Option | Description |
|---|---|
--dry-run |
Preview a unified diff, change nothing (this is the default). |
--write |
Apply the changes to disk. |
--framework <name> |
auto | html | next | nuxt | astro (default auto). |
--max-hero <n> |
Max hero images to mark high priority per file (default 1). |
--no-lazy |
Do not add loading="lazy" to below-the-fold images. |
--no-annotate |
Do not insert explanatory code comments. |
--backup |
Write a .bak copy before modifying each file. |
--force |
Overwrite existing priority attributes (author intent is respected otherwise). |
--ignore <glob> |
Ignore matching files (repeatable). |
--json |
Emit a machine-readable JSON report. |
--no-color |
Disable ANSI colors. |
--version |
Print the version. |
--help |
Print help. |
The tool ships its own glob matcher and recursive walker (no dependencies)
supporting **, *, and ?. Pass files, directories (walked recursively for
known extensions), or glob patterns.
# Preview across a Next.js app
priority-hints-codemod "src/**/*.{jsx,tsx}"
# Apply to one HTML file
priority-hints-codemod public/index.html --write
# A whole Nuxt components directory, with backups
priority-hints-codemod components --framework nuxt --write --backup
# Ignore vendored and minified files
priority-hints-codemod src --ignore "**/vendor/**" --ignore "*.min.*"Exit code is 0 on success and non-zero on a usage or runtime error.
Each framework needs the correct attribute casing and mechanism —
getting these details right
is most of the value here. React uses camelCase fetchPriority; HTML, Vue and
Astro use lowercase fetchpriority; loading and decoding are lowercase
everywhere; and next/image uses a priority boolean prop instead of
fetchpriority altogether.
| Framework | Image tag(s) handled | High-priority mechanism | Lazy mechanism | Attribute casing |
|---|---|---|---|---|
HTML (.html) |
<img> |
fetchpriority="high" + <link rel="preload"> |
loading="lazy" decoding="async" |
lowercase |
Next.js (.jsx / .tsx) |
next/image <Image>, <img>, next/script |
<Image priority> (component); fetchPriority="high" (<img>) |
next/image is lazy by default; <img> gets loading="lazy" |
React camelCase fetchPriority; loading/decoding lowercase |
Nuxt / Vue (.vue) |
<NuxtImg> / <nuxt-img>, <img> |
fetchpriority="high" |
loading="lazy" decoding="async" |
lowercase |
Astro (.astro) |
astro:assets <Image />, <img> |
fetchpriority="high" + loading="eager" (overrides Astro's lazy default) |
loading="lazy" decoding="async" |
lowercase |
Framework-specific deep dives:
- Fixing Next.js LCP image priority
- Choosing a next/script strategy
- Tuning NuxtLink prefetch behavior
- Sequencing Astro client directives
Because full framework AST parsing is heavy, the codemod uses robust, well-tested tag-level transforms driven by these heuristics:
- Above the fold / hero. An image qualifies as the hero if it is the first
significant image in the file, or its
class/id/alt/src(or its nearest un-closed section) matcheshero|banner|masthead|cover|jumbotron|splash. The first qualifying image (up to--max-hero, default1) is marked high priority. If a hero also hasloading="lazy", that is contradictory — the codemod removes the lazy hint and flags it as a warning. - Below the fold. Images whose class matches
thumb|thumbnail|avatar|icon|sprite|badge|footer, or which live inside a<footer>/<aside>, getloading="lazy",decoding="async"andfetchpriority="low". - Neutral. Any other non-hero image (typically further down the page) gets
loading="lazy"anddecoding="async", but nofetchpriority— the tool won't demote what it can't prove is off-screen. - One hero per file. It never marks more than
--max-heroimages high priority (default1). - Idempotent and intent-respecting. Existing explicit attributes are left
alone unless you pass
--force, and a second run makes no changes.
<head> injection (preload + preconnect) only happens when a literal <head>
is present, and is skipped for Next.js — there, next/image and the metadata
API own the document head.
Full before/after files for every framework live in examples/.
A few highlights:
<header class="hero">
<img src="/img/hero.jpg" alt="A sweeping landscape" width="1600" height="900" />
</header>
<footer>
<img class="avatar" src="/img/author.png" alt="Author portrait" width="64" height="64" />
</footer><!-- (in <head>) -->
<link rel="preload" as="image" href="/img/hero.jpg" fetchpriority="high">
<header class="hero">
<!-- priority hint added by priority-hints-codemod — see network-priority.com -->
<img src="/img/hero.jpg" alt="A sweeping landscape" width="1600" height="900" fetchpriority="high" decoding="async" />
</header>
<footer>
<img class="avatar" src="/img/author.png" alt="Author portrait" width="64" height="64" loading="lazy" decoding="async" fetchpriority="low" />
</footer>// before
<Image src={heroPic} alt="Product hero" width={1280} height={720} loading="lazy" />
// after — priority prop, contradictory lazy removed
<Image src={heroPic} alt="Product hero" width={1280} height={720} priority /><!-- before -->
<NuxtImg src="/img/hero.jpg" alt="Marketing hero" width="1600" height="900" />
<!-- after -->
<NuxtImg src="/img/hero.jpg" alt="Marketing hero" width="1600" height="900" fetchpriority="high" decoding="async" />// before
<Image src={heroImage} alt="Hero illustration" width={1440} height={720} />
// after — fetchpriority plus explicit eager to beat Astro's lazy default
<Image src={heroImage} alt="Hero illustration" width={1440} height={720} fetchpriority="high" loading="eager" decoding="async" />This is a heuristic assist, not an oracle — always review the diff.
- It cannot truly know which image is your LCP element; it guesses the hero from structure and naming. Confirm with a real measurement tool such as Lighthouse or the web-performance workflow.
- It does not add a hint to more than
--max-heroimages per file. - It does not overwrite attributes you set yourself (without
--force). - It does not rewrite your image
src, generate responsivesrcset, resize or optimize images, or add width/height to prevent layout shift. - It does not preload web fonts (it only preconnects to detected font origins); the exact font file URL usually isn't available in the template.
- For
next/script, it only annotates a suggestion — it does not pick astrategyfor you. See Choosing a next/script strategy. - It only auto-inserts
preconnectfor well-known font CDNs (Google Fonts), to avoid guessing wrong about arbitrary third parties.
- Framework-specific loading strategies (overview)
- The fetchpriority attribute & Priority Hints
- Fixing Next.js LCP image priority
- Choosing a next/script strategy
- Tuning NuxtLink prefetch behavior
- Sequencing Astro client directives
Contributions are welcome. The codebase is small, dependency-free, and fully
covered by node --test:
node --testPlease add a fixture under test/fixtures/ (an input.* and
an expected.* pair) for any new transform, and make sure the idempotency tests
still pass. Keep the tool dependency-free and the transforms
formatting-preserving.
MIT © 2026 Network Priority