# Caddy + Hugo: Language-Aware Redirection Based on Accept-Language


## Background

My blog (built with Hugo + FixIt theme) supports Chinese and English. The content structure looks like:

```
content/
├── posts/
│   ├── podman.zh-CN.md
│   ├── podman.en.md
│   ├── custom-caddy-build.zh-CN.md
│   └── custom-caddy-build.en.md
└── ...
```

After Hugo builds, it generates separate language directories:

```
/www/blog/
├── zh-cn/
│   ├── posts/
│   │   └── custom-caddy-build/
│   └── 404.html
├── en/
│   ├── posts/
│   │   └── custom-caddy-build/
│   └── 404.html
└── index.html (root redirect)
```

The root path `/` is typically empty. When a visitor reaches `www.example.com`, I want Caddy to automatically redirect them to the right language version based on their browser's language preference. If the target language's article doesn't exist, fall back to the other language. If neither exists, show the default 404 page.

## Complete Configuration

```caddyfile
www.example.com {
    root * /www/blog

    # Root path language redirect
    route / {
        @chinese header_regexp Accept-Language ^zh
        redir @chinese /zh-cn/ 302

        redir * /en/ 302
    }

    # Subpath language detection
    @needs_localization not path / /en/* /zh-cn/*

    route @needs_localization {
        @zh_target_exists {
            header_regexp Accept-Language ^zh
            file {root}/zh-cn{uri} {root}/zh-cn{uri}/index.html
        }
        redir @zh_target_exists /zh-cn{uri} 301

        @en_target_exists {
            file {root}/en{uri} {root}/en{uri}/index.html
        }
        redir @en_target_exists /en{uri} 301
    }

    file_server

    handle_errors 404 {
        rewrite * {root}/en/404.html
        file_server
    }
}
```

## Step-by-Step Breakdown

### Root Path Language Redirect

```caddyfile
route / {
    @chinese header_regexp Accept-Language ^zh
    redir @chinese /zh-cn/ 302

    redir * /en/ 302
}
```

When visiting `www.example.com/` (root path):

- Check if the `Accept-Language` header starts with `zh` (covers `zh-CN`, `zh-TW`, `zh-HK`, etc.)
- Chinese users → `/zh-cn/` (302 temporary redirect)
- Everyone else → `/en/` (default fallback)

We use 302 instead of 301 because language preference is temporary - a user might switch their browser language, and 301 would be cached.

### Subpath Language Detection

```caddyfile
@needs_localization not path / /en/* /zh-cn/*
```

This named matcher defines which paths need language detection. Three cases are excluded:

- `/`: Root path is already handled above
- `/en/*`: Already an English path
- `/zh-cn/*`: Already a Chinese path

So when a user visits a neutral path like `/posts/custom-caddy-build/` (no language prefix), detection is triggered.

```caddyfile
route @needs_localization {
    @zh_target_exists {
        header_regexp Accept-Language ^zh
        file {root}/zh-cn{uri} {root}/zh-cn{uri}/index.html
    }
    redir @zh_target_exists /zh-cn{uri} 301

    @en_target_exists {
        file {root}/en{uri} {root}/en{uri}/index.html
    }
    redir @en_target_exists /en{uri} 301
}
```

Detection logic:

1. Check **Chinese condition**: user language is Chinese `^zh` **and** the Chinese version's path exists (probed via `file` directive)
2. If Chinese version exists → 301 redirect to `/zh-cn{uri}`
3. Then check **English condition**: does the English version's path exist?
4. If English version exists → 301 redirect to `/en{uri}`
5. If neither exists → fall through to `file_server` (will hit 404)

The `file` directive tries two patterns: `{uri}` for directory-style access (Hugo generates directory structures) and `{uri}/index.html` for file-style access. If either exists, the language version is considered available.

### 404 Error Handling

```caddyfile
handle_errors 404 {
    rewrite * {root}/en/404.html
    file_server
}
```

When a user visits a path that doesn't exist (in any language), show the English 404 page. You could make this smarter by checking `Accept-Language` for locale-specific 404s, but a simple uniform 404 page hardly justifies that complexity.

## References

- [Caddy redir Directive](https://caddyserver.com/docs/caddyfile/directives/redir)
- [Caddy file Matchers](https://caddyserver.com/docs/caddyfile/matchers#file)
- [Caddy header_regexp Matchers](https://caddyserver.com/docs/caddyfile/matchers#header-regexp)
- [Hugo Multilingual Mode](https://gohugo.io/content-management/multilingual/)


---

> Author: Nite  
> URL: https://www.nite07.com/en/posts/caddy-hugo-multilingual/  

