Skip to content
Monitoring
Skill

/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

From plugin
grafana-skills
21349 skills
Install
$ npx -y skills add grafana/skills --skill plugin-bundle-size --agent claude-code

How 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.md
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
Read more
Ships withgrafana-skills

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.

Get the whole plugin

Other skills on grafana-skills.