Vue + Django: Combining Django Templates and Vue Single File Components without compromise

This video features Mike Hoolehan at DjangoCon US 2023 in Durham, North Carolina, USA.

Vue + Django: Combining Django Templates and Vue Single File Components without compromise
0:22:50
Published November 22, 2023
5,336 views

Django and Vue both have unique front-end strengths. Django’s context-driven template views offer rapid development directly from back-end model content. Vue’s modern reactive components provide powerful tools for building complex UIs within the rich JavaScript ecosystem.

Do we have to choose one or the other, or is there a way to combine both front-end frameworks without compromising their strengths?

Learn how to inject Vue SFCs directly into Django Templates, with no need for REST APIs, such that targeted areas can be enriched with Vue while retaining the flexibility and convenience of Django Templates in the remainder.

This talk was presented at: https://2023.djangocon.us/talks/vue-django-combining-django-templates-and-vue-single-file-components-without-compromise/

LINKS:
Follow Mike Hoolehan 👇

Follow DjangCon US 👇
https://fosstodon.org/@djangocon
https://twitter.com/djangocon

Follow DEFNA 👇
https://www.defna.org/

Video production by the presenter and DjangoCon US 2023 volunteers.

Summary

Django templates and Vue single-file components can coexist without forcing the application into a REST API or abandoning Django’s server-rendered pages. Mike Hoolehan demonstrates a rewards page that keeps page structure, authentication, middleware, and most rendering in Django, while Vue handles interactive elements through Pinia state, mounted components, and data attributes passed from template context. He also explains entry points, Vite development and production builds, and a workflow for dividing responsibilities between Django and Vue while retaining each tool’s normal development experience.

Key takeaways

  • Django templates and a build-based Vue application are not mutually exclusive in a multipage Django site.
  • Vue components can be mounted into Django-rendered container elements and receive props through HTML data attributes.
  • Pinia can hold shared page state and business logic while Vue components update interactive controls and derived values reactively.
  • Vite’s development server provides hot reload and debugging, while production builds can be emitted into Django’s static directory.
  • The integration lets teams keep Django’s middleware and authentication while using Vue and the broader JavaScript ecosystem only where interactivity requires it.

Summarised automatically from the transcript.

Transcript

3,670 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:21

So it's always exciting to start a new project. All the possibilities are open to you. And you have the opportunity to choose the technologies and the approaches that work best for your project. And so these decisions are made, which inform subsequent decisions, and these decisions together coalesce into a strategy. And it's all downhill from there. When we start a Django project, usually one of the first decisions we need to make is regarding our front-end stack. Again, there are numerous technologies, numerous approaches we can take. And that leads to a lot of discussion online. People seeking advice, people giving advice. Now, in these discussions, I often see developers in the same basic dilemma that I was in four years ago when I started a new project

1:15

I had a new website I was creating that would be mostly static, content-driven pages, based on my Django models. Now this is exactly what Django templates are so good at, with their rapid development, their convenience, and their tight coupling to Django mechanisms. Now I also had areas of higher interactivity in my site, with more dynamic behaviors, both in my front-facing side and on my administrative interface. Now for these I wanted the power, the node ecosystem, the development experience provided by a full-weight JavaScript framework, something like Vue or React.

2:01

In short, I wanted the best of both front ends, JavaScript and templates, and the flexibility to be able to use each when and where they're most effective for my project. Now, at the time, I was under the impression that I couldn't have both. At least not without significant compromise. After all, using a uh build-based JavaScript framework means a REST backend API. And if I were using a REST API, uh that means we're going to bypass the uh Django front end, which means no Django templates. On the other hand,

2:47

if I was using a uh if I wanted to stay with Django templates, that means I would get my interactivity from a script-based JavaScript framework, HTMX or Alpine perhaps. And uh that's because a build-based JavaScript framework just doesn't work well in this multi-page architecture uh that's required by Django templates. So I had to choose one or the other. Give up the other. Or so I thought Well, long story short, I did find a way to combine Django templates and Vue in a way that I felt deserved the best of both front ends. In fact, I was so pleased with my approach that I wrote an article entitled, fittingly enough, View plus Django, the best of both front ends.

3:35

But I still see in many online discussions many developers that still feel that for all practical purposes, templates and view are mutually exclusive. So I'm here today to continue my quest to dispel this myth. Now also view has changed quite a lot in the four years since I've written since I wrote my article. The build system has changed from Webpack to VET. State management system has changed from Vuex to Penia, and there's a new streamlined composition API for building components. Now luckily I feel all these changes make it easier than ever to integrate Vue and Django. So today I'll present my updated approach, which includes the latest updates to Vue.

4:22

And uh well this will be a fast introduction though. I apologize for that, but I will leave you with an updated article that has everything I've uh goes in more detail than everything I've discussed today. I also have some resources for you to bootstrap your own project. But before I proceed, it's not my intention to say that this is the best stack for all Django projects. It's not. I believe it is a very good approach, but there are very many good approaches. Approaches. We heard one just today earlier in this room. The approach you take depends on your project, your circumstances, your goals. But what I would like you to take from this talk today, if you are looking to combine Vue and Django, there is an effective solution that preserves the strengths of both.

5:08

Now I've used this particular approach in production for over four years and it served me very well. Okay, I'll demonstrate this approach today with a fictitious app. In this fictitious app, a user may spend virtual points to claim one or more rewards presented to them. Now I've already written both sides of this I'm sorry I don't have time to interactively code with you. I know how riveting it is to watch people code. But uh I will go over the code with you. On the Django side, we have a very simple model uh application with a model, the reward model has three fields, name, description, point value, point value being how many points it takes to purchase that reward. We have a list view that presents all these rewards to the user and an associated template

5:55

which builds the HTML for that view. Oops, uh sorry. Here's what that view looks like. You see we loop through all the rewards. We show the name, description, and point value for each. And we have some areas set aside where we want interactivity, namely these buttons here. Now Oh, and we also want these uh points remaining to be decrementing every time we choose a reward to redeem. So I said we'd be using Vue to interact in inject this interactivity. We'll do so with this Vue application on the right. It will consist of three elements: a pinia store. store a P store is where we encapsulate the business logic and also the shared state for all the components on that page.

6:43

Then we'll use two single file components. These are the basic building blocks of view for renderable content Uh the first will be point status element, which will correspond to this area up top, and reward claim, which will correspond to the buttons here. So these will serve to kick off the redeem logic when clicked. Okay, now let's look at the code. I'll skip most of the Django side, but here's the template that renders that view. You can see up top we have a header element which contains this. This section, we show the redeemable points, which comes from our view layer, uh, it's context variable, but this is static now, it's not going to change. Then we loop through our rewards and show a card for each that includes the name, the point value, and the description.

7:31

And we have a place set aside here for our button. It will go there. Okay, let's look at the view side. Now, as I said, three components. The first being the store. Here's the file for the store. Uh the first line we define the store. Then we have two uh two pieces of state that we'll be tracking. The first will be how many points have been redeemed so far in this session. We'll start with zero And then which specific specific rewards have been redeemed? We'll use a dictionary for here. The keys will be the reward ID, the values will be true or false, depending on whether that reward is redeemed. We'll assume that absence from this dictionary means that they are not redeemed. Next, we have two actions, redeem and unredeem. Actions in a PNY store

8:17

are functions that encapsulate business logic. So here in our business logic Uh if we are redeeming a specific reward ID with a point value, we will simply update our store. Points redeemed is incremented, and in our dictionary of rewards redeemed, we set that specific reward ID to true, indicating that it's redeemed. Unredeemed works very similar, but opposite. We decrement the number of points, and in the dictionary we set that to false, indicating it is not redeemed. Finally, we have a helper function is redeemed. We'll use this in our component. And this will return true or false for a given reward ID if it has been redeemed. Okay, now for the two components. The first one is point status, and that corresponds again

9:02

to this area up top Right at the top we have a prop defined. A prop is Vue's concept of a uh parameter. So the idea here is that different users will have different points made available to them. So this component needs to know how many points are available so that it can do its calculation to show how many are remaining. Now, typically in a view application, you might be using a REST backend to pull this information, but we're not going to be doing that, as I said. So we'll be getting this information from our view layer in Django I'll show you how we do that in a moment, but for now just keep in mind that this component is parameterized. We'll need to supply this points available for us to use this component. Moving on, we grab a handle to the store, the one we just created, as we'll be using it here in this template.

9:50

And in the template, we show the points remaining string and a container that shows how many points are remaining. Now this is a reactive content here. We take the points available, which is the prop, and we subtract the number of points redeemed from our store. That'll show us how many are remaining. Now up here we have a dynamic view binding to a class. We're going to apply a specific class to this element. Specifically, uh the the negative points class will be added if the user has redeemed more points than are available. In other words, if they have gone negative in their balance. The point of showing this is to show you that you can style components here individually. By virtue of being placed on the page, they will automatically get all the styling of your Django page.

10:35

a Django site, but if you want to add additional styles, you can add them here as so. Uh the second component, reward claim This corresponds to the button , these buttons. This takes two props, so each button will need to know for which reward it's redeeming and how many points that specific reward costs Again, we'll show you how later we pass that information. Again, we grab a handle to the store because we'll use it in our template. And here on the template we show one of two buttons If the user has not yet redeemed the reward, then we show the redeem button, and when clicked, that will trigger the redeem action in our PNS store, our business logic. Otherwise We will show the unredeem button.

11:21

And we'll, when that was clicked, we will show the unread or do the unredeem action from our store. Okay, so that's it. I told you we'd need three components, a store, and two single file components. We have them. How do we tie these all together now, especially in such a way that we can inject them into our Django template? Well, the view of that's done with what's called an entry point. And I'll show you the code here. Okay. Starting at the top, just a little boilerplate, all the boilerplate, all the components here will be using the pinyus store. So we need to instantiate that here at the top. But otherwise, moving on to line nine, we look in our document for an element

12:08

identified by the ID point status. So if we look again at our Django template here, you'll see up at the top we have this section here, a header that has this content and is identified by the ID point status. So that is indeed where we want to inject our view component. So we grab a handle to it here. On the next line, we run create app, a view function. This is view's top-level function for inserting a component into your page. The first argument for this needs to be the root component in question. In our case it's point status. And the second will be the names and values of your prop values, your root component prop values. Now you can pass these normally in any way you wish But uh here I'm using a function that I wrote. It's made available as a function. You can use this in your own projects. I'll focus on what it allows us to do, not the exact code of the function itself.

12:54

But what this allows us to do is take the dataset attributes of the parent element and pass them in as component values to our root component. So in this case, if we go back to our Django template we can add a data attribute for that um for that root component prop. It was called points available We'll convert it to kebab case here and pass in a value. Now the value we want is the same as the one that we're showing to the user here. Comes from our template variable, redeemable points. So there, there's our basic mechanism how we can pass information from the Django view layer to our view component without having to go through rest. We're using it with data attributes here.

13:41

Moving on on the entry point, we note that this component will use PENIA for its state management, and then we physically mount it into that status element that we identified above. That completes the process here. What about reward claim? Well, we can't use an ID for this one because we see it's being repeated several times on the page. IDs have to be unique. So let's look at our template. What can we use as a handle? Going through our loop here in the rewards, we see that we have a reward claim section , a placeholder for our button here identified by the reward claim class. So this is what we'll use as our handle here. And you can see in our entry point we do a query select RL, get all the reward claim class elements with all the reward claim, all the elements identified with the reward claim class.

14:28

And for each of those, we create an app. This time our root component is create as reward claim. Again, we convert dataset attributes into properties to that. So we can go back to our Django template now and pass those. It required a reward ID. We'll use that from our loop variable, which is a reward model, and the number of points that that costs. Okay. Finishing the entry point, we again note that this uh component will use Penia for state management, and then we physically mount it into that element. Now we have our view side completed. We have all the elements that make up our app, and we have an entry point that serves to map that into our Django template

15:14

On our Django template side, we have uh set aside those container elements and we have passed the needed props. Is there anything left to finish this? Uh yes, one last thing, we need to import the uh JavaScript for the entry point in our template. Okay, so now if we go back to our page and reload, we have the dynamic behaviors that we are expecting. You'll see that the buttons switched from the redeemed to unredeemed side, and our point totals are updated as we go along. Now what's cool is that we still get all the benefits of our view templates, despite the fact that we're now running this through a Django

16:05

application. If I bring up the Developer toolbar, for example, and bring up the view tools. You can see that we can, for example, inspect our state on the page as we modify it down here at the bottom. We can even physically manipulate this state through our browser for testing purposes or for observing the effects of state changes. What's better, we still get hot reload from view. So if I make a code change to my view code uh like here, a point remaining, I'll change this to uh different text, points uh left. Oops, wrong component.

16:56

Yeah, here. I'll change this color down here to blue as well It's immediately updated in our page, no need for page reload. And it even maintains our state all the while. Okay, one quick detour. You saw that when I coded this uh source for the JavaScript, I use a local host directory. Running on a different port. This is a URL to my VET dev server running on the machine. VET comes with a dev server very much like Django's run server, which enables all the debugging uh functionality. like I just showed you. This is ex absolutely what you want to use when you're running or developing your code. But in production you're you will be expecting uh bundled uh uh rather uh

17:42

compiled JavaScript files Production assets. So how do we handle that? All we really need to do is ask Veat to output its build uh files to uh someplace where Django can get to it, which is namely the static directory. So here's my VT config file You can see I've set the output directory here to my Django static directory. So if I run a build, a production build, it will output here into my Django static directory We can then change this URL to a static URL. We can reload the page. It still works. Uh however, we don't have any access to our developer tools anymore because this is now a production build.

18:29

Of course, it doesn't make sense to have to change our templates every time we switch between production and uh development. So I personally use a template tag. To manage that process for me, just give it the name of a bundle and it will build the URL appropriately to the dev server or to uh static based off of a setting. I've made this available as a library, you can use it on your own um uh projects. And that completes that loop. Um one thing to note before I go on though, so still most of this page is written in Django templates. If you want to change the structure of the cards or the entire uh Shell of the site, this is still all done in Django templates. Uh and we still get all the benefits of running through Django templates such as middleware and authentication and so on.

19:16

All those Django mechanisms are preserved It's only when we get into interactivity that we move then into View. But when we move into View, we're writing only in Vue. We can use all the View tools that we're comfortable with in the entire node ecosystem. So Django templates stays templates, view stays view. Okay, so having completed that example, we have the basic um roadmap for integration at this point. So first decide what you want to keep as a Django template, which part of the page is Django template, and which parts you want which parts you want to become view. I personally try to keep as much as possible in the Django templates because of the convenience. Then you code those parts.

20:03

You can uh do this independently. You can potentially have a separate team working on each side using the tools that they're most familiar and comfortable with. It's only then when it when it comes time to integrate that we need to coordinate a little bit. On the view side, we build our entry points and we map, in those entry points, we map our components to the container elements in our template. Now on the Django side, we create those container elements in our template. We pass any needed props to the view components that we're using. And then finally we import the entry point JavaScript. You could import multiple entry points per page. How you organize your entry points is up to you.

20:49

You could do it per component, you could do it per page, you could lump uh theme uh similar components all together in a single entry point. Uh it's all up to you Okay, so I'm sorry that was so quick and and uh uh very much a crash introduction, but I'll leave you with some additional resources here. First, there's an article. Uh Django view plus feet rest not required, which goes into uh, as I said, a lot more detail uh about the uh uh techniques I described here. It also has a lot more additional techniques such as additional additional ways to pass data from Django to Vue. There is examples of how to post back data from the viewfront band front

21:34

end to Django. How to have persistent state across page reloads. Normally if you would reload the page, you'd lose your state. But there are convenient plugins to keep your state cons persistent across page loads. And finally, some discussion of views teleport. capability which will allow you to move parts of your view uh components to all over your Django template. very useful when integrating there. There's a cookie cutter project made available. This is a great way to bootstrap bootstrap your own project using these techniques It's based off of the standard Django cookie cutter but includes the the view integration and also a very much more in-depth or thorough rewards example than the one that I showed you here.

22:21

And then finally, a couple libraries that encapsulate some of those helper functions and logic that I described during this talk. All right, thank you very much for your time. Uh enjoy the rest of DjangoCon. And if you have any questions, please come up and ask me either now or anytime during the conference Thank you.

Questions this talk answers

Can I use Django templates and Vue together without building a REST API?

Yes. Keep the mostly static page structure, rendering, authentication, and other Django mechanisms in Django templates, and use Vue single-file components only for interactive areas. The two sides communicate through mounted Vue components and props rather than requiring a REST backend.

Discussed at 2:47

How should shared state and business logic be handled when combining Vue with Django templates?

Use a Pinia store to hold shared state and encapsulate actions such as redeeming and unredeeming rewards. Vue components access the store reactively, so one component can update state that another component displays.

Discussed at 7:31

How do I mount Vue single-file components inside a Django template?

Create placeholder elements in the Django template, identify them with an ID for a unique component or a class for repeated components, and use a Vue entry point to query those elements, create the appropriate app, and mount each component into its placeholder.

Discussed at 11:21

How do I pass Django template data into Vue components?

Put Django values in data attributes on the component’s container, then have the Vue entry point convert those dataset attributes into the component’s props. For example, a Django context value can supply the available points, reward ID, or reward cost without going through REST.

Discussed at 12:54

How do I use Vite with Django in development and production?

During development, load the JavaScript from Vite’s dev server to get debugging and hot reload. For production, configure Vite to build into Django’s static directory and use a template tag or equivalent helper to choose the dev-server URL or static asset URL based on the environment.

Discussed at 17:36

What Django and Vue features do I keep with this integration approach?

Django templates retain their convenience and Django features such as middleware and authentication, while the interactive portions remain full Vue code with Vue’s tooling and the Node ecosystem. Vue Devtools, hot reload, and state-preserving development behavior are also available during development.

Discussed at 19:16

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos by Mike Hoolehan

More videos from DjangoCon US