
(
Illustration )
Senior web developers Anton and Alexey continue the story about the difficult struggle with Nuxt. In the previous round of battle with this framework, they showed how to run a project on Nuxt so that everyone was happy. In the new article we will talk about the real application of the framework.We began to rewrite the project with a huge technical debt. The monthly audience was 6-7 million unique visitors, but the existing platform presented too many problems. Therefore, it was decided to send her to retire. Of course, performance was our greatest concern, but also did not want to squander on SEO.
')
After a couple of rounds of discussion, we decided not to rely on the traditional approach with server-side rendering only - but not to drive ourselves into the trap of client rendering. As a result, we began to build a solution based on
Nuxt.js.Good old Nuxt.js
We take already known to us
from the previous article "framework for the framework" based on
Vue.js for building universal client-server applications. In our case, the application works in conjunction with a rather complicated API (microservice artfulness, but about this some other time) and several layers of caching, it renders editable content and returns static content for lightning-fast performance. Great, right?
In fact, there is nothing new here. But what makes Nuxt.js interesting is the ability to quickly start a project with client-server rendering. Sometimes you need to go against the framework set by the framework. That is what we did.
No time to explain, build once, deploy many!
Somehow, the techlid approached us and puzzled: whenever we push changes into the repository, we need to make a build for each of the environments (dev-, stage- and prod-environments) separately. It was slow. But what is the difference between these builds? Yes, only in environment variables! And what he asked to do sounded logical and reasonable. But our first reaction was: O_o
The “Build once, deploy many” strategy makes sense in the software development world. But in the world of Javascript ... We have a whole battery of compilers, transpilers, pre- and post-processors, and also tests and linters. All this takes time to set up for each of the environments. In addition, there are many potential problems with leaking sensitive data (secrets, API keys, and so on, which can be stored in configurations).
And we started
Of course, we started with Google search. Then we talked to the Nuxt.js maintainers, but without much success. What to do - I had to invent a solution on my own, and not to copy from StackOverflow (this is the basis of our activity, isn't it?).
We will understand how Nuxt.js does it
Nuxt.js has a configuration file with the expected name nuxt.config.js. It is used to programmatically transfer configurations to an application:
const config = require('nuxt.config.js') const nuxt = new Nuxt(config)
It is possible to set the environment through env-variables. In general, a fairly common practice is to include the configuration file dynamically. Then it all goes to the definePlugin webpack and can be used on the client and server, like this:
process.env.propertyName
//or
context.env.propertyName.
These variables are baked during assembly, more information here:
Nuxt.js env page .
Notice the webpack? Yes, it means compiling, and this is not what we want.
Try otherwise
Understanding how Nuxt.js works means to us:
- we can no longer use env inside nuxt.config.js;
- any other dynamic variables (for example, inside head.meta) must be passed to the nuxt.config.js object in runtime.
The code in server / index.js is:
const config = require('../nuxt.config.js')
Change to:
Where utils / extendedNuxtConfig.js:
import config from 'config' import get from 'lodash/get'
An elephant, we did not notice
Well, we solved the problem of obtaining dynamic variables from outside the env properties of the configuration object in nuxt.config.js. But the original problem is still not solved.
It was suggested that some abstract sharedEnv.js would be used for:
- client - create an env.js file that will be loaded globally (window.env.envKey),
- server - imported into modules, where necessary,
- isomorphic code, something like
context.isClient? window.env [key]: global.sharedEnv [key].
Somehow not great. This abstraction would solve the most serious problem - the leakage of confidential data to the client application, since it would be necessary to add value consciously.
Vuex will help us
During the investigation of the problem, we noticed that the Vuex store is exported to a window object. This decision is forced to support the isomorphism of Nuxt, js. Vuex is a Flux inspired data warehouse specifically designed for Vue.js applications.
Well, why not use it for our common variables? This is a more organic approach - the data in the global repository suits us.
Let's start with server / utils / sharedEnv.js:
import config from 'config' const sharedEnv = {
The code above will be executed during server startup. Then add it to the Vuex repository:
const getSharedEnv = () => process.server ? require('~/server/utils/sharedEnv').default || {} : {}
We will rely on the fact that nuxtServerInit runs during, hm, server initialization. There is some difficulty: pay attention to the getSharedEnv method, a check for repeated executions on the server is added here.
What happened
Now we have obtained common variables that can be extracted in components like this:
this. $ store.state.sharedEnv.canonicalDomain
Victory!Oh, no. What about plugins?
To configure some plugins, environment variables are needed. And when we want to use them:
Vue.use (MyPlugin, {someEnvOption: 'There is no access to the vuex store'})
Great, race condition, Vue.js tries to initialize itself before Nuxt.js registers the sharedEnvobject in the Vuex repository.
Although the function that registers plugins provides access to a Context object containing a reference to the repository, sharedEnv is still empty. This is solved quite simply - let's make the plugin an async function and wait for the nuxtServerInit to execute:
import Vue from 'vue' import MyPlugin from 'my-plugin' export default async (context) => {
Now it's a victory.