Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Shannon Hicks
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Shannon Hicks ( @iotashan )

Hotwire Turbo Drive Doesn't Work With .cfm Page Extensions

By on

Over the holiday break, I had this grand vision of building a ColdFusion site and then adding Hotwire (HTML Over The Wire) to it as a progressive enhancement. Unfortunately, it took me all of break just to get the ColdFusion parts written (I chose a poor problem space). And then, when I finally installed Hotwire and tried to use Turbo Drive, nothing happened. Every link and form submission lead to a full page refresh. After a few hours of Googling, I discovered that Hotwire Turbo Drive doesn't work with .cfm file extensions.

According to GitHub Issue 519: Remove isHTML, this is by design. Hotwire Turbo Drive works by intercepting anchor links and form submissions, AJAX'ifying the interactions, and then preventing full-page reloads. However, it can only do this if the target of the link / form is known to be an HTML page. And, since dynamic server-side technologies like ColdFusion, PHP, Ruby, Python, .etc can serve up anything (such as image binaries), Hotwire cannot - in good conscience - assume that a .cfm file extension will lead to HTML content.

The Basecamp team is considering how to open Hotwire up to more technologies by default - see GitHub issue above; but, in the meantime, in order to get Hotwire Turbo Drive working with my ColdFusion / Lucee CFML demo application, I switched all my .cfm extensions to be .htm and then enabled URL rewriting in my CommandBox server.

Thankfully, CommandBox this as easy as setting the BOX_SERVER_WEB_REWRITES_ENABLE environment variable in my docker-compose.yml file:

version: "2.4"

services:

  lucee:
    build:
      context: "./docker/lucee/"
      dockerfile: "Dockerfile"
    ports:
      - "80:8080"
      - "8080:8080"
    volumes:
      - "./app:/app"
    environment:
      APP_DIR: "/app/wwwroot"
      BOX_SERVER_APP_CFENGINE: "lucee@5.3.10+97"
      BOX_SERVER_PROFILE: "development"
      BOX_SERVER_WEB_REWRITES_ENABLE: "true" # <------- Enable URL rewrites!
      cfconfig_adminPassword: "password"
      HEALTHCHECK_URI: "http://lucee:8080/healthcheck.cfm"
      LUCEE_CASCADE_TO_RESULTSET: "false"
      LUCEE_LISTENER_TYPE: "modern"
      LUCEE_PRESERVE_CASE: "true"

With this configuration in place, I was then able to go into my ColdFusion templates and changes all references of index.cfm to be index.htm. Here's my primary layout template, which demonstrates this change in the <nav> element:

<!--- Reset the output buffer. --->
<cfcontent type="text/html; charset=utf-8" />
<cfoutput>

	<!doctype html>
	<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<title>
			#encodeForHtml( request.template.title )#
		</title>
		<script src="/js/main.js" defer></script>
	</head>
	<body>

		<div>
			Sticky Tips
		</div>

		<nav>
			<ul>
				<li>
					<a href="/index.htm?event=dashboard">Dashboard</a>
				</li>
				<li>
					<a href="/index.htm?event=tip">Tips</a>
				</li>
				<li>
					<a href="/index.htm?event=tippee">Tippees</a>
				</li>
				<li>
					<a href="/index.htm?event=event">Events</a>
				</li>
				<li>
					<a href="/index.htm?event=faq">FAQ</a>
				</li>
			</ul>
		</nav>

		<hr />

		#request.template.body#

	</body>
	</html>

</cfoutput>

As you can see, all of my primary navigation link elements point to /index.htm. This page doesn't actually exist. As such, the underlying J2E server is rewriting the request to be:

/index.cfm/index.htm

... where the originally requested resource (/index.htm) becomes the cgi.path_info value. My entire ColdFusion site routes through index.cfm, so this URL rewrite is seamless - no extra work on my part.

Once I had the .htm file extensions and this URL rewriting in place, I was able to install Hotwire Turbo:

npm install --save-dev @hotwired/turbo

And activate it with a simple import:

// Import vendor modules.
import * as Turbo from "@hotwired/turbo";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

document.addEventListener(
	"turbo:load",
	( event ) => {
		console.log( "turbo:load event" );
	}
);
document.addEventListener(
	"turbo:before-visit",
	( event ) => {
		console.log( "turbo:before-visit event" );
	}
);
document.addEventListener(
	"turbo:before-fetch-request",
	( event ) => {
		console.log( "turbo:before-fetch-request event" );
	}
);
document.addEventListener(
	"turbo:before-render",
	( event ) => {
		console.log( "turbo:before-render event" );
	}
);

Now, if I load the ColdFusion application and try clicking through the primary navigation links, we can see the Hotwire Turbo events being logged to the Chrome developer tools console. Note that the console log is persisting across clicks, indicating that the page is not refreshing, but is - instead - being progressively enhanced.

Hotwire Turbo intercepting ColdFusion page requests with .htm file extensions.

Finally, I can start exploring this whole Hotwire framework! I had intended to get back from holiday knowing all this stuff already; but, the sample ColdFusion app that I'm building is likely not a good fit for some of this stuff and is more complicated than it needed to be. Anyway, at least I can start to play now.

Want to use code from this post? Check out the license.

Reader Comments

4 Comments

I've been using hotwire a lot with Rails and it is awesome. I do love coldfusion though and thought I would search around and see if anyone has hooked hotwire up to it and found this! Great info. Any other work arounds other than command box to get around the cfm file extension? I have a CF 2018 prod server that is managed by a separate IT group.

15,663 Comments

@Chris,

URL rewriting would be the only workaround that I know of at this time. That said, in the GitHub issue, they talk about coming up with a way to do this more programmatically in the client-side code (ie, determine which files are Turbo'able). But, that's still in the discussion phase as I understand it.

I have a sense that all this Hotwire stuff is really cool; but, I'm struggling to figure out how to approach it. Part of me just wants to start adding Stimulus everywhere. But, I know that this is not the Hotwire way - that I should be pushing the boundaries of Turbo first, and then filling in the gaps with Stimulus. It's really turning my brain inside-out and I'm feeling rather stuck.

4 Comments

Stimulus is great too. I had done some basic tests integrating that into CF/Lucee. I need to spend some more time with it.

Looking into some things I see cbwire as well which sounds similar in concept.

https://cbwire.ortusbooks.com/

Have you worked with this at all?

15,663 Comments

@Chris,

I've tried a bit of Stimulus - it looks pretty nice, and keeps things with the Turbo Drive simple (in that it automatically connects / disconnects elements and controllers).

I have not tried CBWire yet - but, I was just watching a video demo of it over the weekend. It looks interesting, something I'll definitely take a closer look at - the Ortus people are very bright, so I have no doubt it's some great stuff.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel