a blog about Django & Web Development

Add Vue to your Django templates with Vite

You don't have to build a separate app to use Vue with Django

When you take a course in Vue, you’re normally taught how to build a Single Page Application (SPA). This is where you build an interactive user interface but it’s not a full-stack application on its own. You will need an API to supply data to your SPA. You can build that with Django.

When developers talk about using Vue with Django, they often talk about building a Vue SPA and a Django API, hosted on separate servers. This is also known as decoupled front and back ends.

While this approach provides familiarity for frontend developers, the decision between a Single Page App and the more traditional Multi Page App (MPA) is one that requires care. Even for new projects, SPAs have disadvantages including accessibility and JavaScript bloat, as described in Rich Harris’ compelling talk, Have SPAs Ruined the Web?

A common myth is that an SPA is necessary to use a front-end framework like Vue or React.

I am going to show you how to add Vue to a Django project without building a separate SPA. Instead, we will use Vue in the MPA style to enhance our Django templates.

The Django developer’s challenge is how to put Vue inside Django templates. After all, you can’t use Vue CLI and get started just as you would with a Vue SPA. You can just import the Vue library into main.js, but you won’t be able to use Single File Components (SFC), which are essential for organising your Vue code.

In order to use Vue with the benefits of a modern JavaScript development environment, we are going to use Vite.

Vite

Vite is a JavaScript build tool. It bundles your JavaScript to improve performance on live sites by reducing the number of network requests. It uses Rollup under the hood and is designed to handle the demands of JavaScript heavy projects.

For Django developers, the real benefit of Vite is its development server. The development server hosts static assets which can be imported into Django templates.

This means:

  • You can use Single File Components in your project
  • You can use TypeScript
  • If you make a change to your Vue components or CSS, you will see the update in your browser as soon as your source files are saved.

Vite means you get the full benefits of a JavaScript development environment without creating a separate codebase for your frontend.

I recently wrote a post on how you can set up Vite for your Django project. In that tutorial, I showed how Vite can be set up to serve JavaScript files to a Django template, in an environment where packages can be installed via npm.

In this tutorial, we are going to build on that to add Vue to a Django template.

I am going to show you how variables can be passed from Django views into Vue components.

Tutorial

I am going to show you how to integrate a Vue app with a Django template.

To keep things simple, I am going to have Vue render a message set in the Django view. This will teach you how to pass variables from the template into Vue components.

I am also going to set up a very basic counter component, defined in its own file.

As usual, I have provided some example code to refer to if you get stuck.

If you want to start from scratch, I recommend cloning the START branch and follow the instructions of my Vite tutorial.

You can also clone the results of the tutorial using the vite-demo branch. It already has Vite installed.

The finished code for this tutorial can be found in main branch.

Step 1: Add the Vue plugin

Install the Vue plugin using npm or your chosen JavaScript package manager.

npm install @vitejs/plugin-vue

This is necessary for Vite to work with Single File Components (files with the .vue extension).

Once installed, import the plugin to your vite.config.js file. Then add vue to the plugins section of the config. You may have to create the plugins section.

My vite.config.js now looks like this…

import vue from '@vitejs/plugin-vue'

const { resolve } = require('path');

module.exports = {
  plugins: [    
    vue(),
  ],
  root: resolve('./static/src'),
  base: '/static/',
  server: {
    host: '0.0.0.0',
    port: 3000,
    open: false,
    watch: {
      usePolling: true,
      disableGlobbing: false,
    },
  },
  resolve: {
    extensions: ['.js', '.json'],
  },
  build: {
    outDir: resolve('./static/dist'),
    assetsDir: '',
    manifest: true,
    emptyOutDir: true,
    target: 'es2015',
    rollupOptions: {
      input: {
        main: resolve('./static/src/js/main.js'),
      },
      output: {
        chunkFileNames: undefined,
      },
    },
  },
};

Step 2: Install Vue

Next install Vue…

npm install vue

Step 3: Create the Vue app

In static/src/js, create a file called App.vue. For now, the component will just render a message. In the next step, we will replace the message with one set in the Django view.

<template>
    <h1>{{message}}</h1>
</template>

<srcipt>
export default {
  data() {
    return {
      message: "Hi from App.vue",
    };
  },
};
</srcipt>

If you’re copying this snippet, please excuse the typos in the script tags, my website mistakes them for a cross-site scripting attack.

In main.js, we need to initialise the Vue app by adding the following code…

import '../css/styles.css';

import { createApp } from 'vue';

import App from './App.vue';

const app = createApp(App)

app.mount("#app") 

The snippet above creates a Vue application based on App.vue and renders it inside the HTML element with an ID of “app”.

Go to your browser to see the result. Don’t forget to start your Vite server by running npm run build.

My window now looks like this. The message defined in App.vue appears in the browser. The rest of the layout comes from the Django template index.html.

A screenshot demonstrating a message defined in a Vue component and rendered by Vue.

Try changing the message in App.vue and notice that the window updates immediately after the file is saved. No need to refresh the page.

When preparing this demo, I got an error because I forgot to restart the Vite server after adding the Vue plugin to the config options.

Vite renders error messages in the browser, just like you would in a Vue app created with Vue CLI.

A screenshot demonstrating that Vite displays error messages in the browser.

Step 4: Create a Counter

Next, I’m going to add a basic counter to demonstrate that Vue can be used in a Django template just as easily as an SPA.

First, create the counter component in a new file, Counter.vue.

// Counter.vue

<template>
    <button @click="count++">{{count}}</button>
</template>

<srcipt>
export default {
  data() {
    return {
      count: 0,
    };
  },
};
</srcipt>

Next, import the component into App.vue.

// App.vue

<srcipt setup>
import Counter from "./Counter.vue"
</srcipt>

<template>
    <h1>{{message}}</h1>
    <counter />
</template>

<srcipt>
export default {
  components: { Counter },
  data() {
    return {
      message: "Hi from App.vue!",
    };
  },
};
</srcipt>

The result is an interactive counter on our page…

A screenshot displaying the rendered counter component.

Step 5: Pass Variables from Django to Vue

Django passes data from views to the templates as context. Our next challenge is passing the context from the Django templates to the Vue components.

This is my Django view. It has two variables that I want to pass to Vue: a message and a value for the counter.

def index(request: HttpRequest) -> HttpResponse:

    context = {
        "django_message": "Hello from Django!",
        "vue_message": "This message has been passed to a Vue component from a Django view.",
        "initial_value": 1000
    }

    return render(request, "index.html", context)

There are a few ways to do this. If you’re interested in a comparison of methods, then I highly recommend Adam Johnson’s detailed guide on the safe ways to pass variables to your JavaScript.

We will do this by making use of json_script filters, which is one of his approved methods. These represent Python objects as JSON inside script tags.

In your template, you can reference your context like this…

{{ vue_message|json_script:"vue-message"}}

{{ initial_value|json_script:"initial-value"}}

This creates script tags with an ID (excuse the deliberate typo of “script” in the code snippet- my website doesn’t allow the correct spelling).

<scirpt id="vue-message" type="application/json">
"This message has been passed to a Vue component from a Django view."
</scirpt>

We can access the content of the scripts directly in our Vue components using the ID to identify the script element.

// App.vue
<scirpt>
export default {
  components: { Counter },
  data() {
    return {
      message: JSON.parse(document.getElementById("vue-message").textContent),
    };
  },
};
</scirpt>

And we can do the same in our counter component…

<scirpt>
export default {
  data() {
    return {
      count: JSON.parse(document.getElementById("initial-value").textContent),
    };
  },
};
</scirpt>

Result

Screenshot showing that Vue has rendered the variables from the Django view.

Conclusion

You can use Vite to use Vue alongside Django templates. Vite helps you create a modern JavaScript development environment without creating a separate codebase.

While it is possible to use Vue without Vite, Vite provides benefits such as:

  • Not having to refresh the page to see your changes. It updates quickly once the file is saved.
  • Error messages displayed in the browser
  • The ability to bundle static assets for production
  • Support for Single File Components (files with the .vue extension)
  • Support for TypeScript

Using the examples in this tutorial, you can set up Vue applications contained to a single page. Data can be passed from Django views into Vue components using json_script filters.

Related Posts