12 min read
Colocation is the practice of grouping related pieces of code together within a project's directory structure. Learn how to set it up using the Nuxt framework.
Nuxt provides the ability to create routes based on the directory structure of the /pages
folder within your web application. While it's very easy to setup the routes for your application quickly, it's also very easy to create an oversimplified file structure that doesn't scale well and leads to problems down the road.
These issues often go unnoticed at first but become apparent later as the project grows, making refactoring time-consuming and difficult. The solution is to create a file structure that remains clear and scalable from the start. This way, no matter how large the project grows, components have a designated place, allowing new features to be developed seamlessly without repeatedly restructuring the codebase.
Basic File Structure
When starting a new project, it can be tempting to quickly add pages in the way shown below.
We can start adding components and even organize them into folders to group related items.
- Icon
- IconVue.vue
- IconNuxt.vue
- Button.vue
- Header.vue
- index.vue
- blog
- index.vue
- [...slug].vue
This doesn't seem like a problem at the moment, and there might not be any apparent issues. However, as we start adding more components, it quickly becomes more difficult to manage.
To illustrate this a bit better, expand the components
folder and notice how quickly a small number of components begins to look unorganized and hard to read. In this example we have less than twenty components, imagine dealing with a hundred or more components.
- index.vue
- blog
- index.vue
- [...slug].vue
Take the Icon
directory, for example, which contains IconVue.vue
and IconNuxt.vue
. While grouping components into directories offers some organization, it introduces its own challenges. With a large number of components, we end up with many directories, which can bring us back to the same problem of searching through countless folders.
Another issue is that some components don't belong in any directory and we end up creating a directory just for that single component.
This approach also becomes problematic when dealing with generic directories that contain common UI elements like headers, footers, and buttons. As these directories grow, they can become unmanageably large once again.
Colocation
This issue can be addressed through colocation. Instead of maintaining a single, large components folder, we can organize components relevant to each page into dedicated, local components directories. In fact, the below file structure is exactly what Vercel uses for their production site.
This approach offers several key benefits:
- No need to search through the entire project to find relevant code.
- Easily add new features without disrupting the existing structure.
- Remove code cleanly without leaving remnants behind.
- Enable other developers to quickly focus on specific features without navigating the whole codebase.
Setup
You may have noticed there are now /components
directories in the /pages
folder. By default, Nuxt treats all directories inside /pages
as routes, which is not the behavior we want for these component folders. To resolve this, we can use the pages:extend
hook to exclude any routes that include components
in their path.
import type { NuxtPage } from 'nuxt/schema'
export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
const pagesToRemove: NuxtPage[] = []
pages.forEach((page) => {
if (page.path.includes('component')) {
pagesToRemove.push(page)
}
})
pagesToRemove.forEach(page => {
pages.splice(pages.indexOf(page), 1)
})
}
}
})
You can choose to use auto imports for components if you prefer. However, opting for manual imports gives you the advantage of clearly seeing where each component originates, as all imports are explicitly declared at the top of each file.
import type { NuxtPage } from 'nuxt/schema'
export default defineNuxtConfig({
hooks: {
'pages:extend'(pages) {
const pagesToRemove: NuxtPage[] = []
pages.forEach((page) => {
if (page.path.includes('component')) {
pagesToRemove.push(page)
}
})
pagesToRemove.forEach(page => {
pages.splice(pages.indexOf(page), 1)
})
}
},
components: [
{
path: '~/pages',
pattern: '**/components/**',
pathPrefix: false
},
'~/components'
]
})
components
array defines search priority. Nuxt checks ~/pages
with the **/components/**
pattern first, then falls back to the general ~/components
entry, ensuring specific matches take precedence.Conclusion
Using file colocation in Nuxt is a powerful organizational strategy that enhances both development efficiency and code maintainability. By grouping related components, and assets near the pages or features they belong to, you eliminate the need to traverse the entire project to locate relevant code.
This structure simplifies adding new features and ensures that removing code is clean and straightforward, minimizing the risk of leaving behind unused code. Additionally, colocation makes the codebase more intuitive for other developers, enabling them to quickly focus on specific features without having to navigate through the entire codebase.