Deploying django-tailwind to Heroku
Production Django Templates and Tailwind CSS
My preferred technology for rapidly prototyping ideas is Django, styled with Tailwind CSS and deployed on Heroku. But Tailwind CSS is designed to be used with the Javascript ecosystem, such as projects based on React.js. Until recently, using Tailwind CSS with Django templates was complex unless you wanted to integrate the whole Javascript build process into your your Django project. I never wanted to spend a day fighting with Webpack to figure that out.
django-tailwind to the rescue!
This project aims to provide a comfortable way of using the Tailwind CSS framework within a Django project.
django-tailwind
makes it easy to develop with Tailwind CSS and Django templates. It also has a convenient manage.py tailwind build
command to get a production build of Tailwind CSS.
But getting this all to work with Heroku took a little bit of trial and error. Here's how I did it.
- Follow the instructions to install
django-tailwind
, including thetheme
app. Use the recommendedjit
compiler. Make sure Tailwind is working properly in development. - Install Whitenoise for serving static assets in production on Heroku.
- Every time a change is made to styling in a Django template, the
jit
compiler may generate a newstyles.css
file. We don't want to commit a whole newstyles.css
file every time we make a minor change to a template, so add**/dist/styles.css
to your root.gitignore
. - Because we've the
styles.css
file won't be checked into the git repo, we need to generate it at build time, preferably as a minified, production-ready css file. Heroku automatically runs thecollectstatic
command, so we override that command to run themanage.py tailwind build
command first. Add this to thetheme/management/commands/collectstatic.py
:# theme/management/commands/collectstatic.py from django.core.management import call_command from django.contrib.staticfiles.management.commands.collectstatic import ( Command as CollectStaticCommand, ) class Command(CollectStaticCommand): def handle(self, *args, **options): call_command("tailwind", "build") super().handle(*args, **options)
- Because we're overriding the
collectstatic
command, we need to make sure that thetheme
app comes before thedjango.contrib.staticfiles app
. Otherwise, the defaultcollectstatic
command will get picked up instead of our custom one. h/t @skrengel for pointing this out to me!INSTALLED_APPS = [ ... "theme.apps.ThemeConfig", ... "django.contrib.staticfiles", ... ]
- Because of the positioning of the
styles.css
file within thethemes/static/
directory, once its been generated by themanage.py tailwind build
command, it will automatically be collected bycollectstatic
into theSTATICFILES_DIR
. - The
manage.py tailwind build
command relies on Javascript, so you need to add thenodejs
buildpack to Heroku:heroku buildpacks:add --index 1 heroku/nodejs
. - In
theme/static_src/package.json
, renamedevDependencies
todependencies
as Heroku will need those packages during themanage.py tailwind build
command. If they aredevDependencies
, Heroku won't have access to those during the production build. - You actually have to install those
dependencies
during the build. The Herokunodejs
buildpack looks for apackage.json
in the project root. Create the followingpackage.json
in the project root:{ "name": "your_project", "version": "1.0.0", "description": "Dummy package.json to point the Heroku buildpack at the correct one.", "scripts": { "postinstall": "cd theme/static_src && npm install" }, "cacheDirectories": [ "theme/static_src/node_modules" ] }
- Finally, Django doesn't pick up the fact that new static directories were created in the overriden
collectstatic
command so we make sure thethemes/static/css/dist
directory exists in the repo with an empty.keep
file placed in thethemes/static/css/dist
directory.
These steps allow you to use django-tailwind
in production with Heroku. Happy rapid prototyping!