7 Frontend Architecture Lessons From Nuxt.js
Front-end architecture is a hot topic, and for good reason.
This has in turn led to the rise of frameworks and application patterns to manage that complexity.
While there is no single right choice to architecture, there are a number of patterns that have begun to emerge.
These are often encapsulated in starter templates or even full-on application frameworks.
One such application framework is Nuxt.js. Nuxt.js provides a higher-level application framework on top of Vue.js.
By using Nuxt.js, you opt in to a particular set of front-end architectural decisions. In this post, I'm going to go through 7 of those decisions and draw out some of the advantages of each.
At the end, whether you choose to use Nuxt.js or not, you can draw from these lessons for building your own applications.
One choice that Nuxt.js makes for you is that your application should be able to render the same pages and components on the server and the client.
It also means that logic that can only run on server (such as filesystem access) or client (such as libraries accessing
window) must be contained within special lifecycle hooks or the special 'no-ssr' wrapper component.
The benefit is that you gain some of the best properties of both server-side rendered and SPA applications - a fast time to first page view, while also being network efficient and having all of the latency and interactivity benefits if a SPA.
2. Prefetching Asynchronous Data
In order to realize the benefits of pre-rendering pages on the server, you need to make certain that your server-side rendering has all of the data it needs before rendering. This is trivial for static pages, but for dynamic applications that depend on API calls, you will need to make sure all critical data is fetched before the page is rendered and sent from the server.
Even for pure SPA applications, it can be helpful to have hooks that let you specify what data is necessary before a page is rendered, and what can be filled in afterwards.
In Nuxt, there are three distinct hooks provided specifically for this purpose:
asyncData. It is also possible to use middleware for this purpose.
Each of these hooks has different nuances and usecases, but the architectural pattern is clear: Provide mechanisms for prefetching any type of asynchronous data used in your application.
3. Deconstruct pages into Layouts, Pages, and Components
One of the beautiful things about component-based architectures is that one can conceive of everything as a component.
However, when translating that into a system that uses routing to create distinct pages, it is useful to add some more structure on top of that abstraction.
Nuxt does this by using the concepts of pages and layouts. A page corresponds to a route, and fits naturally with how we're used to thinking about the web. Each page can have a layout that it renders within, so layouts become a way of creating shared structure across pages.
These tools - pages and layouts - not only can be used to share template structure, but provide natural hooks for middleware or data prefetching. For example, an admin layout might not only show the admin navigation but also include middleware that checks that a user has admin permissions, redirecting if not.
4. Organize File Structure by Role
One of the first questions in any sort of application is how to organize the files. Nuxt takes a relatively simple approach while still creating structure.
Files are separated by role, with directories for
assets, and completely
I have found it also useful to add directories for
lib (aka other unassociated logic like API wrappers), but your mileage may vary.
5. Filesystem Based Routing
Another architectural pattern that Nuxt introduces is filesystem based routing. While less flexible than purely programatic routing, this approach brings a number of advantages.
First and foremost, it makes it easy for newcomers to the codebase to find exactly where to start when looking at a particular page. Trying to debug the
/login page? Take a look at
It also reduces the number of decisions you need to make by standardizing a route structure.
Most ideal for more static routes, the segment-based dynamic routing provides enough flexibility for most websites, and if you absolutely must have more complex routes you can always fall back to a completely custom router.
6. Decompose Your Vuex Store Into Modules
Vuex provides the ability to split your store into
modules, with (optionally namespaced) separate state objects, getters, mutations, and actions.
In Nuxt, this mode is recommended and default, with individual files in the
store directory automatically becoming namespaced Vuex modules.
While for simple applications it might be overkill, this is almost a pure win. Especially since Nuxt takes care of all the boilerplate of setting up a module-based store without you needing to worry about it.
7. Use Middleware To Share Logic Across Routes
Middleware is a very common approach to sharing functionality on the server side, allowing developers to layer on functionality that checks a request, performs some logic, and either adds some data or decides if a redirect is necessary.
Middleware can be applied at either the
layout or the
page level, making it ideal for things like checking authentication or authorization.
It also can be run in an async mode, letting it prefetch data similar to other async hooks. The only disadvantage of using it for this purpose is that later middleware can still result in a redirect, so expensive API calls should probably be saved for later in the lifecycle.
As we've covered, Nuxt makes a number of front-end architecture choices for you right out of the box.
This can dramatically speed up the process of building a complex Vue project.
They also make server side rendering a breeze, doing all of the configuration you might otherwise need to hand-tune.
I highly recommend trying Nuxt for your next Vue project, but even if you don't there is plenty to learn from the way they approach front-end architecture.