Remember, Young Padawan: DRY
One of the software development principles that we get taught very early in our dev careers is DRY - Don’t Repeat Yourself. It’s a good thing too, as there isn’t much worse than trying to crawl through a massive codebase trying to find all the copy-pasted instances of the same piece of logic.
When we first started with Vue (and later Nuxt) I wasn’t always sure where to put certain bits of code, like getting data from the server, or checking if the user is logged in. Enter: the topic of this mini series. I will start with a quick recap of what mechanisms are available in Vue/Nuxt landscapes, I’ll follow with examples of situations when each of these might be useful, I’ll point out places where we got it wrong so that you don’t have to, and summarise the whole thing with a little reference table.
One of the trickiest aspects was to reconcile how the situation varies between SSR and client-side, and there were a few cases we had to work out why things would work on refresh but not route change, or vice-versa. We sometimes got it wrong when various hooks/methods get called and, more importantly, when they don’t get called. The information is usually somewhere in the docs (plus, the documentation has massively improved in the last year or so) - but I think it’s nice to have it collected all in one place.
Recap: Vue lifecycle
Vue documentation has an excellent diagram showing the order/situations in which Vue component methods are called. Unfortunately, it does not clearly mention a few important things (as it’s related more to how Nuxt operates in universal mode, than to pure Vue).
- Only beforeCreate and created are called during SSR (as well as on the client). All the other methods (most importantly: mounted, which is one used quite often in examples) are only called on the client. So, if you have a piece of logic that needs to be executed during SSR, mounted (which, otherwise, is often a good place for some extra logic) is not a good place for this.
- beforeCreate does not have access to the components props/data because this (the component reference) is still undefined.
- created, does have access to this , including data and props, but does not have access to DOM. How does it matter? If you ever want to use e.g. this.$refs, they are not initialised at this point. They will only be processed (visible) in mounted. Which does not run in SSR.
Recap: Nuxt-specific tools
Note: many of the following methods accept Nuxt context as one of the parameters.
Plugins
Plugins are bits of code that get executed once or twice per visitor, before Vue.js app instance gets created. You can have plugin that’s executed on both server and client side (thus twice total), or only on one side. Nuxt has useful convention that any plugin called XXX.client.js get executed only on client side, and YYY.server.js is only in SSR. Additionally, Nuxt makes available an inject method that allows you to make shared code/functionality available in vue instances/components, Nuxt context, and/or VueX store. A popular plugin is Axios, which allows you to access a shared Axios instance e.g. via this.$axios. Similarly, you can create your own plugin and access it, e.g. via this.$eventBus.
Modules
A module code is executed on Nuxt startup (i.e. once during the lifetime of your Node.js server ). Modules extend nuxt functionality - for example they can automatically add and configure a plugin. It is NOT executed in browser/on each page, or even on the server for each client accessing your page. Therefore, modules are not a good place for any code that should be executed for each visitor. Unless, of course, your Nuxt module adds code into one of the hooks that do get executed for each visitor - but the module code itself would run just once, to initialise certain hooks.
nuxtServerInit in store/index.js
One of the first actions executed in SSR (only) is nuxtServerInit. It is executed just once for each visitor to your website (when they first navigate to your website, or when they hit refresh). It is a good place to put Axios calls to get some commonly used data and put it in store.
Middleware
Middleware is executed before rendering each page (before route is loaded), regardless if you’re on server- or client-side. You can have global middleware (configured in nuxt.config.js), or localised middleware, attached only to certain layouts and/or pages. It’s important to know that middleware is only executed once before render - i.e. on first hit to the page it will be executed in SSR only. On subsequent pages/routes it will be executed on the client only. It does not get called on both client and server for the same page.
Mixins
Mixins are extensions to components, pages, or layouts. They have access to the whole component that they are mixed into - so they can use this.$route, this.$store, and anything else you’d be able to call in the component. They are very useful to extract common functionality (including hooks like mounted) that for some reason cannot be extracted as standalone components. In simple terms, they behave in the same way as if you had copy-pasted the mixin code into each component where it’s used.
asyncData & fetch
Both asyncData and fetch methods are executed before the component is initialised and so do not have access to this. Both can be used to get some data from an API to populate the component. Both are *only executed for pages (NOT components). Both take Nuxt context as parameter. Both will be executed on server-side on first load, and on client side for subsequent route changes. (Note: there are some subtle caveats here as to when these are called or not which I’ll get into in a separate post)
- asyncData should return a promise, or use async/await - but in either case, the result returned will be integrated into data part of the component
- fetch , on the other hand, should be used for data intended for VueX store - it does not need to return anything and should instead commit to store any required data. It can use async/await.
Bonus: watch route
None of asyncData or fetch get triggered in some specific situations when only route params change. For this situation, you may need to watch the route to refresh data - or change your router configuration. More details in a separate post.
Official Nuxt documentation has a useful diagram showing the order in which things are called. Let’s go into a bit more details into what it means in relation to typical user-app interaction.
Example
The code for this post (and all the more detailed follow-ups in this series) can be found on Github.
In the next post (or a few) in this series, I’ll go through what exactly happens as the user navigates through the app, and will point out various techniques and gotchas related to the tools covered above.
Top comments (17)
Hi Lili, sounds like you have been doing a lot of Vue / Nuxt / SSR stuff so you may be the right person to ask. I'd really appreciate your quick 2-second take on this.
I am thinking of creating a site like dev.to for a different focus area (a dynamic site with user-generated content, not a static site hosted on netlify). I love how snappy dev.to site is, every page loads so fast.
My back-end is currently in Laravel 5.8, and I don't intend to change that because I've spent the last 3 yrs learning Laravel. However, I am trying to get to a SSR or server-side-rendered app (as against the slow client-server default app). I care about speed (just like dev.to) and SEO is important also because of UGC. Which of these would you recommend if starting with Laravel and vue right now?
What tech stack / architecture would you use? Appreciate your thoughts!
Hi KP!
Our page is actually also UGC so I know exactly your thoughts / concerns!
Let me start by saying that apart having heard the name, I know VERY little about Laravel - but I do know it's often used with Vue, and I'm assuming the capabilities are roughly equivalent to any other backend-server, i.e. amongst other things, you can expose REST APIs out of it.
If that's the case, then we actually went through all 3 options with OSBO :)
We started with 1 - in our case, it was Java Spring Boot webapp, and Vue was embedded within just by importing from CDN, and later we also hosted the libraries from webapp. This I would only recommend for a very, very light touch website. You lose ability to package your app in a sensible way (it will be slower as you can't easily to code splitting, or otherwise use webpack), you start having messy code, and managing dependencies can be a nightmare. No helpful resources, as not many people do it this way. Overall, I'd strongly recommend against route 1. The familiarity of your existing stack will be tempting, but in the long run, it's a waste of time IMO (except, as said, with very light/static usage of Vue).
We moved on to 2, that is, a separate (plain) Vue app in Node.js server and Java backend with REST APIs. This is a huge improvement. The pros are that you are already in the land of "common standard", plenty of resources, cleaner code. The main con is that you now have to run two services. At the moment we run things on GCP and that means managing two deployments, two services (and paying for them) etc. But there are ways to cut the cost, if that's a main concern (it will be for us soon). Other cons are that you still have quite a bit of integrating tools on your hands (e.g. VueX, Vue Router, Vuetify - or whichever components framework you use) and, most importantly, SSR. You are right that SEO hints at using SSR, especially for sites that are not super-simple (google bots manage to process a bit of JavaScript - but you don't want them to process ALL of your content, it didn't work for us very well). You actually can do SSR with plain Vue, but in our case, that seemed so complex that this was exactly the point when we moved from route 2 to route 3.
Route 3 is, IMO, the absolutely best for complex Vue projects. It comes with an awful lot "out-of-the-box", and once you understand the SSR mode, it's actually really easy to work with. It takes some time, and I'm trying to write this up as I didn't find as many resources in this topic (apart from simple tutorials) as I'd like to - but... yeah. From what you said, your app seems quite similar in terms of architectural needs to ours, and Nuxt works really, really well for us.
Take a quick look at my guide to VueJS post - it's written from Java devs perspective, but I think the explanation of various Nuxt modes can be helpful for you regardless.
Any other questions, feel free to ask! :)
H Lili, phenomenal answer, thanks for taking the time to write this. It really helps (and I havent found a half-decent explanation anywhere). Even the Nuxt introductory video (why use Nuxt?) at nuxtjs.org/ is wayyy too complicated for a beginner to understand.
I did take a look at your post on the VueJs ecosystem: dev.to/lilianaziolek/a-super-quick... which is phenomenal in it's own right.
If I want to create a complex social networking app (ala dev.to or Linkedin), it sounds like "universal mode" is the way to go for me....is that correct?
Yes, Laravel is a back-end framework with powerful capabilities like APIs and lots of powerful features and integrations.
I really appreciate your perspective.
You're very welcome :)
Yes, IMVHO in Vue ecosystem, for any more complex applications, Nuxt in universal mode is the best thing since sliced bread ;)
As for your other questions:
Yes, in route 3 (and 2 really) they will both have their own little worlds. In our case, since we haven't grown too big yet, for now it is a single git repo, with subfolders for java/python/vue, and then further subfolders for projects. You could also create completely separate git repos - that is down to you, how big your team is (if any), and personal preference to some degree.
I don't think I came across any significant guide for this purpose - and since I just started blogging, I don't have our journey documented in any sensible manner. :( The good news is that, unless you have loads of code already (we had...) the migration should be relatively straightforward as it will be mostly moving files around (Nuxt requires you to put things in certain places) - but should not be a complete re-write.
Nuxt has a good "starter" project, so I'd recommend kicking it off from there, there is a route or two as an example.
One concept that, IIRC, is new to Nuxt vs plain Vue is the concept of pages vs components. Basically, pages are what gets automatically translated to Vue Router routes (if you used router already). Components, on the other hand, are the smaller building blocks. So whatever .vue files you now have, if it's mapped to Vue router route you'd move and name appropriately into /pages directory, and the rest would go into components.
Also, if you're using VueX, then there is a standardised structure for organising modules, and I'd recommend sticking to this instead of manually configuring VueX.
Finally, in our case the biggest stumbling block when decoupling from 1st way (all-in-one) was authentication/authorisation. I am a strong believer in backend driven security (i.e. "never trust the client") and I wanted to keep using Spring Security for this purpose. That was one of the most frustrating aspects of integrating the two technologies and I do have a blog post in plans for this, too. So, if you want a good (risky) place to start, I'd probably start from login and some basic authenticated user page. Once you have this working, all else will seem trivial in comparison ;)
Overall, if you have any specific questions (how to do X), feel free to shout. I have loads of posts in plans, but if I know there is interest in specific topic, I may prioritize it over some other less interesting/better covered topics.
Lili, I am so appreciative of your response! From both your posts and your detailed responses, one can tell that you have a ton of experience. Thank you for sharing your knowledge and expertise.
I just installed the nuxt starter project. I also found myself nodding my head silently in agreement to your points about:
I'm already following your updates. If you follow me back we can connect / message each other. Love to chat further and help you with OSBO if possible. I'm good on the business side.
Hmmmm, I just took a look at your laravel-nuxt project and the project structure looks quite a bit different than my typical nuxt project (see one linked from this post as example). Is this what was generated by
yarn create nuxt-app XXX
?) I can't see any recent commits though so hopefully it's just because it's not pushed yet!Stay tuned, I'll put the auth post in line right after I finish this series.
Followed back, and happy to chat direct :) Would not mind if you have any hints on how to reach our target audience, haha! :)
Actually is the structure that different?
Compare the 'client' folder at github.com/cretueusebiu/laravel-nu...
with github.com/lilianaziolek/blog-exam...
Sounds good, look forward to reading more posts from you on this topic, and chatting 1:1 :)
Ah yes, client folder seems very similar. But it's inside of the other codebase, that's quite different. In my case spring boot app is in osbo/rest-api and nuxt is in osbo/nuxt - they are basically totally separate. That's what got me confused.
In this case, how would you typically build and deploy this to run? Do you just run two different scripts from the main folder, deploy two things and still end up with two separate services? Or does laravel end up hosting nuxt generated code?
Nuxt has "generate" option where you'd end up getting ready html/CSS/js to host, and if this code structure gives you that, and laravel then serves it, then it's not quite the same as option 3. And if you end up with two independent services, then whilst it can work from one folder, I'm not sure if I see the benefit of keeping it all in one project.
As said, I don't know much about laravel and how it's typically used with nuxt, so perhaps that's normal - but I just can't see a benefit of keeping these two protects blended into one if you're after the nuxt universal/two separate services route.
I can only speak to the development / local server, because that's all I have tested so far. Production might be more complicated, I'm not sure though.
To run this on my local / dev server, I basically need 2 commands, both run from the root laravel-nuxt/ folder:
php artisan serve ---> laravel starts listening on port 8000. Laravel development server started: 127.0.0.1:8000 and hosts the APIs in app/Http/Controllers: github.com/cretueusebiu/laravel-nu...
npm run dev --> this starts nuxt on localhost:3000.
You then go to localhost:3000 and it just works. Nuxt calls the Laravel APIs.
What do you think, given this description?
I'm not familiar with the nuxt "generate" option..so I am confused / curious what you think.
What is the benefit of the two separate services route?
Technically the 'client' folder could be external to the Laravel app (laravel-nuxt), and I have done exactly that in Laravel -> Vuejs SPA apps, but never with Nuxt. I just started with the codebase above. What is the drawback of having them all in one project?
ps. also left you a message
If you end up with two servers (Node.js running Nuxt/Vue on 3000 and Laravel server on 8000), then I think you are indeed on route 3 :)
Overall, you can keep it this way as it works, but there doesn't seem to be a requirement to keep it all in one folder. To a degree it's personal preference. I would/do keep things that run as separate processes and involve different tech in separate directories - just to make it easier for anybody looking into this code to see where the boundaries are (it also makes managing deployments easier for example). But, it's just a minor point, as long as you end up with these two servers running, all is fine and will work:)
Generate option: it's just an option in Nuxt to generate a static site. It will create a dist folder with everything (HTML/CSS/JS rather than .vue files) inside ready to be deployed.
That's good to know, Lili! I was worried that I was going down a wrong path! I agree with you, keeping the folders separate and managing the deployments separately makes a lot of sense. Right now, since it's just me and I don't have a team, I'm going to keep them all in one folder (though I may make the 'client' folder it's own git repo and the master repo include it as a git submodule: git-scm.com/book/en/v2/Git-Tools-S...)
One thing that is still confusing to me is why this repo has a router.js
github.com/cretueusebiu/laravel-nu...
I thought Nuxt automatically compiles the routes based on the files in the directory. Not sure if you have an idea what this is for...
Re: router.js - you are absolutely right, Nuxt will create router.js based on /pages folder structure (you can see this in sample project for this post too). Not sure why there is one in your source code... Maybe a leftover from another approach? Or maybe sth went wrong in code generation in Nuxt template? Either way, you should not need it, and I'm not sure what happens if you have it (will it override Nuxt, or just be ignored?).
Thanks Lili. Upon digging deeper this is what I found / filed: github.com/cretueusebiu/laravel-nu...
He is essentially overriding the default Nuxt functionality of auto-creating routes because he likes doing it the manual way. So yeah, I've asked him to remove it and he seems open to the idea.
Lili,
Great summary!
With Nuxt 2.12 (released in March 2020), the capabilities of the fetch method have changed (some would say improved), specifically the availability of
this
in the fetch method.I learned about it here: nuxtjs.org/blog/understanding-how-...). Might be a great excuse to revisit this list and update the post.
Thanks,
Ian
Hello Lili,
Recently I created Website using Nuxtjs, Everything is going fine after that i added dark mode in website and then the problem starting, I saved dark mode data in local storage and when i call it from default page it works but after some sec first show normal page and then it changed to dark mode.
Here is my code:
beforeMount() {
if (this.$ssrContext) return
this.$vuetify.theme.dark = localStorage.getItem('darkmode') === 'true'
},
After some time i know that beforeMount() is only working in client side that's why this happens.
I want to know what should i use to apply dark mode before render, In simple work i want to know alternative of beforeMount() in SSR
App Mode - Universal (Server Side Rendering)
Have you tried using cookie instead ?
You might find this helpful
github.com/microcipcip/cookie-univ...