/plugin-bundle-size
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all
$ npx -y skills add grafana/skills --skill plugin-bundle-size --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/plugin-bundle-size
Context preview
The summary Claude sees to decide when to auto-load this skill.
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all
SKILL.md
plugin-bundle-size.SKILL.mdname: plugin-bundle-size
license: Apache-2.0
description:
Optimise Grafana app plugin bundle size using React.lazy, Suspense, and webpack code splitting.
Use when the user asks to reduce plugin bundle size, optimise module.js, add code splitting,
improve initial plugin load performance, split plugin chunks, lazy load plugin pages, or
help implement lazy loading in a Grafana app plugin. Triggers on phrases like "optimise plugin
bundle size", "module.js is too large", "plugin is slow to load", "code split the plugin",
"reduce initial JS payload", or "help me with Suspense in my plugin".
Grafana plugin bundle size optimisation
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all feature code loads on demand.
**Target:** ~15–25 JS chunks total. Fewer means too little splitting; far more (50+) means over-engineering.
Risk levels
Not all splitting opportunities carry the same risk. Apply them in this order:
| Level | What | Risk | Impact | |---|---|---|---| | **Safe** | `module.tsx` lazy wrappers (Priority 1) | Very low — no behaviour change | Highest — module.js drops 90%+ | | **Safe** | Route-level `lazy()` (Priority 2) | Low — each route is self-contained | High — one chunk per route | | **Safe** | Extension `lazy()` (Priority 3) | Low — extensions are isolated | Medium — independent chunk per extension | | **Moderate** | Component registries / tab panels (Priority 4) | Medium — verify Suspense placement | Medium — splits heavy pages further | | **Do not touch** | Vendor libraries (`@grafana/scenes`, `@reduxjs/toolkit`) | N/A | N/A — webpack splits these automatically | | **Do not touch** | Shared utility components (Markdown, Spinner) used across many files | High churn, many callsites | Low — already in shared vendor chunks |
When in doubt, stop after Priority 2. Routes alone typically reduce `module.js` by 95%+.
---
Step 1: Add bundle size CI reporting (recommended)
Add the `grafana/plugin-actions/bundle-size` action to get automatic bundle size comparison comments on every PR. This posts a table showing entry point size changes, file count diffs, and total bundle impact.
**Root-level plugins** (plugin at repo root):
# .github/workflows/bundle-size.yml
name: Bundle Size
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install and build
run: yarn install
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0**Subdirectory plugins** (e.g. `plugin/` in a monorepo):
The action's install step runs at the repo root and cannot find `yarn.lock` in a subdirectory. Work around this by installing deps yourself and symlinking to root:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ./plugin/.nvmrc
- name: Install dependencies
working-directory: ./plugin
run: yarn install
- name: Symlink plugin to root for bundle-size action
run: |
ln -s plugin/yarn.lock yarn.lock
ln -s plugin/package.json package.json
ln -s plugin/.yarnrc.yml .yarnrc.yml
ln -s plugin/node_modules node_modules
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0
with:
working-directory: ./plugin**How it works:** On push to main, builds and uploads a baseline artifact. On PRs, compares against it and posts a diff comment. Use `workflow_dispatch` to generate the first baseline.
**Reference:** [grafana-k8s-plugin workflow](https://github.com/grafana/grafana-k8s-plugin/blob/main/.github/workflows/grafana.yml)
---
Step 2: Detect plugin context
# Confirm this is an app plugin (type: "app" — datasource/panel plugins have different needs)
jq -r '"\(.id) — \(.type)"' src/plugin.json
# Locate the entry point
ls src/module.ts src/module.tsx 2>/dev/null
# Measure the current PRODUCTION bundle size BEFORE making any changes
# Dev builds are unminified and much larger — always measure production
yarn build 2>/dev/null || npm run build
echo "=== module.js ===" && ls -lah dist/module.js
echo "=== all JS chunks ===" && ls -lah dist/*.js | sort -k5 -rh | head -20
echo "=== chunk count ===" && ls dist/*.js | wc -l
Record the baseline. A pre-split plugin commonly has a `module.js` of 1–3 MB with no other JS chunks.
---
Step 3: Check and update create-plugin
The `@grafana/create-plugin` tool controls `.config/webpack/`, `.config/jest/`, and other build scaffolding. Updating it often unlocks faster SWC compilation and better chunk output.
cat .config/.cprc.json 2>/dev/null || grep '"@grafana/create-plugin"' package.json
npm view @grafana/create-plugin version
npx @grafana/create-plugin@latest update
After updating, review the diff (especially `.config/webpack/webpack.config.ts`) and run a test build. If the plugin has a top-level `webpack.config.ts` that `webpack-merge`s the base config, review the merge for conflicts.
---
Step 4: Analyse the codebase — find what to split
Do **not** start implementing until you have read all of these.
# Entry point — look for direct (non-lazy) imports of App, ConfigPage, e
Read more
name: plugin-bundle-size license: Apache-2.0 description: Optimise Grafana app plugin bundle size using React.lazy, Suspense, and webpack code splitting. Use when the user asks to reduce plugin bundle size, optimise module.js, add code splitting, improve initial plugin load performance, split plugin chunks, lazy load plugin pages, or help implement lazy loading in a Grafana app plugin. Triggers on phrases like "optimise plugin bundle size", "module.js is too large", "plugin is slow to load", "code split the plugin", "reduce initial JS payload", or "help me with Suspense in my plugin".
Grafana plugin bundle size optimisation
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all feature code loads on demand.
**Target:** ~15–25 JS chunks total. Fewer means too little splitting; far more (50+) means over-engineering.
Risk levels
Not all splitting opportunities carry the same risk. Apply them in this order:
| Level | What | Risk | Impact | |---|---|---|---| | **Safe** | `module.tsx` lazy wrappers (Priority 1) | Very low — no behaviour change | Highest — module.js drops 90%+ | | **Safe** | Route-level `lazy()` (Priority 2) | Low — each route is self-contained | High — one chunk per route | | **Safe** | Extension `lazy()` (Priority 3) | Low — extensions are isolated | Medium — independent chunk per extension | | **Moderate** | Component registries / tab panels (Priority 4) | Medium — verify Suspense placement | Medium — splits heavy pages further | | **Do not touch** | Vendor libraries (`@grafana/scenes`, `@reduxjs/toolkit`) | N/A | N/A — webpack splits these automatically | | **Do not touch** | Shared utility components (Markdown, Spinner) used across many files | High churn, many callsites | Low — already in shared vendor chunks |
When in doubt, stop after Priority 2. Routes alone typically reduce `module.js` by 95%+.
---
Step 1: Add bundle size CI reporting (recommended)
Add the `grafana/plugin-actions/bundle-size` action to get automatic bundle size comparison comments on every PR. This posts a table showing entry point size changes, file count diffs, and total bundle impact.
**Root-level plugins** (plugin at repo root):
# .github/workflows/bundle-size.yml
name: Bundle Size
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install and build
run: yarn install
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0**Subdirectory plugins** (e.g. `plugin/` in a monorepo):
The action's install step runs at the repo root and cannot find `yarn.lock` in a subdirectory. Work around this by installing deps yourself and symlinking to root:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ./plugin/.nvmrc
- name: Install dependencies
working-directory: ./plugin
run: yarn install
- name: Symlink plugin to root for bundle-size action
run: |
ln -s plugin/yarn.lock yarn.lock
ln -s plugin/package.json package.json
ln -s plugin/.yarnrc.yml .yarnrc.yml
ln -s plugin/node_modules node_modules
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0
with:
working-directory: ./plugin**How it works:** On push to main, builds and uploads a baseline artifact. On PRs, compares against it and posts a diff comment. Use `workflow_dispatch` to generate the first baseline.
**Reference:** [grafana-k8s-plugin workflow](https://github.com/grafana/grafana-k8s-plugin/blob/main/.github/workflows/grafana.yml)
---
Step 2: Detect plugin context
# Confirm this is an app plugin (type: "app" — datasource/panel plugins have different needs) jq -r '"\(.id) — \(.type)"' src/plugin.json # Locate the entry point ls src/module.ts src/module.tsx 2>/dev/null # Measure the current PRODUCTION bundle size BEFORE making any changes # Dev builds are unminified and much larger — always measure production yarn build 2>/dev/null || npm run build echo "=== module.js ===" && ls -lah dist/module.js echo "=== all JS chunks ===" && ls -lah dist/*.js | sort -k5 -rh | head -20 echo "=== chunk count ===" && ls dist/*.js | wc -l
Record the baseline. A pre-split plugin commonly has a `module.js` of 1–3 MB with no other JS chunks.
---
Step 3: Check and update create-plugin
The `@grafana/create-plugin` tool controls `.config/webpack/`, `.config/jest/`, and other build scaffolding. Updating it often unlocks faster SWC compilation and better chunk output.
cat .config/.cprc.json 2>/dev/null || grep '"@grafana/create-plugin"' package.json npm view @grafana/create-plugin version npx @grafana/create-plugin@latest update
After updating, review the diff (especially `.config/webpack/webpack.config.ts`) and run a test build. If the plugin has a top-level `webpack.config.ts` that `webpack-merge`s the base config, review the merge for conflicts.
---
Step 4: Analyse the codebase — find what to split
Do **not** start implementing until you have read all of these.
# Entry point — look for direct (non-lazy) imports of App, ConfigPage, e
Public skills for working with Grafana, Prometheus, Loki, Tempo, Pyroscope, k6, and the broader LGTM observability stack. Compatible with Claude Code, Cursor, Codex, and any tool supporting the Agent Skills open standard.
Repo: grafana/skills
Other skills on grafana-skills.
- /admission-control
Use when the user asks to "write a validator", "add validation", "implement admission control", "write a mutating webhook", "add a mutation handler", "validate incoming resources", "implement admission logic", "add admission webhooks", "write ingress validation", or asks how to
Open skill - /app-sdk-concepts
Use when starting any grafana-app-sdk work — scaffolding a Grafana app, initializing a Grafana App Platform app, picking a deployment mode (standalone operator / grafana/apps / frontend-only), wiring app-specific config, or onboarding to the SDK. Covers `grafana-app-sdk` CLI
Open skill - /cue-kind-definition
Author CUE kind definitions for grafana-app-sdk apps - schemas, versioning, field constraints, named type definitions, custom routes, and codegen configuration. Scaffolds kinds via `grafana-app-sdk project kind add`, writes spec/status schemas with type constraints (regex, enum,
Open skill - /reconciler-logic
Implement reconcilers and watchers for grafana-app-sdk apps — write `TypedReconciler[*MyKind]` reconcile functions, apply generation-based skip patterns, do conflict-safe status updates via `resource.UpdateObject`, configure `BasicReconcileOptions` (namespace, label/field
Open skill - /adaptive-metrics
Cut Grafana Cloud Metrics cost by shrinking active-series count with Adaptive Metrics aggregation rules — auto-recommendations from query history, custom exact/regex rules, label-drop config, unused-metric detection, and Alloy remote_write fallback. Use when investigating a high
Open skill - /admin
Manage Grafana Cloud accounts — organizations, stacks, RBAC roles and assignments, SSO/SAML/OAuth/GitHub auth, service accounts for CI/CD, user invites, team membership, and API-driven provisioning. Creates stacks via the Cloud API, mints service-account tokens, applies role
Open skill

