How can I combine Vue.js with Flask?

When using vue-cli or Webpack, process can be simplified. Simply create

vue.config.js in your Vue project, see: Vue config

module.exports = {
    outputDir: "../dist",

    // relative to outputDir
    assetsDir: "static" 
};

then config your flask app:

app.py

from flask import Flask, render_template

app = Flask(__name__,
            static_folder = "./dist/static",
            template_folder = "./dist")


@app.route('/')
def index():
    return render_template("index.html")

Note, that in flask static_folder and template_folder can't be the same.


I recently had this problem (combining Vue.js and Flask).

There are at least two ways to combine them, depending on whether you're creating 1) a simple Vue.js app or 2) a more-complicated Vue.js app that needs to use a module bundler like Webpack to combine Single-File Components or npm packages.


Simple Vue.js app:

This is actually fairly easy and very powerful on its own:

  1. If you want the Vue.js functionality (the "app") to be on its own page, create a new template .html file. Otherwise just open whatever .html template file you want the app to be in.

    • This is where your Vue.js template code will go.
  2. If you're OK with having your Vue.js JavaScript code in the same file as your HTML, below is a simple "Hello World" example template you can use to get a sense of what needs to be in the Flask template file:

     <head>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
     </head>
     <body>
     <div id="app">
         <p>{{ message }}</p>
     </div>
     <script>
     new Vue({
       el: '#app',
       data: {
         message: 'Hello Vue.js!'
       }
     })
     </script>
     </body>
    
  • Note that this example is including the Vue.js JavaScript code within the Flask template file, so that you don't need to include the Vue.js JavaScript separately. This might be easier for smaller projects.
  1. Alternatively, if you want the Vue.js JavaScript code to be in its own file:

    1. Create a new JavaScript file in your static folder, name it after this app you want to create.
      • This is where your Vue.js JavaScript code will go.
    2. At the bottom of the .html template file include a script tag to include Vue.js.
      • <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
        • Note that that version number will change, so don't just copy that line. You can get the link for the most-recent version here.
    3. Also in that template file, also at the bottom, include a script tag to load the JavaScript file you just created.
    • <script src="%% url_for('static', filename='js/YOUR_APP_NAME.js') %%"></script>
    1. Create a new div in the .html template file that has an id of app.
    • <div id="app"></div>
  2. If you are using Jinja2 to render your templates, you will need to add a few lines of code to tell Jinja2 to not use the {{ }} syntax to render variables, because we need those double-curly-brace symbols for Vue.js. Below is the code you need to add to your app.py to do this:

     class CustomFlask(Flask):
         jinja_options = Flask.jinja_options.copy()
         jinja_options.update(dict(
             variable_start_string='%%',  # Default is '{{', I'm changing this because Vue.js uses '{{' / '}}'
             variable_end_string='%%',
         ))
    
     app = CustomFlask(__name__)  # This replaces your existing "app = Flask(__name__)"
    
    • Note that Flask-Bootstrap will not work if you switch Flask's syntax, as Flask-Bootstrap has its own templates which will still contain the "{{" / "}}" syntax. You can see here how to change Vue.js's syntax, it's even easier than changing Flask's syntax.
  3. Serve the page as usual. / Render the template file as usual.

  4. If you want, use a Vue.js 2.0 Hello World JSFiddle to do some quicker prototyping, and then copy the code over into your .html and .js files.

    • Make sure that the fiddle is using the most-recent version of Vue.js!

Easy!


More-complicated Vue.js app using Webpack:

  1. Install Node (it comes with npm, which is what we need).
  2. Install vue-cli:
    • npm install -g @vue/cli
  3. Create a new Vue.js Webpack project:
    • vue create my-project
    • One way to do this is to create a server folder and a client folder, where the server folder contains the Flask server code, and the client folder contains the Vue.js project code.
    • Another way is to have the Vue.js project contained as a folder within your Flask project.
  4. Set up your Webpack config so that the app.html file is created in your Flask server/templates folder, and the static JavaScript and CSS needed by app.html is created in a server/static/app/ folder, isolated from the static assets used by the non-Vue portions of you Flask app.
  5. When you want to combine your Vue.js project with your Flask project, run npm run build from within the folder containing your Vue.js project, which will generate an .html file and several static files (JavaScript and CSS).

The exact changes I made to my Webpack config (via my git commit):

client/build/webpack.dev.conf.js:

new HtmlWebpackPlugin({
-   filename: 'index.html',
-   template: 'index.html',
+   filename: 'app.html',
+   template: 'app.html',

Here (above) I'm changing the name of the Vue.js 'launch' file to app.html so that it doesn't conflict with my Flask app's 'index.html'.


client/config/index.js:

module.exports = {
  build: {
    env: require('./prod.env'),
-    index: path.resolve(__dirname, '../dist/index.html'),
-    assetsRoot: path.resolve(__dirname, '../dist'),
-    assetsSubDirectory: 'static',
-    assetsPublicPath: '/',
+    index: path.resolve(__dirname, '../../server/templates/app.html'),
+    assetsRoot: path.resolve(__dirname, '../../server/static/app'),
+    assetsSubDirectory: '',
+    assetsPublicPath: '/static/app',

Here (above) I'm setting where the app.html file and static assets should be created.

Because we're directing Webpack to generate the static assets within Flask's "static" folder (/static/app/)...

  1. The relative URLs to include those assets within the html file will automatically be set up correctly by Webpack.
    • Ex: src=/static/app/js/app.f5b53b475d0a8ec9499e.js
  2. When queried for the files at those URLs, Flask will automatically know how to serve them since they're within the static/ folder, which Flask assumes has a /static/etc. URL.
    • The only generated file that will need a Flask route is the app.html file.

client/build/webpack.prod.conf.js:

new HtmlWebpackPlugin({
  filename: process.env.NODE_ENV === 'testing'
-    ? 'index.html'
+    ? 'app.html'
    : config.build.index,
-  template: 'index.html',
+  template: 'app.html',

Here (above) I'm just renaming the 'launch' page, same as in webpack.dev.conf.js.


routes.py:

@web_routes.route('/app')
@login_required
def app():
    if current_user.datetime_subscription_valid_until < datetime.datetime.utcnow():
        return redirect(url_for('web_routes.pay'))

    return render_template('app.html')

Here (above) is my render function. I'm using Flask's Blueprints feature (<blueprint_name>.route) but you don't have to.