Conversation
Both the push and pull_request filters were limited to master, so commits pushed to a version branch such as v7 ran no CI. Using [master, 'v*'] covers future majors without a further edit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every export is or wraps a hook and the timers run on window.requestAnimationFrame, so the package is client-only. Marking the published bundles means importing it from a React server component fails at the module boundary rather than with a hooks error at render time. Rollup strips file-level directives while bundling, so the directive is added via output.banner on the CJS and ES configs. Terser classifies 'use client' as a non-standard directive and drops it from the minified CJS bundle unless compress.directives is disabled. The dist/index.js dev/prod shim carries it in source, above 'use strict'. UMD bundles are left alone: they are script-tag targets that never enter a server/client module graph. test/bundles.spec.ts asserts the directive on every published module, derived from the contents of dist rather than a hard-coded bundle list, so it holds across a change of build tool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing checked what npm actually publishes. publint audits the package.json/exports shape; arethetypeswrong verifies TypeScript consumers resolve types under node10, node16 (from CJS and ESM) and bundler resolution. Both run as a postbuild hook rather than alongside check:*, because check:* runs before build in the test script order and these need dist/ to exist. attw's --pack flag cannot be used directly here: it shells out to npm pack, which runs prepare, which runs build, whose postbuild hook runs attw again. scripts/attw.js packs with --ignore-scripts into a temp directory and hands attw the tarball, which breaks the loop. publint needs no equivalent workaround, as it builds its own file list without running lifecycle scripts. Current state passes: attw reports no problems in all four modes, and publint reports no errors. publint's warning about react-nprogress.esm.js being interpreted as CJS, and its suggestions for a type field and an exports map, are left standing for now; the exports map work resolves them. Warnings do not fail publint, so the gate is green today. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
React 19 ships no UMD build of its own, so the script-tag path already only worked with React 18 and earlier. Script-tag users can pin @tanem/react-nprogress@^6. Removes the two UMD rollup configs, their jest configs and test scripts, and the umd-dev/umd-prod examples. With UMD gone every remaining bundle externalises peers and dependencies alike, so getExternal collapses to a single predicate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
React's docs treat higher-order components as a legacy pattern, and the
HOC was the only consumer of hoist-non-react-statics, one of the two
runtime dependencies. The replacement is a line of consumer code:
const Enhanced = (props) => <Inner {...props} {...useNProgress(props)} />
Removes the source, its test, the hoc example and the README sections,
along with the higher-order-component and hoc keywords. The NProgress
render-props component stays: it is zero-dependency and the docs and
examples lean on it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the tsc -> compiled/ -> Rollup + Babel pipeline with a single tsdown build. Node ESM consumers were resolving to the CommonJS build because Node ignores pkg.module, so the package now ships an exports map with matching .d.mts/.d.cts pairs. main/module/types stay for webpack 4 and TypeScript node10 resolution. Output still targets es2019 so webpack 4 can parse it. @babel/runtime is gone, leaving the package with no runtime dependencies at all. The separate development and production CommonJS builds go too: they differed only by minification, which bundlers apply themselves, so process.env.NODE_ENV is no longer read at require time. The ES module bundle is now a .mjs file, which TypeScript always emits as ESM, so test:es runs Jest in ESM mode. src is published so the declaration maps resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adding StrictMode coverage surfaced two bugs. Mounted under
StrictMode, `useNProgress()` with `isAnimating: false` animated
straight to `progress: 1`, and `useNProgress({ isAnimating: true })`
reported `{ isFinished: true, progress: 0 }` instead of starting.
Both came from `useUpdateEffect`, whose first-mount ref is flipped
during render: StrictMode's double-invoked render makes the committed
render look like an update, so the completion effect fired on mount.
The hook is now a `{ phase, progress }` reducer over four phases,
driven by one effect that dispatches `start`/`complete` from the
`isAnimating` prop and one effect keyed on the phase that owns the
timeout. Transitions are gated on the phase rather than on whether
this is the first render, so a double-mount is a no-op.
That removes the `sideEffect`-in-state bridge, the three hooks
vendored from `react-use`, `createQueue` and four eslint
suppressions. Observable pacing is unchanged: every pre-existing spec
passes unedited and coverage stays at 100%.
Also adds React 19.2 to the test matrix as the current 19 latest
minor, alongside the existing 19.0 boundary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing prevented a size regression from landing, despite the README advertising a minzip badge. `npm run size` now runs inside `npm test`, after `build`, so local and CI runs agree. Uses `@size-limit/file` only, so the gate measures our own output rather than a resolved dependency tree, and budgets are gzip to match the badge metric. Actuals at this commit: ESM 997 B, CJS 1056 B. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Usage snippets mounted with `render` from `react-dom`, which React 19 removed, so the first thing a new user copied threw at runtime. They now show the component only, dropping the mount boilerplate. Adds a "When to Use This" section covering when a drop-in router-integrated bar is the better fit and when this package is, with a nav entry alongside the existing sections. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move the contents of .github/copilot-instructions.md to a root AGENTS.md, the cross-tool location read by Claude Code, Copilot, Cursor and Codex. Copilot code review, the last surface tied to the old path, gained root AGENTS.md support in June 2026, so the vendor-specific file is deleted rather than kept as a pointer. Update the rules the v7 work invalidated: the build is tsdown with a postbuild package check, coverage is collected by test:src alone and the bundle tests run against dist, and size-limit gates the bundle sizes. CLAUDE.md imports AGENTS.md. An import rather than a symlink, since symlinks need admin rights on Windows checkouts. Add a Contributing section to the README pointing at AGENTS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four options were bullets mixing name, type, default and prose, with no anchors to link at, and the API section repeated the Container/Bar/ Spinner boilerplate the Usage section had already shown. The options are now a summary table over one heading per option, so each has an anchor, and the API snippets show only the two entry points. Documents what the bullets left out: `minimum` is clamped against the first increment, which starts from 0.1, so the bar appears at max(0.1, minimum) and the option only bites above 0.1; `isAnimating` going false is what drives completion, `animationDuration` later. The return shape of `useNProgress` was previously only inferable from the snippets and now has a table of its own. Live Examples moves below the API as a two-column table instead of a wall of pipe-separated bullets. The nav line keeps every fragment it linked before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The examples pin `"@tanem/react-nprogress": "latest"` and nothing in CI touches them, so v7's packaging would first meet them the moment 7.0.0 publishes. Ran `npm run build && npm pack`, clean-installed the tarball into all eight examples in place of the registry pin, and drove each progress bar in a headless browser. Both Next examples were additionally checked under `next build && next start`. Every example starts, advances and completes with no console errors, and the CJS entry plus the `"use client"` directive hold up on the Next server. The `"latest"` pins are restored: CodeSandbox resolves the registry, not the tarball. Three things needed fixing along the way. The react-router example passed one `nodeRef`, held by `Home`, to every CSSTransition, and it was never attached to a DOM node, since `<Routes>` cannot take a ref. react-transition-group ends the transition on the next tick when the ref does not resolve, so the fade classes never applied and `onEntered` fired straight after `onEnter`: the bar completed 323ms after a click instead of running for the 1200ms the example configures. A `Fade` wrapper now owns a ref per `key` and renders the element it points at. Reproduced against the published 6.0.4 too, so it predates v7. Next rewrites `next-env.d.ts` on every run, with different contents for `next dev` and `next build`, and in a format prettier rejects. It is now gitignored and prettier-ignored in both Next examples, as create-next-app has it. `next build` also reconfigures `jsx` to `react-jsx`, which Next 16 requires, so both example tsconfigs now carry that instead of `preserve`. AGENTS.md records the tarball smoke-test procedure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`src/index.tsx` re-exported only `NProgress` and `useNProgress`, so the option shape and the hook's return shape reached the emitted declarations as local interfaces with no exported names. Anyone typing a wrapper around either entry point had to reach for `Parameters<typeof useNProgress>[0]` and `ReturnType<typeof useNProgress>`. `Options` is renamed to `NProgressOptions`, since `Options` is too generic an identifier to put in a consumer's import namespace. The return shape, previously an inline object type on `useNProgress` and spelled `ReturnType<typeof useNProgress>` in the render-prop signature, is now `NProgressState`. No MIGRATION.md entry: neither name was reachable from the package entry point before this commit. Both are type-only exports, so the runtime bundles are byte-identical: ESM 997 B, CJS 1.06 kB gzipped, unchanged. `test/bundles.spec.ts` already asserts facts about what the build emits, so the public export surface is checked there, over the declaration files it finds in `dist` rather than a hard-coded list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`app/about/page.tsx` and `app/forever/page.tsx` simulate a delayed API request with a bare `await new Promise(setTimeout)`. Since Next 15 an uncached `fetch()` opts a route out of static prerendering, so a real app's slow data fetch produces a slow navigation on every request. A `setTimeout` carries no such signal: Next treats it as slow work during prerendering, runs it once at build time and bakes the HTML in. Under `next build && next start` both routes built as `○ (Static)` and navigation to /forever settled in 75ms, so the progress bar jumped straight to 100% and faded instead of animating. Only `next dev` behaved as intended, which is where CodeSandbox runs the example, so this went unnoticed. `await connection()` immediately before the delay supplies the missing signal. Both routes now build as `ƒ (Dynamic)`, and navigation to /forever takes 3061ms in production with the bar visible for 3.4s. `connection()` is per-render and sits next to the simulated fetch, so it reads as part of the fake. The alternative, `export const dynamic = 'force-dynamic'`, is a route-level declaration a real app usually never writes: its data access opts the route in by itself. The pages-router example never had the problem, since `getServerSideProps` is inherently per-request. It builds the same two routes as `ƒ`, and both examples now trace the same progress curve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The React boundary verification snippet chained `cd test/react/<version>` into the jest run, so jest resolved `--config ./scripts/jest/config.src.js` against the version directory, which holds only `package.json` and `node_modules`. It failed with "Can't find a root directory while resolving a config file path". `scripts/test-react.js` scopes only the install to that directory and runs jest from the repo root, which is what `config.src.js` needs: it sets `rootDir` to the current working directory and `roots` to `<rootDir>/test`. The install now runs in a subshell so the jest line keeps the repo root. Checked against 16.14 and 17.0, 26 tests passing on each. Three gaps alongside it. Nothing recorded how a release triggers. `release.yml` runs on a Monday cron against master with no content gate, and `tanem-scripts release` throws on unlabelled and multi-labelled PRs while deriving the bump from the labels it finds. An agent opening an unlabelled PR breaks the next release with no local signal that it did. MIGRATION.md appeared only in a list of docs to keep current, so the rule that a breaking change earns an entry was left to inference. It now sits under Versioning, next to the semver rule it follows from. The coverage requirement read as machine-enforced. `config.src.js` sets no `coverageThreshold` and `codecov.yml` turns patch status off, so a drop below 100% fails nothing and has to be read off the report. `roadmap/` stays undocumented here: it is excluded via `.git/info/exclude` and is not present in a fresh clone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
Code reviewFound 1 issue:
Lines 134 to 137 in 63b1dcf react-nprogress/src/useNProgress.tsx Lines 52 to 62 in 63b1dcf 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
The README said changing `minimum` while the bar is animating restarts it from the first increment. The `start` reducer case returns the existing state unchanged when the phase is already `animating`, so progress holds instead. Describe what the code does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"and `animationDuration` later `isFinished` becomes `true`" was missing its unit, so the clause did not read as a sentence. State the delay the same way the `animationDuration` section does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v7.0.0. Fifteen commits, each with the detail in its own message; the consumer-facing changes are all in MIGRATION.md.
Breaking
withNProgressHOC. React's docs treat higher-order components as a legacy pattern, and it was the only consumer ofhoist-non-react-statics. The replacement is one line of consumer code.tsc→ Rollup + Babel pipeline with a single tsdown build, and adds anexportsmap. Output filenames change todist/react-nprogress.cjs/.mjswith matching.d.cts/.d.mts. Node ESM consumers were resolving to the CommonJS build because Node ignoresmodule; they now get the ES module build.main,moduleandtypesstay for webpack 4 and TypeScriptnode10resolution.process.env.NODE_ENVis no longer read at require time.@babel/runtime. The package now has no runtime dependencies, only peers.Fixes
useNProgress()withisAnimating: falseanimated straight toprogress: 1, anduseNProgress({ isAnimating: true })reported{ isFinished: true, progress: 0 }instead of starting. Both came fromuseUpdateEffect, whose first-mount ref is flipped during render, so StrictMode's double-invoked render made the committed render look like an update. The hook is now a{ phase, progress }reducer over four phases, gated on the phase rather than on whether this is the first render. That removes thesideEffect-in-state bridge, three hooks vendored fromreact-use,createQueueand four eslint suppressions. Observable pacing is unchanged: every pre-existing spec passes unedited.minimumrewound the bar, becausestartre-ran against an animation already in flight; on the published 6.0.4 the same change reset progress to 0 outright. ChanginganimationDuration, which the animating phase never reads, cancelled the pending trickle.startis now guarded on the phase the same waycompleteis, and the timer effect is split per phase so each depends only on the options its own phase reads. Three specs cover it."use client"to the published bundles. Every export is or wraps a hook and the timers run onwindow.requestAnimationFrame, so importing from a server component now fails at the module boundary rather than with a hooks error at render time.renderfromreact-dom, removed in React 19, so the first thing a new user copied threw at runtime.nodeRefto everyCSSTransition, so the bar completed 323ms after a click instead of running for the configured 1200ms. Predates v7: reproduced against the published 6.0.4.await new Promise(setTimeout)carries no dynamic signal, so Next ran it at build time and both routes shipped static; onlynext devbehaved as intended.Additions
NProgressOptionsandNProgressState. Both shapes previously reached the declarations as unnamed local interfaces, so wrapping either entry point meantParameters<typeof useNProgress>[0]. Type-only, so the runtime bundles are byte-identical.publintandarethetypeswrong, as a postbuild hook.test/bundles.spec.ts, run from its own Jest config after the build. Keeping it out ofconfig.src.jsleavesnpm run test:srcand the React matrix runnable on a clean checkout, and stops the bundle assertions repeating once per React version.size-limit, gzip to match the README badge. Actuals: ESM 1003 B, CJS 1063 B.v*branches. Both filters were limited tomaster, so v7 ran no CI at all.Docs
minimumis clamped against the first increment, andisAnimatinggoing false is what drives completion..github/copilot-instructions.mdto a rootAGENTS.md, read by Claude Code, Copilot, Cursor and Codex. Copilot code review gained rootAGENTS.mdsupport in June 2026, so the vendor-specific file is deleted rather than kept as a pointer.Test plan
npm testpasses: format and type checks, lint, build,publint,arethetypeswrong, size gate, then src, CJS, ESM, bundle and React-matrix specssrcstays at 100%arethetypeswrongreports no problems undernode10,node16(from CJS and ESM) andbundlerresolutionnpm packtarball of this branch and driven in a headless browser: each starts, advances and completes with no console errorsnext build && next start, confirming the CJS entry and the"use client"directive hold on the serverLabelled
breaking, so the Monday release cron derives a major bump.🤖 Generated with Claude Code