💡 Respect `pageExtensions` option in Next.js config
Suggest an idea for Knip
👋 @webpro! We use knip in a Next.js project and I’ve noticed one false-negative which is to do with how our app is setup. When pageExtensions option is set in next.config.ts, standard globs from the plugin are no longer valid:
https://github.com/webpro-nl/knip/blob/c3664373828751945ebad38a617135df6047dced/packages/knip/src/plugins/next/index.ts#L14-L23
We’ve got this in next.config.ts:
pageExtensions: ["page.tsx", "handler.ts"],
With this setting, the following files would be entry points:
pages/index.page.tsx
pages/about.page.tsx
pages/product/[slug].page.tsx
pages/ping.handler.ts
However, these files would not be entry points and should be reported, if unused:
pages/shared/logo.tsx
pages/about.page/map.tsx
pages/product/[slug].page/interactive-3d-model.tsx
pages/ping.handler/helpers.ts
At the moment, knip is unaware of pageExtensions so marks all files inside the pages folder as used.
I understand that pageExtensions may be a tricky thing to support, because essentially knip needs to run next.config.ts to get the right value. Perhaps, at least we could disable standard globs is pageExtensions is found anywhere in file. In the meantime, I’ll see if I can manually turn off the Next.js plugin to define custom globs. Open to other ideas too!
Knip could run next.config.ts (it does this for many plugins).
Feel free to take a stab at it, there are a few plugins that do something similar already:
- https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/nuxt/index.ts
- https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/eleventy/index.ts
So the idea is to implement resolveEntryPaths and return toProductionEntry inputs.
I'm not sure about potential issues with loading next.config.ts but in general this doesn't give issues (apart from users often not having environment variables set).
I see, interesting! I don’t think I’ll have capacity to submit a PR in the next couple of weeks, so it’s up for grabs. In the meantime, I’ve added a local pnpm patch (patches/knip.patch) and it seems to be working!
diff --git a/dist/plugins/next/index.js b/dist/plugins/next/index.js
index 268b0d10de39192d2dc4ef08fc09c4d668edd45d..60a1a569432bcc11ee4f0bf31bcc616c900bba4f 100644
--- a/dist/plugins/next/index.js
+++ b/dist/plugins/next/index.js
@@ -11,7 +11,10 @@ const productionEntryFilePatternsWithoutSrc = [
'app/{manifest,sitemap,robots}.{js,ts}',
'app/**/{icon,apple-icon}.{js,jsx,ts,tsx}',
'app/**/{opengraph,twitter}-image.{js,jsx,ts,tsx}',
- 'pages/**/*.{js,jsx,ts,tsx}',
+ // https://github.com/webpro-nl/knip/issues/960
+ "instrumentation.handler.ts",
+ "pages/**/*.handler.ts",
+ "pages/**/*.page.tsx",
];
const production = [
...productionEntryFilePatternsWithoutSrc,
Why not override next.entry in your Knip config instead of patching the package, if I may ask?
Oh I did not know about this option! Indeed, setting this in knip.config.ts for a workspace works like a charm!
next: {
entry: [
// https://github.com/webpro-nl/knip/issues/960
"src/instrumentation.handler.ts",
"src/pages/**/*.handler.ts",
"src/pages/**/*.page.tsx",
],
I believe that the issue is still relevant because automatic pageExtensions would save users from a footgun. But the workaround is much neater than I initially thought, thank you!
Great! And indeed the issue is still relevant, but it would require some work and testing as it requires to actually load (not just statically parse) next.config.ts, which might come with its own set of issues (I mean, you know about eslint.config.js...)
This is resolved by merging #1005.
Just found a remaining edge case: "instrumentation.handler.ts" is not being picked up as an entry point:
Unused files (1)
apps/frontend/src/instrumentation.handler.ts
Relevant source:
https://github.com/webpro-nl/knip/blob/b1ee77635954df6bd5328b65ad456c9ea1d95906/packages/knip/src/plugins/next/index.ts#L18-L34
I believe that '{instrumentation,instrumentation-client,middleware,proxy}.{js,ts}' should be returned from getEntryFilePatterns, WDYT?