Colocating components with Inertia pages
I like keeping page-specific components next to the page that uses them.
With transformComponentUsing, I can give each page an _page.vue entry
point without exposing that directory convention to my controllers.
The components around a page
Inertia applications commonly map component names such as users/index and users/show directly to files. When those pages grow, their smaller components often end up mixed together in a separate components directory:
- pages/users
- index.vue
- show.vue
- components/users
- activity.vue
- empty-state.vue
- profile.vue
- user-row.vue
Here, user-row.vue and empty-state.vue belong to the index page, while profile.vue and activity.vue belong to the show page. I could recreate the index and show hierarchy under components/users, but then the same page structure would need to be maintained in two places. Putting the components directly beside index.vue and show.vue avoids that duplication, but makes the page entry points less obvious.
This tradeoff is especially noticeable with Vue. A single-file component has one template and one default component, so extracting even a small page-specific component usually means creating another file. React, by comparison, can keep several local component functions in the same .tsx file.
Instead, I like to give each page entry point its own folder:
- pages/users/index
- _page.vue
- empty-state.vue
- user-row.vue
- pages/users/show
- _page.vue
- activity.vue
- profile.vue
The _page.vue file is the page itself; the directory is simply a boundary around it and the components it owns. The leading underscore also keeps the entry point above its supporting files in the editor’s file tree.
This makes ownership explicit. A component used by one page stays in that page’s directory, while a component used by several pages still belongs in a shared components directory. Opening a page directory reveals its entry point and supporting code, and moving or deleting the page takes that code with it.
Wiring it up in Laravel
With Inertia 3, Laravel only needs to know how a page name maps to its entry point. In the boot method of AppServiceProvider, I append /_page to every component name:
use Inertia\Inertia;
public function boot(): void
{
Inertia::transformComponentUsing(fn (string $name) => "{$name}/_page");
}
Controllers keep using the same logical page name:
return Inertia::render('users/index');
Before building the response, the adapter transforms users/index into users/index/_page and sends that name to the browser, where the frontend resolver loads pages/users/index/_page.vue. The /_page suffix never has to appear in a controller.
A small contribution to Inertia
I originally proposed this feature after configuring the same transform in Inertia’s frontend page resolver. My first implementation added a matching transform to the Laravel adapter.
During review, Pascal suggested applying the transform while rendering the response instead. That made the feature simpler: Laravel now sends the transformed component name to the frontend, so the separate client-side transform is unnecessary.
The final API is one line of application setup. More importantly, it lets the project structure describe which code belongs to a page without changing the component names used by the rest of the application.