301 redirects for default language after update to kirby3

Hey Community

After my recent upgrade vom kirby2 to kirby3 I noticed, that my language detection does not work anymore.
In my kirby2 site I had the url param for the default language german set to /.
According to the issue https://github.com/getkirby/kirby/issues/1924 this does not work anymore in kirby3 and I had to set it to null or /de. After applying this change, the language detection works perfectly smooth again.

Now I would like to assure, that the old url’s are redirected correctly. I tried to apply some stuff in the .htaccess, but could not make it work. I mostly ended up with too many redirects.

Is there an effective way to do this via routes? Or do I have to apply all the redirects manually?
My goal is that all url’s without a language param redirect to the equivalent of the request with the de param. However, requests with en, fr and it should not be influenced by this.

Thanks in advance for your help!

Hm, these redirects should actually happen automatically. If you enter a url without the language parameter, it will automatically redirect to the URL with the default language parameter.

they actually do. but with a 302 redirect. But since these url’s won’t come back in that case, it should be a 301 redirect to not harm the SEO.

Try this route:

  [
    'pattern' => '/(:all)',
    'language' => 'de',
    'action' => function($language, $id) {
        if ($page = page($id)) {
          go($page->url(), 301);
        }
        
    },
  ],

I added the route to my config, but i’m still ending up with a 302 for all requests…

Hm, then Kirby’s route kicks in earlier (which actually makes sense).

Then you have to do it on the .htaccess level, I guess.

Can you show us your files & folders layout, how does your old and new urls’s look like and your .htaccess with what you have tried so far?

filestructure

mainfolder
-- assets
-- content
-- kirby
-- media
--- site1
--- site2
--- site3
--- panel
-- site
--- site1
---- cache
---- config
---- sessions
--- site2
---- cache
---- config
---- sessions
--- site3
---- cache
---- config
---- sessions
--- shared
---- accounts
---- blueprints
---- controllers
---- languages
---- plugins
---- snippets
---- templates

language files
de (default):

return [
  'code' => 'de',
  'default' => true,
  'locale' => 'de_CH',
  'name' => 'Deutsch',
  'url' => null,
  'translations' => [
    ...
  ]
];

the other languages:

return [
  'code' => 'en',
  'locale' => 'en_US',
  'name' => 'English',
  'url' => '/en',
  'translations' => [
    ...
  ]
];

URLS
old urls in kirby2:
default language (de):

https://www.example.com
https://www.example.com/subsite

other languages:

https://www.example.com/en
https://www.example.com/en/subsite-en

new urls in kirby3:
default language (de):

https://www.example.com/de
https://www.example.com/de/subsite

other languages:

https://www.example.com/en
https://www.example.com/en/subsite-en

hope that gives you an overview. let me know if you need more details somewhere.

one of the things i tested for the htaccess already:

RewriteRule !^(de|en|fr|it)/ /de%{REQUEST_URI} [R=301,L]

any ideas @Adspectus?

Sorry for the late reply. I think the problem is that your rewrite rule interferes with Kirbys builtin routes which will be applied when having a multilang setup. Your problem seems only occur with URLs that does not contain the language value and should therefore be redirected to /de/… - if I get it right. However you can not just rewrite everything which does not contain the language code to an URL with the language code as this will disturb Kirbys internal mechanism to sort things out. So far as I can tell you have at least 3 options to solve this:

  1. Make Kirby believe that you do not have a multilang setup and rebuild all necessary routes with rewrite rules by yourself. Honestly I do not think that this is the best solution or even a good solution at all as you need a deep understanding of Kirbys internals and it requires a lot of caretaking.

  2. If it is correct what can be derived from you description above and you have only URLs that start with the string “subsite” then you could try to only rewrite all URLs of kind “/subsite/” to “/de/subsite/” which should also be possible with “Redirect” (or “RedirectMatch”) configuration directives and which is the recommended way in that case as far I remember from the Apache docs.

  3. I inspected the Kirby source code and found that the default status code 302 is hard-coded in kirby/src/Cms/responder.php with an option to change it with a parameter to the redirect method:

    public function redirect(?string $location = null, ?int $code = null)
    {
     $location = Url::to($location ?? '/');
     $location = Url::unIdn($location);
    
     return $this
         ->header('Location', (string)$location)
         ->code($code ?? 302);
    }
    

The LanguageRoutes.php however, where all the redirects being created for multilang setup, does not make use of this parameter. Since there seems to be also no configuration option for changing the default value 302 into 301, you could change it in the file mentioned above, which of course is a dirty hack.

Saying that, I have to admit that I do not know enough about the internals of Kirby and the reasons which led to the default status code of 302 being hard-coded in the file mentioned above. Maybe it is possible to change that to a configurable setting or maybe it is possible to change this in a more compatible way - which is a question to the Kirby developers around here.

The reason behind making 302 the default redirect is that 301 redirects are cached by browsers. Since you cannot influence this on the client side, 301 redirects may have unwanted side effects if you ever decide you want to use those redirected URLs again.