Skip to content

Default rewrite.config 404s static assets in 3.x app trees (non-/images,/css,/js dirs) #2626

Description

@bpamiri

Describe the bug

When a 3.x app booted under Wheels 4.0 (via wheels start) has its static assets under directory names other than the defaults — typical 3.x convention is /miscellaneous/, /javascripts/, /stylesheets/, /files/ — every CSS/JS/image request returns 404 and the site renders unstyled.

Root cause: the auto-generated rewrite.config at ~/.wheels/servers/<name>/conf/Catalina/localhost/rewrite.config only short-circuits a narrow set of directories:

RewriteCond %{REQUEST_URI} !^/(images|css|js|fonts|assets|static)/
RewriteCond %{REQUEST_URI} !\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|json|xml|txt|map)$
RewriteRule ^/(.*)$ /index.cfm/$1 [L]

Two problems:

  1. Allow-list is too narrow. Real 3.x apps use directory names outside this set. A /miscellaneous/libs/bootstrap/... request gets rewritten to /index.cfm/miscellaneous/libs/bootstrap/..., dispatched into Wheels, no controller matches → 404 with the Wheels error page.

  2. Negated RewriteCond chains don't reliably short-circuit on Tomcat 9 + Lucee 7.0.0.395's bundled RewriteValve. Even when the URI clearly matches the negated extension pattern (URI ends in .css), the catch-all RewriteRule still fires. Reproduced consistently — only switching to explicit [L]-flagged pass-through rules fixes it.

  3. REQUEST_FILENAME -f test (the standard Apache idiom) is not usable as a workaround. It throws:

    java.lang.IllegalArgumentException: The resource path [null] is not valid
      at org.apache.catalina.webresources.StandardRoot.validate(StandardRoot.java:245)
      at org.apache.catalina.valves.rewrite.ResolverImpl.resolveResource(ResolverImpl.java:322)
      at org.apache.catalina.valves.rewrite.RewriteCond$ResourceCondition.evaluate(RewriteCond.java:85)
    

To Reproduce

  1. Start with any 3.x Wheels app whose static assets live in non-default directories — e.g., public/miscellaneous/, public/javascripts/, public/stylesheets/, or public/files/.
  2. Upgrade per the 3.x → 4.0 guide.
  3. wheels start. Boot succeeds, redirect-to-login works, controllers render.
  4. Observe in the browser: every <link rel="stylesheet"> and <script src> returns 404. Site renders unstyled.
  5. Access log confirms requests like /miscellaneous/libs/bootstrap/4.3.1/css/bootstrap.min.css are rewritten to /index.cfm/miscellaneous/... and 404.

Expected behavior

The default rewrite.config should serve real files in webroot without rewriting them, regardless of which directory name the 3.x app uses.

Working fix (for the upgrade testbed)

Project-level rewrite.config using explicit [L]-flagged pass-through rules instead of negated RewriteCond chains:

RewriteRule ^/(.*)\.(cfm|cfc|cfml)$ - [L]
RewriteRule ^/(images|javascripts|stylesheets|miscellaneous|files)/.* - [L]
RewriteRule ^/.*\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|json|xml|txt|map|webmanifest)$ - [L]
RewriteRule ^/(lucee|robots\.txt|favicon\.ico|sitemap\.xml)(/.*)?$ - [L]
RewriteRule ^/files/feedback/.* - [F]
RewriteRule ^/(.*)$ /index.cfm/$1 [L]

Suggested fixes (pick any/all)

  1. Broaden the default allow-list to common 3.x directory names: images|javascripts|stylesheets|miscellaneous|files|assets|css|js|fonts|static|public.
  2. Switch the default rule style from negated RewriteCond chains to explicit [L]-flagged pass-through rules, as above.
  3. Add an upgrade-guide note under "Static assets" calling out the urlrewrite.xmlrewrite.config migration explicitly, with a working sample for 3.x apps. Currently this surfaces only as a boot-log hint — no upgrade-guide section mentions it.

Environment

  • Wheels CLI: 4.0.0-SNAPSHOT+1779 (Homebrew tap wheels-dev/wheels)
  • Engine: Lucee 7.0.0.395 (bundled with the CLI)
  • Tomcat: 9.x (bundled)
  • Java: OpenJDK 21.0.11 (Homebrew)
  • OS: macOS

Why this matters

This is the first wall any 3.x app upgrader hits after a successful framework boot. The boot log warns that urlrewrite.xml (Tuckey) is ignored — but does not say how the default replacement will fail. The hint that follows (💡 Create rewrite.config in your project to customize rewrite rules) understates the impact: most 3.x apps must create one or static assets won't serve.

Surfaced during a real-world 3.x→4.0 upgrade of a production app (PAI Industries' titan e-commerce storefront, ~46 models, ~5 datasources).

🤖 Filed by Claude Code while assisting with a 4.0 upgrade testbed

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions