When developing Next.js apps I want to be able to include other files within the src/pages
directory besides the page React components or API routes. I'm perfectly fine putting components in src/components
and helper functions in src/utils
. But I like to co-locate other files with my pages and API routes.
Instead of creating a parallel __tests__
folder, I like putting my *.test.ts
or *.test.tsx
Jest unit tests next to whatever it is that I'm testing. That works great in src/components
, src/utils
or any other location outside of src/pages
. Similarly, I like putting my Storybook stories (*.stories.tsx
) next to my React components, including my page components (made possible by Storybook Addon Next Router).
But when I add these files within src/pages
or src/pages/api
and run next build
, I get the following error:
Build error occurred
Error: Build optimization failed: found page without a
React Component as default export in
pages/my/plan.stories
See https://nextjs.org/docs/messages/page-without-valid-component
for more info.
It's pretty nice that the error leads me to a place where I can find more info. I love when frameworks do this. But when I visit the Page Without Valid React Component page it gives a rather short explanation as to why the error ocurred, and some possible ways to fix it. The problem is that all the possible ways to fix it assume I somehow screwed up! π’
I mean I guess I did screw up by putting other files in the src/pages
folder, but it seems like something that should be allowed! Why is Next.js so picky about what files go in there? I guess since Next uses page-based routing it makes sense that it will have no way of interpreting page component files from test files or Storybook stories.
So for the longest I just accepted my fate and put my stories in a sibling src/page-stories
directory and just avoided writing unit tests, moving nearly everything into src/utils
. But it felt a bit clunky.
Then a coworker showed me a workaround! The pageExtensions
configuration allows us to alter the default list of valid page extensions (.tsx
, .ts
, .jsx
, and .js
). The config option is aimed at tools like @next/mdx
, which have files that end in .mdx
. But we can take advantage of it to let us put non-page files in the src/pages
folder as well. And it's right there in the docs.
We add special extensions for the page components and API routes to help Next.js distinguish them from all of the other files.
module.exports = {
pageExtensions: [
// `.page.tsx` for page components
'page.tsx',
// `.api.ts` for API routes
'api.ts',
],
}
Because I exclusively develop in TypeScript, I only have TypeScript extensions. So all of my pages end in .page.tsx
, such as src/pages/my/plan.page.tsx
. And any of my API routes end in .api.ts
, such as src/pages/api/client/auth.api.ts
. I give the API routes their own extension, just for added clarity. And because page components will always have JSX, there's no need for .page.ts
. Likewise, since API routes should never have JSX, there's no need for .api.tsx
.
Now I can have plan.page.tsx
, plan.test.tsx
and plan.stories.tsx
all in the same src/pages/my
folder! And the cool thing is if I want to add a utils.ts
or constants.ts
in the src/pages
folder, it will also work. Who knew something so simple could be so exciting? π
Keep in mind, though, that this change also has to apply to any Custom App (src/pages/_app.tsx
), Custom Document (src/pages/_document.tsx
), and Custom Error Pages (src/pages/404.tsx
& src/pages/500.tsx
) that we've defined. They'll all need the .pages.tsx
extension.
While I'd love for everyone to find my blog post to get their answer, the Page Without Valid React Component error page is slightly more discoverable. π So I submitted a pull request to add a link to the Custom Page Extensions page. As of writing, there are 194 open pull requests on the Next.js repo. So it may take a while to merge, if ever. Until then, I'll rake in the page views! π€£
If you've got any questions or suggestions, feel free to reach out to me on Twitter at @benmvp.
Keep learning my friends. π€