Skip to content

network-priority/priority-hints-codemod

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

priority-hints-codemod

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" and decoding="async" to non-critical images, plus fetchpriority="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 priority on the hero next/image, and annotate next/script tags that are missing a strategy.

Zero runtime dependencies. Pure Node (>= 18).

What it does

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.

Safety model

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 --backup to write a .bak copy 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, or decoding, the codemod does not overwrite it unless you pass --force. Contradictions it can't silently resolve (for example loading="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.

Install

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 link

There is nothing to npm install — the tool has zero dependencies.

Usage & options

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.

Framework support matrix

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:

The heuristics explained

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) matches hero|banner|masthead|cover|jumbotron|splash. The first qualifying image (up to --max-hero, default 1) is marked high priority. If a hero also has loading="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>, get loading="lazy", decoding="async" and fetchpriority="low".
  • Neutral. Any other non-hero image (typically further down the page) gets loading="lazy" and decoding="async", but no fetchpriority — the tool won't demote what it can't prove is off-screen.
  • One hero per file. It never marks more than --max-hero images high priority (default 1).
  • 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.

Examples

Full before/after files for every framework live in examples/. A few highlights:

HTML — before

<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>

HTML — after

<!-- (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>

Next.js — before / after

// 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 />

Nuxt — before / after

<!-- 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" />

Astro — before / after

// 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" />

What it will NOT do

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-hero images per file.
  • It does not overwrite attributes you set yourself (without --force).
  • It does not rewrite your image src, generate responsive srcset, 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 a strategy for you. See Choosing a next/script strategy.
  • It only auto-inserts preconnect for well-known font CDNs (Google Fonts), to avoid guessing wrong about arbitrary third parties.

Further reading

Contributing

Contributions are welcome. The codebase is small, dependency-free, and fully covered by node --test:

node --test

Please 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.

License

MIT © 2026 Network Priority

About

Codemod that adds fetchpriority and resource hints (preload/preconnect, lazy loading) across HTML, Next.js, Nuxt, and Astro templates.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors