Yes, here are 3 ways to create a multiple layout system with Vue 3

Futari Boy - developer & indie hacker
ITNEXT
Published in
6 min readApr 28, 2023

--

Layouts are the foundations of medium-to-large websites or apps.

Hear me out, let’s say you’re creating a web app with a home page, marketing pages and app pages.

  • You want the home page to have a unique layout
  • You want your marketing pages to have a sidebar or whatever
  • You want your app pages to have common things like alert messages, error messages, a specific header, nav, etc

You wouldn’t want to repeat all the work for each page right?

Unlike Nuxt, Vue 3 doesn’t have a native layout system, but don’t worry, I’m going to show you 3 simple ways to achieve that.

⚠️ If you want the best method in my opinion, you can skip to method 3, but if you want to become a better developer, read all the methods.

1. Importing Layouts as regular components to create a layout system

This is the easiest way to create a layout system, but it’s less flexible.

To simplify the explanation, let’s keep with my example above.

We have 5 pages :

  • Home (will have a specific layout)
  • About and Contact (will have the marketing layout)
  • Inside1 and Inside2 (will have the app layout)

We will create a “layouts” folder in which we will create 3 layout components containing a slot.

here’s how to create a multiple layout system with Vue 3
the layouts folder
here’s how to create a multiple layout system with Vue 3
an example of a layout component

Then you will simply import the layout you need in each of your page components like this:

first way to create a multiple layouts system with Vue 3
the home page component

This method has 2 major issues :

1. You need to import the layout in each page

Of course, you could make those components global, but you would still need to wrap your content manually each time.

2. The layout will be unmounted and destroyed each time the route changes, even if the next route uses the same layout

That has a small performance impact, but the real issue is that you will not be able to keep state from one route to another, even when they use the same layout.

But there are cases where this may be what you want.

The next method will leverage Vue Router and dynamic components.

2. Using Vue Router, the route’s meta property, and dynamic components to create a layout system

To avoid importing the layouts in each page, we can import them once in the Router instead, and give each route its associated layout.

Thanks to the meta property! https://router.vuejs.org/guide/advanced/meta.html#route-meta-fields

Here’s how to create a multiple layout system with Vue 3, Vue-router and the route meta property
The router and routes file

As shown here, we associated each layout component object directly with each route’s meta property. We imported all the layouts only once.

To avoid the layout from being unmounted and destroyed, we will put the layout above the page instead of within the page.

The App.vue file

To put the layout above the page, we created a dynamic component in the App.vue component.

A dynamic component can take either:

  • a component definition
  • a string of an HTML tag or a local/global component name.

In the template, we can access the current route with $route and on each route. We have access to its meta property, which means we have access to the previously set layout component object.

In case a route has no layout property on the meta object, we fallback to a string to use a DIV tag.

That’s it, we’re done.

We only imported the layouts once, we do not need to import or even wrap the layout in each page, and now, we will not have performance issues and we can keep state when navigating from 2 routes with the same layout.

So my Home page component now looks like this:

the home page component when using method 2

No need to wrap anything anymore; all is handled in App.vue around the <router-view> that represents each page when the route changes.

This method works in most use-cases, but it has 1 issue

The layout only changes when the route changes.

If you need to change the layout dynamically without changing the route, this method will not work.

There are only a few scenarios where you would want to change a layout dynamically but it could happen.

Examples:

  • It could be to show a locked page after a certain period of time
  • It could be to show an offline page
  • It could be to show an error page

Those examples could be achieved with a fullscreen modal system, but a modal is easy to delete from the DOM via the console.

The next method will leverage Vue’s reactivity and provide/inject system.

3. Using ShallowRef, Provide, Inject and Vue Router’s afterEach hook to create a layout system

To be able to change the layout from anywhere and not just when the route changes, we need to share the layout’s state across the app.

We could use Vuex or Pina for that, but let’s keep it simple here.

We will use Vue’s native reactivity system with the composition api.

Here are the steps:

  1. In App.vue, we will create a layout constant that contains a shallowRef to hold the current layout component
  2. In a separate file, we will create an object with key/value pairs that contains each layout’s name and it’s component
  3. In App.vue or elsewhere, we will listen to each route change with the router’s afterEach hook, to dynamically change the current layout
  4. In App.vue, we will provide the layout constant to it’s descendants, so that any component withing App.vue’s tree can inject the layout constant to change its value.
  5. In the routes, we will change each layout property on the meta to only a string containing the name of the layout to choose.

So here’s step 2, a file containing all the layouts and exposed as an object:

layouts.js the only file where we will import the layouts

Now we can also change the meta in the routes to only strings because they will be mapped to the object above:

the router and routes file

Now let’s glue all this together

App.vue using a dynamic component and providing the layout to it’s descendant

Why do we use a shallowRef instead of a ref?

A ref will make reactive the base value and all the nested values, but a shallowRef only makes the base value reactive.

Since we are storing a component, which is a complex object with a lot of nested values, it causes performance issues to use a ref.

It’s also unnecessary because we only need to know when the whole component has changed, not when a nested value has changed.

Now, how can we change the layout dynamically outside of the router you may ask? Well, anywhere!

Here’s an example of the Home that can change its layout with a click.

Home.vue using inject to access the layout reactive state

As you can see, we can now inject and access the layout’s state and change it to any component we want. Thanks to reactivity, it will dynamically change the component in App.vue.

As said earlier, you could do the same with Vuex or Pinia for the shared state, but for most scenarios, this is enough.

Well that’s it! You are now a Vue 3 architect 🎉

If you appreciated this story, don’t hesitate to clap & share this article.

Drop your email to get notified for crispy news on Vue, Nuxt & more: https://links.maisonfutari.com/get-notified

Mata ne またね

Yes, here are 3 ways to create a multiple layout system with Vue 3

--

--

Developer, nomad, SaaS & micro-SaaS founder 👋 #indiehacker 👨‍💻 #freelance 🇯🇵 #anime #japan