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
vite/src
is the root of the Vite projectvite/dist
is the output of the build stepvite/views.py
contains the Blueprint logic
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,
},
});
base: ''
will make the paths relative to whatever URL we’ll choose for the Vite appoutDir: '../dist'
will place the output of the build step as a sibling to thesrc
folderemptyOutDir: true
will make sure that thedist
folder is emptied out (it’sfalse
if thedist
folder is not a subfolder of the project root).
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