Introduction

I’ve recently started learning Python’s Django framework and have brought my favourite JavaScript framework along with me: TailwindCSS. From a development standpoint, I find that TailwindCSS feels much more efficient when I don’t have to refresh the page every time I make a change. At first, I wasn’t entirely certain about how I could achieve auto-refreshing my browser while using Django’s manage.py runserver command. After some research, I came across an elegant, simple solution. That is what I’ll share with you today so you can save yourself time and jump right back into coding.

The Approach

We’re going to use two NPM packages to achieve an auto-refreshing browser with Django:

  • browser-sync
  • concurrently

Browser Sync is a proxying web server that reads a source URL and then serves that content on its own endpoint. You can provide it with a list of directories and files to watch and, when any of them are changed, the server will refresh the browser for anyone who’s connected.

Concurrently allows multiple NPM commands to be executed simultaneously (concurrently, you might say). By using this, we can run the browser-sync command at the same time as the tailwindcss command without needing to spawn more terminal instances.

Before We Begin

Please ensure that the following works for your project before following this guidance, as these are requirements for this advice to work:

  • You can run python manage.py runserver and use your application without issue at the local address.
  • You have Node and NPM installed correctly.
  • Within your package.json, you have a scripts command for your TailwindCSS output (or SASS, or whichever frontend framework is your poison).

Here is my npm run start command, for your reference:

// package.json
...
"scripts": {
    ...
    "start": "tailwindcss -i ./static/css/styles.css -o ./static/css/output.css --watch",
},

Installing Browser Sync and Concurrently

Assuming you’re using NPM, you can install these packages as follows:

npm install browser-sync concurrently --save-dev

If you’re using Yarn, or another package manager, you’ll need to adjust this command accordingly.

Configuring Browser Sync for Django

In the root of your project, create a file called bs-config.js:

// bs-config.js
module.exports = {
    proxy: "127.0.0.1:8000",
    files: [
        "static/css/*.css",
        "static/js/*.js",
        "app/templates/**/*.html"
    ],
    open: false,
    notify: false
};

In this file, on line 3, you’ll see we provide a URL as the proxy. This is the source address from which the Browser Sync server will get the webpage. Whenever you navigate to the proxy server URL (which I’ll show you how to do shortly), it’ll take your request and serve you the content from this URL. In my example, it’s set to 127.0.0.1:8000, which is the default URL from Django’s manage.py runserver command.

The files array is all of the files which should be ‘watched’ by Browser Sync. When changes are detected in these files, it’ll refresh the connected clients. As such, you’ll want to set these to match your project’s structure.

Adding a Concurrently Command for Browser Sync

Within our package.json file, we’re now going to add a concurrently command. This will allow both the browser-sync command and the tailwindcss/npm run start command to run in one place. This means that the browser can refresh and the CSS can be regenerated without adding even more terminals to manage:

// package.json
...
"scripts": {
    ...
    "start": "tailwindcss -i ./static/css/styles.css -o ./static/css/output.css --watch",
    "dev": "concurrently \"npm run start\" \"browser-sync start --config bs-config.js\""
},

This new dev command runs the existing start command for TailwindCSS, then runs browser-sync with the bs-config.js file we created earlier to configure it.

Running the Auto-Reloading Server

With everything configured, it’s now time to put this all together. First, in one terminal, run python manage.py runserver. This will start your Django development server like usual on 127.0.0.1:8000:

python ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
November 01, 2024 - 09:10:25
Django version 4.2.16, using settings 'my-project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Next, run npm run dev. This will start our TailwindCSS compiler and Browser Sync proxy server:

npm run dev

> [email protected] dev
> concurrently "npm run start" "browser-sync start --config bs-config.js"

[0] 
[0] > [email protected] start
[0] > tailwindcss -i ./static/css/styles.css -o ./static/css/output.css --watch
[0] 
[1] [Browsersync] Proxying: http://127.0.0.1:8000
[1] [Browsersync] Access URLs:
[1]  -----------------------------------
[1]        Local: http://localhost:3000
[1]     External: http://172.16.0.4:3000
[1]  -----------------------------------
[1]           UI: http://localhost:3001
[1]  UI External: http://172.16.0.4:3001
[1]  -----------------------------------
[1] [Browsersync] Watching files...
[0] 
[0] Rebuilding...
[0] 
[0] Done in 187ms.
[1] [Browsersync] File event [change] : static/css/output.css

You’ll see in the above example that any output with the [0] prefix belongs to Tailwind, and any output with the [1] prefix belongs to Browser Sync.

To test this, you should now visit http://localhost:3000. On there, you should see your website as you’d expect. If you were to now make a change to the files which you added to the bs-config.js paths, then the browser should refresh automatically.

You can play around with various settings for Browser Sync by visiting http://localhost:3001.

Troubleshooting

My browser refreshes but it doesn’t use the new CSS file

Browsers tend to cache static files such as CSS files. Typically, other auto-reloading servers such as Vite will give the files randomised names between generations so that the browser doesn’t use old, cached versions.

For this approach, what we can do instead is tell the browser not to use any cached data. To do this (and this will vary depending on your browser), go to your development tools (f12), and then your networking tab. On there, you should have the option to disable the cache. For this to be effective, you might need to keep your development tools open, but this also depends on your browser/settings.

With this setting enabled, you should see the new CSS file is fetched from the server every time the page reloads, allowing changes to show up live.

Closing thoughts and things to note

On occasion, I’ve noticed the browser refreshes faster than TailwindCSS manages to recompile its files. On these occasions, I’ve had to manually refresh afterwards. In my testing, these instances are rare and mostly happen when saving multiple times in quick succession.

Overall, I’m happy with the ease of this approach. Being able to just proxy the traffic and not have to worry about the origin server is great from a usability perspective. You can easily apply this approach to most development environments where the local server already exists. The only exceptions to this might be for any projects using more complicated protocols, such as WebSockets.

If you haven’t already, I’d highly encourage you to read more about Browser Sync. Beyond being a useful tool for a development auto-reload server, it also has a suite of features for helping to test your website in multiple browsers.

I hope that this post allowed you to get auto-reloading working on your project quickly and easily. If it did, be sure to leave a comment or browse some of my other articles.

Leave a Reply

Your email address will not be published. Required fields are marked *