Harry Khanna
Khanna Law
Published on
Friday, August 6, 2021

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.

  1. Follow the instructions to install django-tailwind, including the theme app. Use the recommended jit compiler. Make sure Tailwind is working properly in development.
  2. Install Whitenoise for serving static assets in production on Heroku.
  3. Every time a change is made to styling in a Django template, the jit compiler may generate a new styles.css file. We don't want to commit a whole new styles.css file every time we make a minor change to a template, so add **/dist/styles.css to your root .gitignore.
  4. 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 the collectstatic command, so we override that command to run the manage.py tailwind build command first. Add this to the theme/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)
    
  5. Because we're overriding the collectstatic command, we need to make sure that the theme app comes before the django.contrib.staticfiles app. Otherwise, the default collectstatic 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",
    ...
    ]
    
  6. Because of the positioning of the styles.css file within the themes/static/ directory, once its been generated by the manage.py tailwind build command, it will automatically be collected by collectstatic into the STATICFILES_DIR.
  7. The manage.py tailwind build command relies on Javascript, so you need to add the nodejs buildpack to Heroku: heroku buildpacks:add --index 1 heroku/nodejs.
  8. In theme/static_src/package.json, rename devDependencies to dependencies as Heroku will need those packages during the manage.py tailwind build command. If they are devDependencies, Heroku won't have access to those during the production build.
  9. You actually have to install those dependencies during the build. The Heroku nodejs buildpack looks for a package.json in the project root. Create the following package.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"
      ]
    }
    
  10. Finally, Django doesn't pick up the fact that new static directories were created in the overriden collectstatic command so we make sure the themes/static/css/dist directory exists in the repo with an empty .keep file placed in the themes/static/css/dist directory.

These steps allow you to use django-tailwind in production with Heroku. Happy rapid prototyping!