Resources
Code analysis
Spot what makes a site heavier directly in its source code
Contents
Even before going live
The audit measures the site as it is served, but much of what makes it heavier can already be seen in the repository, a four-megabyte photo in public/ or a TTF font for example. The .grammage-source job therefore reads the repository itself, with no browser and no preview, and reports what will weigh on the pages before they go live, with the file, the line and the fix to apply.
grammage-source:
extends: .grammage-sourcegrammage source .
grammage source . --fail-on warningThe job never blocks the pipeline, because a finding about the code says what is likely to weigh, not what really weighs. --fail-on warning is for the team that prefers to block anyway. The analysis reads neither the dependencies, nor the build outputs, nor what .gitignore leaves out, nor the folders that never go live, docs, scripts, tests and fixtures. It needs no licence, and the API does not do it, since it has no access to your code.
What the analysis reports
| Rule | What it reports | The fix |
|---|---|---|
source-image-heavy | a raster image of more than 200 KB | compress it, resize it to its largest display size and serve it as AVIF or WebP |
source-image-legacy | a photo in PNG or JPEG, from 50 KB | convert it to WebP or AVIF, or let the image component of the framework do it |
source-image-oversized | an image more than 2,560 pixels wide | bring it down to 2,560 pixels at most and offer smaller widths through srcset |
source-font-format | a font in TTF, OTF or WOFF | convert it to WOFF2 and serve only that format |
source-font-heavy | a font of more than 100 KB | reduce it to the characters of the site, for example with pyftsubset |
source-autoplay | a video or a sound that autoplays | remove autoplay, show a still image and start playback on click |
source-infinite-animation | a CSS animation that runs endlessly | give it a finite number of iterations and respect prefers-reduced-motion |
source-eager-island | an Astro island with client:load | switch to client:visible or client:idle when it is not needed right away |
source-framework-image | a raw <img> in a Nuxt, Next.js or Astro project | use <NuxtImg>, next/image or <Image /> from astro:assets |
source-heavy-dependency | moment, the whole of lodash or jquery as a dependency | Intl.DateTimeFormat, functions imported one by one, or the native DOM |
source-server-compression | a Caddyfile without encode, an nginx.conf without gzip on | encode zstd gzip for Caddy, gzip on; and brotli on; for nginx |
source-server-cache | static files without a cache duration | Cache-Control: public, max-age=31536000, immutable on fingerprinted files |
Each finding arrives in the Code Quality widget of the merge request, annotated on the line of the file concerned, so the person who added the image or the dependency sees it while discussing it with their team, and not three months later.
Code that never runs, file by file
In production, unused JavaScript can only be seen per bundle, _nuxt/B5pp_0lr.js for example, which does not say which component or dependency to fix. When the CI job has the project build with its source maps at hand, Grammage attaches each byte that never runs to its original file, app/components/MapComponent.vue or node_modules/gsap, and the report ranks them from the heaviest to the lightest.
- Nuxt
- Vite
- Astro
- Next.js
Two things are needed for that. The build produces source maps without publishing them, which the hidden mode does very well, and the Grammage job receives this build as an artifact, built by the same pipeline as the preview, so that the files really match what is served.
export default defineNuxtConfig({
sourcemap: { client: 'hidden' },
});export default defineConfig({
build: { sourcemap: 'hidden' },
});export default defineConfig({
vite: { build: { sourcemap: 'hidden' } },
});module.exports = {
productionBrowserSourceMaps: true,
};grammage:
extends: .grammage
needs: [build, preview]| Framework | Setting |
|---|---|
| Nuxt | sourcemap: { client: 'hidden' } in nuxt.config.ts |
| Vite | build: { sourcemap: 'hidden' } in vite.config.ts |
| Astro | vite: { build: { sourcemap: 'hidden' } } in astro.config.mjs |
| Next.js | productionBrowserSourceMaps: true, then do not deploy the .map files |
Without source maps, the report simply keeps the detail per bundle.
From the measurement to the repository file
In CI, the audit job also reads the cloned repository, and links each offending file measured on the page to its file in the repository, an image public/images/hero.png served under /_nuxt/hero.Bx1aB2cD.png or through /_next/image for example. The finding is then annotated on the right file, and the grammage-agent.md brief gives a coding agent or a colleague the list of files to touch, the fix specific to the framework, the expected gain and the command that checks it is done.
See what your site really weighs
Grammage brings together in a single tool the audit in a real browser, the code analysis in the CI/CD and a signed result that anyone can verify.
Free trial, no commitment.
Try it for free