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 thethemeapp. Use the recommendedjitcompiler. 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
jitcompiler may generate a newstyles.cssfile. We don't want to commit a whole newstyles.cssfile every time we make a minor change to a template, so add**/dist/styles.cssto your root.gitignore. - Because we've the
styles.cssfile 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 thecollectstaticcommand, so we override that command to run themanage.py tailwind buildcommand 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
collectstaticcommand, we need to make sure that thethemeapp comes before thedjango.contrib.staticfiles app. Otherwise, the defaultcollectstaticcommand 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.cssfile within thethemes/static/directory, once its been generated by themanage.py tailwind buildcommand, it will automatically be collected bycollectstaticinto theSTATICFILES_DIR. - The
manage.py tailwind buildcommand relies on Javascript, so you need to add thenodejsbuildpack to Heroku:heroku buildpacks:add --index 1 heroku/nodejs. - In
theme/static_src/package.json, renamedevDependenciestodependenciesas Heroku will need those packages during themanage.py tailwind buildcommand. If they aredevDependencies, Heroku won't have access to those during the production build. - You actually have to install those
dependenciesduring the build. The Herokunodejsbuildpack looks for apackage.jsonin the project root. Create the followingpackage.jsonin 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
collectstaticcommand so we make sure thethemes/static/css/distdirectory exists in the repo with an empty.keepfile placed in thethemes/static/css/distdirectory.
These steps allow you to use django-tailwind in production with Heroku. Happy rapid prototyping!