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

Cyberdime
Published: January 6, 2023

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="https://www.bennadel.com/js/main.js" defer></script>
	</head>
	<body>
		<div>
			Sticky Tips
		</div>
		<nav>
			<ul>
				<li>
					<a href="http://www.bennadel.com/index.htm?event=dashboard">Dashboard</a>
				</li>
				<li>
					<a href="http://www.bennadel.com/index.htm?event=tip">Tips</a>
				</li>
				<li>
					<a href="http://www.bennadel.com/index.htm?event=tippee">Tippees</a>
				</li>
				<li>
					<a href="http://www.bennadel.com/index.htm?event=event">Events</a>
				</li>
				<li>
					<a href="http://www.bennadel.com/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.

Source: www.bennadel.com