Skip to content

[5.x] Fix static cache file paths for multisite setups #11724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 5.x
Choose a base branch
from

Conversation

ChristianPraiss
Copy link
Contributor

This PR fixes an issue with static caching in multisite setups. It was forgotten to add the site parameter in all places where the cache path is calculated, leading to all sites overriding the default site's content in setups that use separate subdomains for the sites.

@ChristianPraiss ChristianPraiss force-pushed the feat/fix-multisite-static-caching branch from a03bd54 to c047ede Compare April 22, 2025 14:00
@jasonvarga
Copy link
Member

jasonvarga commented Apr 22, 2025

Thank you but when you use separate domains or subdomains, you should be configuring the paths.

See from https://statamic.dev/static-caching#paths on.

'strategies' => [
    'full' => [
        'driver' => 'file',
        'path' => [
           'english' => public_path('static') . '/domain.com/',
           'french' => public_path('static') . '/domain.fr/',
           'german' => public_path('static') . '/domain.de/',
        ],
    ],
],

@jasonvarga jasonvarga closed this Apr 22, 2025
@ChristianPraiss
Copy link
Contributor Author

Hey, that's exactly what I am doing. But the site falls back to the config locale, which in my case is not being used, we have subdomains like b2b.customer.com, company.customer.com etc. as our sites. So when it goes to the non-default site it will just fallback to config('locale'), which will lead to no cached entry. The related piece of code is below, $site is null when no explicitly passed, which is why my PR would make sure that it is always passed. Related code below for reference.

Let me know if you need a repo to reproduce this 🙂


public function getCachePath($site = null)
    {
        $paths = $this->getCachePaths();
        if (! $site) {
            $site = $this->config('locale');
        }
        return $paths[$site];
    }

@jasonvarga
Copy link
Member

Yeah a repo would be helpful, thanks.

@jasonvarga jasonvarga reopened this Apr 22, 2025
@ChristianPraiss
Copy link
Contributor Author

Here you go: https://github.com/ChristianPraiss/multisite-caching-demo

Steps to reproduce:

Screenshot 2025-04-23 at 08 29 19

@jasonvarga
Copy link
Member

The paths should include the host, not arbitrary stings (you've used the site handles). In your provided repo's readme you make no mention about what nginx rule you are using, but you should be modifying the rewrite rule to include the host as mentioned in the docs.

CleanShot 2025-04-23 at 10 28 35

@ChristianPraiss
Copy link
Contributor Author

I see, in that case I guess the naming confused me a bit, because I wouldn't expect something named paths to contain a hostname. Will try it in our project once I get back to work tomorrow and close this issue if that fixes it! Thx so far!

@ChristianPraiss
Copy link
Contributor Author

I've update the readme in the demo to show what nginx rule I am using. It tries to serve the files from the static folder and if that doesn't work forwards it to the running application, pretty much what's recommended in the docs.

I think the issue really is that it will never map the requests to other sites properly if they do not incidentally match the locale.

Looking at the fallback if only a path string is provided I can see that it's initialized with the site handles ($paths = Site::all()->mapWithKeys(fn ($site) => [$site->handle() => $paths])->all()).
But in all other calls to getCachePath $site is never passed and defaults to null with the fallback to $site = $this->config('locale'), which will not match the handles of non-locale sites.

Changing the path keys to include the domains like you recommended didn't work for me, see the error below in my main project:

Screenshot 2025-04-24 at 10 16 23

@jasonvarga
Copy link
Member

jasonvarga commented Apr 24, 2025

The keys of the path array should be the site handles, not the domains.

Your sites.yaml file looks like this:

default:
  name: ...
  url: 'http://domainone.com/'
  locale: ...
web2:
  name: ...
  url: 'http://domaintwo.com/'
  locale: ...

Your paths should look like this:

'paths' => [
  'default' => public_path('static').'/domainone.com/',
  'web2' => public_path('static').'/domaintwo.com/',
]

When Statamic needs to write a file for a specific site, it will plop it into a path containing the host. That's why the array needs to be keyed by site handles. It's also why you're seeing undefined array key "default" because the default key (one of your site handles) doesn't exist in that array.

When Nginx tries to serve a page, it doesn't have knowledge of your Statamic site configuration. It only knows the host, so it tries to serve a file from a directory containing the host name. That's why the directories need to have host names.

@ChristianPraiss
Copy link
Contributor Author

ChristianPraiss commented Apr 25, 2025

Yeah I understood that part, I was expecting putting the domains in the paths to fail.

So my actual config I am encountering this issue with is this (exactly like your example above, only with different domains of course). When applying my patch it works perfectly, placing everything in it's domain-named folders in the public/static directory. Without it it only writes to the public/static/knbr.dev/ folder and the other domain folders are never created.

sites.yaml

default:
  name: '{{ config:app:name }}'
  locale: '{{ config:app:locale }}'
  url: 'knbr.dev'
b2b:
  name: '{{ config:app:b2b_name }}'
  locale: '{{ config:app:locale }}'
  url: 'b2b.knbr.dev'
company:
  name: '{{ config:app:company_name }}'
  locale: '{{ config:app:locale }}'
  url: 'company.knbr.dev'
magazine:
  name: '{{ config:app:magazine_name }}'
  locale: '{{ config:app:locale }}'
  url: 'magazine.knbr.dev'
nginx.conf

upstream octane_upstream {
  server 127.0.0.1:8000;
  keepalive 64;
}

server {
    listen 80;
    server_name knbr.dev b2b.knbr.dev magazine.knbr.dev company.knbr.dev;
    root /var/www/html/public;

    # Handle static files
    location / {
        try_files /static/${host}${uri}_$args.html @octane;
    }

    # Proxy to Octane
    location @octane {
        proxy_pass http://octane_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_request_headers      on;
    }
}
static_caching.php

'path' => [
    'default' => public_path('static').'/knauber.dev/',
    'b2b' => public_path('static').'/b2b.knbr.dev/',
    'magazine' => public_path('static').'/magazine.knbr.dev/',
    'company' => public_path('static').'/company.knbr.dev/',
],

@jasonvarga
Copy link
Member

jasonvarga commented Apr 25, 2025

The urls in the site.yaml file should be actual urls, not just the hosts. e.g. url: 'https://b2b.knbr.dev'

@ChristianPraiss
Copy link
Contributor Author

Yeah we even have the setup through env variables that way, but the problem remains the same. I guess we can go with a vendor patch for our specific project, but I still feel like this should work out of the box according to what's in the docs 😄

@ChristianPraiss
Copy link
Contributor Author

@jasonvarga Asking the other way around: Is there any reason my change would not work? Or a specific reason why the site parameter would not be passed in some cases?

I've just deployed our patched setup yesterday and it works properly, creating all cache folders named according to their sites domain and also reading back from them.

@jasonvarga
Copy link
Member

Everything appears to work on my end because we merge the locale of the "current" site into the static cache config here.

'locale' => Site::current()->handle(),

But, if for some reason Site::current() isn't detecting the correct site for you, it falls back to the default site, which is your issue.

Making Site::current() work is more important here.

Are you able to find out what Statamic\Facades\Site::current() and request()->getUri() output when you visit each of your sites? You could add a temporary route like this:

Route::get('/test', function () {
    dump(\Statamic\Facades\Site::current(), request()->getUri());
});

Then you can visit site1.com/test, site2.com/test, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants