Using Vite with Flask

Integrating a Vite project with Flask can be accomplished using Flask Blueprints.

TLDR: the goal is to have Flask serve the output of the build step as static files.

Starting with a simple Flask app in main.py, the end result will have the following structure:

vite/
	dist/
	src/
	views.py
main.py

For this example, the Flask app is as simple as it can be:

"""
main.py
"""

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello from home'

if __name__ == '__main__':
    app.run(debug=True)

I put a Vite project in a folder called vite inside the Flask project:

mkdir vite
cd vite
npm create vite@latest

In my case, I accepted the default project name vite-project then renamed it to src.

mv vite-project/ src
cd src
npm install

By default, Vite will place the build output in the project root. We can change it in the config file:

// vite.config.js
import { defineConfig } from "vite";

export default defineConfig({
  base: "",
  build: {
    outDir: "../dist",
    emptyOutDir: true,
  },
});

When the app is built with npm run build, the folder structure will look as follows:

vite/
	dist/
	src/
main.py

The final step is to set up a Blueprint so that Flask can serve files inside the vite/dist folder. Blueprints are handy for this because each Blueprint can have a different static folder.

Create views.py inside the vite folder:

"""
vite/views.py
"""

from flask import Blueprint

vite_bp = Blueprint('vite', __name__, static_folder='dist', static_url_path='/')

@vite_bp.route('/')
def index():
    return vite_bp.send_static_file('index.html')

This creates a Blueprint and directs it to use the dist folder as the static folder. We remove the default static URL prefix for static files by setting static_url_path='/' and create a route to serve the index.html file.

Since index.html is inside the dist folder, which we established as a static folder, we can use the send_static_file function.

Finally, register the Blueprint in main.py:

app.register_blueprint(vite_bp, url_prefix='/my_vite_app')

Set url_prefix to where the Vite app should live, run the Flask server, and that’s it!

Sidenote about paths

The reason we set base: '' is that if the url_prefix of the Blueprint ever changes, you won’t have to rebuild the Vite app.

For example, asset links such as:

<link rel="stylesheet" href="assets/index.06d14ce2.css" />

will resolve into http://<...>/my_vite_app/assets/index.06d14ce2.css in our example, and Flask will know how to route a request for that file since it’s relative to the URL prefix my_vite_app.

Try setting the base option to something else, and see how that changes the paths.

Sidenote for App Engine users

If you use App Engine, you will run into an issue where an old version of index.html is served after deploying a new version of the app. This is because App Engine sends a response with the Last-Modified header value always set to January 1, 1980. As a result, browsers will think that the file hasn’t changed and will return a cached version.

One workaround, shared by a user on Google’s issue tracker, is to manually remove the Last-Modified header in Flask by using send_from_directory instead of send_static_file:

response = send_from_directory(vite_bp.static_folder, 'index.html')
response.headers.remove('Last-Modified')
return response

References