📜 ⬆️ ⬇️

How to configure the setting of Nuxt.js environment variables in runtime, or How to do everything differently and do not regret


( 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:



The code in server / index.js is:

 const config = require('../nuxt.config.js') 

Change to:

 //    Nuxt.js const config = require('./utils/extendedNuxtConfig.js').default 

Where utils / extendedNuxtConfig.js:

 import config from 'config' import get from 'lodash/get' //   Nuxt.js const defaultConfig = require('../../nuxt.config.js') //   const extendedConfig = {} //   Nuxt.js const nuxtConfig = { ...defaultConfig, ...extendedConfig } //     //       if (get(nuxtConfig, 'head.meta')) { nuxtConfig.head.meta.push({ hid: 'og:url', property: 'og:url', content: config.get('app.canonical_domain') }) } export default nuxtConfig 

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:


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' /** *  ,      *  ,     *  ,       * * @type {Object} */ const sharedEnv = { // ... canonicalDomain: config.get('app.canonical_domain'), } export default sharedEnv 

The code above will be executed during server startup. Then add it to the Vuex repository:

 /** *   . *        * .   * https://nuxtjs.org/guide/vuex-store/#the-nuxtserverinit-action * * @return {Object} Shared environment variables. */ const getSharedEnv = () => process.server ? require('~/server/utils/sharedEnv').default || {} : {} // ... export const state = () => ({ // ... sharedEnv: {} }) export const mutations = { // ... setSharedEnv (state, content) { state.sharedEnv = content } } export const actions = { nuxtServerInit ({ commit }) { if (process.server) { commit('setSharedEnv', getSharedEnv()) } } } 

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' /** *   MyPlugin . */ export default async (context) => { //  ,      sharedEnv await context.store.dispatch('nuxtServerInit', context) const env = { ...context.store.state.sharedEnv } Vue.use(MyPlugin, { option: env.someKey }) } 

Now it's a victory.

Source: https://habr.com/ru/post/429756/


All Articles