CSS styles do not apply correctly on deployed app

Hello, I have a Sinatra app that is minimally styled using Bootstrap and CSS.

Here is a snippet of the head section with the two CSS links

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Best Buy App | <%= @title %> </title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="/stylesheets/styles.css">
    <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500"
          rel="stylesheet">
  </head>

When I run the server locally, here is how it appears

However the deployed version at the live link displays like the image below

When I deploy, it succeeds without any error. When I run the fly logs, I can’t see any error. What could be causing that difference and causing the CSS not to load correctly?

Are you still having this problem? It looks OK to me (viewing it on Safari on a Mac).

Is it possible you are seeing an old cached version of your stylesheet in the live version? Does hard refreshing help (or clearing browser cache)?

3 Likes

+1 to everything @Stephen said. Following from that, I noticed in the browser’s network monitor that /stylesheets/styles.css and the images have an HTTP response header “last-modified: Tue, 01 Jan 1980 00:00:01 GMT”, which is quite likely to cause caching issues.

I’m not sure why the last modified time has that value. Wikipedia says it’s the epoch of FAT filesystems and the ZIP format…

You can open a shell using fly ssh console and run ls -l /path/to/stylesheets to view the timestamps of the files in deployment (I predict they’ll be 1 Jan 1980), then try to backtrack from there to find where those timestamps are coming from (local filesystem? build process?).

There are ugly workarounds (e.g. fudge the timestamps during the build process), but I’d try to find the root cause first.

1 Like

This worked, Thanks Stephen. I did not know about hard refreshing Ctrl, Shift and the 'R' key but now I know.

This is quite interesting Tom. Let me dig in with your suggestions to see whats going on.

This is great for debugging (and confirms that the problem is due to caching), but I wouldn’t consider this a “solution” (or even a “workaround”) because your users can’t be expected to do a hard refresh every time you modify the files.

I can reproduce the issue both with your code and with Fly’s Helloruby app*. I’m confident the “1 Jan 1980” timestamp is due to the builder image heroku/buildpacks:20. Apparently it overwrites timestamps for reproducibility (discussion; that discussion is about the timestamps in the image metadata, but I assume it also applies to the file modification timestamps).

The same issue came up in Incorrect last modified fs value in Last-Modified header. The “solution” there was to switch from the Heroku builder to a custom Dockerfile.

If you want to stick with the Heroku builder (which Fly picks by default), I’m not sure how you can fix this properly. As an ugly workaround, you can change the file modification times by running find . -exec touch {} + at early startup. Example config.ru:

# frozen_string_literal: true

# config.ru

require './app'

# HACK: set all file modification timestamps to the current time
# (builder "heroku/buildpacks:20" sets them to 1 Jan 1980, which causes caching issues)
# WARNING: if multiple workers are started, this will run multiple times...
system "find . -exec touch {} +"

run Sinatra::Application

I’m surprised that there are no online complaints about caching issues with Heroku due to these timestamps. Maybe the Heroku platform has some magic to deal with this; it would be interesting if somebody can test this. @kurt (I see you’ve read this thread): if Heroku is doing something different from Fly, you might want to look into it (especially given that “flyctl launch” automatically chooses “heroku/buildpacks:20” as the builder, and that this happens with Fly’s Helloruby application).

*To reproduce the issue with Fly’s Helloruby app, I created a file “public/stylesheets/styles.css” and confirmed that the response headers for https://<app>.fly.dev/stylesheets/styles.css included “last-modified: Tue, 01 Jan 1980 00:00:01 GMT”.