One of the most common misunderstandings with Vue.js deals with how to define endpoints for backend services that are not resolvable during build time. In this post I’m going to describe how to define dynamic configurations like backend endpoints so they can be determined at runtime.

Vue.js Logo

Vue.js is a very popular Javascript framework for building user interfaces and single-page applications (SPAs). It’s fast, easy to use, and includes features like routing and state management that a lot of SPAs need.

Vue.js applications are typically designed to access backends through REST APIs whose endpoints are defined as environment variables in a project’s .env file. What many people don’t realize is that when you run npm run build to compile a Vue.js project, those environment variables get hardcoded into the production code output by a process called webpack. Unless you intend to rebuild the webpack bundles every time backend endpoints change then those endpoints should be dynamically loaded, instead.

Avoid this hardcode anti-pattern:

Lets say your Vue app accesses backend services from many different views and components. It makes sense to define backend endpoints as VUE_APP_ environment variables in .env so that they could be globally accessed using the process.env object. However, when you run npm run build webpack compiles each of those references into the production ready code under dist/. Now, your deployable package is effectively hardcoded to use the set of endpoints loaded from .env during build time. If users want to deploy your code with bindings to their own set of backend endpoints then they have to search for “VUE_APP_” variables in the minified code under dist/ and replace them with the values that point to their own backend services. Yuck!

You could automate this with things like AWS CloudFormation templates that completely automate production deployment but the routines for finding and replacing hardcoded backend configurations in production code would be kludgy and slow. More importantly, changing production code in this way is a security liability since it precludes you from adding Subresource Integrity (SRI) checks in webpack which allows browsers to verify that files they fetch are delivered without unexpected manipulation.

Define dynamic configurations under public/.

A far better way to resolve configurations during runtime is to save them under your public/ folder. Vue designates the public folder as the place to put static assets that should be simply copied to dist/ without going through webpack. This also allows you to access a runtime configuration file with an HTTP request just like you would access any other API.

For example, you could define backend endpoints in public/runtimeConfig.json then load that file using a fetch call in the main Javascript file that drives your Vue app (e.g. src/main.js), like this:

const getRuntimeConfig = async () => {
   const runtimeConfig = await fetch('/runtimeConfig.json');
   return await runtimeConfig.json()
}
getRuntimeConfig().then(function(json) {
  const awsconfig = {
    Auth: {
     region: json.AWS_REGION,
     userPoolId: json.USER_POOL_ID,
     userPoolWebClientId: json.USER_POOL_CLIENT_ID,
     identityPoolId: json.IDENTITY_POOL_ID
    },
    API: {
     endpoints: [
       {
         name: "mieElasticsearch",
         endpoint: json.ELASTICSEARCH_ENDPOINT,
         service: "es",
         region: json.AWS_REGION
       }
     ]
    }
  }
};
console.log("Runtime config: " + JSON.stringify(json))

Then you can distribute those runtime configurations globally across all your Vue components using Vue mixins, like this:

Vue.mixin({
    data() {
      return {
        // Distribute runtime configs into every Vue component
        ELASTICSEARCH_ENDPOINT: json.ELASTICSEARCH_ENDPOINT,
        DATAPLANE_API_ENDPOINT: json.DATAPLANE_API_ENDPOINT,
        DATAPLANE_BUCKET: json.DATAPLANE_BUCKET,
      }
    },
  });

By resolving runtime configurations from the public/ folder instead of .env, your application will deploy faster and can benefit from the security features provided by webpack’s Subresource Integrity (SRI) checks.

For more information about how I applied this technique to one of my own projects, see https://github.com/awslabs/aws-media-insights-engine/pull/147.

Please provide your feedback to this article by adding a comment to https://github.com/iandow/iandow.github.io/issues/18.