The Route Tax
You’ve adopted vanity URLs for your Go modules. Clean import paths, platform-independent, looking good:
import "go.mycorp.com/auth"
import "go.mycorp.com/config"
import "go.mycorp.com/logger"
Then your team grows. New services get created every sprint. And every single one needs its own gvu add command:
gvu add go.mycorp.com/auth https://github.com/mycorp/auth.git
gvu add go.mycorp.com/config https://github.com/mycorp/config.git
gvu add go.mycorp.com/logger https://github.com/mycorp/logger.git
gvu add go.mycorp.com/cache https://github.com/mycorp/cache.git
gvu add go.mycorp.com/queue https://github.com/mycorp/queue.git
# ... 45 more
Fifty repos. Fifty routes. Every new repo means a pull request to update the routing config. Someone forgets? The new service’s go get fails and the team is blocked.
This is the route tax — the overhead of maintaining vanity URL mappings that should just be automatic.
One Rule, Every Repo
Wildcard routing eliminates the route tax. Instead of adding each repo individually, you write one rule that covers all repos under a path prefix:
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
That’s it. One line. Now every repo in your mycorp GitHub org is automatically importable:
import "go.mycorp.com/auth" // → github.com/mycorp/auth
import "go.mycorp.com/config" // → github.com/mycorp/config
import "go.mycorp.com/logger" // → github.com/mycorp/logger
import "go.mycorp.com/newthing" // → github.com/mycorp/newthing (works immediately!)
Create a new repo on GitHub? It’s instantly go get-able under your vanity domain. No config update. No pull request. No waiting.
This is Repo Wildcard (W1) — the * in the vanity path matches any single path segment, and the * in the repo URL gets replaced with the matched name.
Monorepo? Subdir Wildcard
What if your modules live in a monorepo instead of separate repos?
monorepo/
├── go/
│ ├── auth/go.mod
│ ├── config/go.mod
│ ├── logger/go.mod
│ ├── cache/go.mod
│ └── ... 20 more
You don’t need a repo wildcard. You need a Subdir Wildcard (W2):
gvu add go.mycorp.com/libs https://github.com/mycorp/monorepo.git '--subdir=*'
Every Go module under any subdirectory of the monorepo is now automatically importable:
import "go.mycorp.com/libs/auth" // → monorepo, subdir go/auth
import "go.mycorp.com/libs/config" // → monorepo, subdir go/config
import "go.mycorp.com/libs/cache" // → monorepo, subdir go/cache
Add a new module to the monorepo? Automatically importable — no route changes needed.
Note: Subdir wildcard requires Go 1.25+ in each module’s
go.mod. See our monorepo tutorial for the full setup.
Real-World Scenarios
Scenario 1: Multi-team organization
Your company has three teams, each with their own GitHub org:
# Platform team — 15 repos
gvu add 'go.mycorp.com/platform/*' 'https://github.com/mycorp-platform/*.git'
# Data team — 12 repos
gvu add 'go.mycorp.com/data/*' 'https://github.com/mycorp-data/*.git'
# Infra team — 8 repos in a monorepo
gvu add go.mycorp.com/infra https://github.com/mycorp-infra/monorepo.git '--subdir=*'
Three rules. Thirty-five modules. Zero maintenance.
Scenario 2: Override specific repos
Sometimes a repo doesn’t follow the convention. Maybe it’s in a different org, or it’s a fork. Use an exact route to override:
# Wildcard covers most repos
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
# But auth was moved to a different org
gvu add go.mycorp.com/auth https://github.com/security-team/auth.git
# And logger is a maintained fork
gvu add go.mycorp.com/logger https://github.com/upstream/logger.git
gvu resolves conflicts using priority rules:
| Priority | Rule type | When it matches |
|---|---|---|
| 1 (highest) | Exact route | Specific override for one repo |
| 2 | Subdir wildcard (W2) | Monorepo sub-packages |
| 3 (lowest) | Repo wildcard (W1) | Catch-all for the org |
Exact routes always win over wildcards. This lets you have a broad default with surgical exceptions.
Scenario 3: Gradual adoption
Not ready to go all-in on wildcards? Start with exact routes for your most-used modules, then add a wildcard when the route count gets annoying:
# Week 1: Just a few modules
gvu add go.mycorp.com/auth https://github.com/mycorp/auth.git
gvu add go.mycorp.com/config https://github.com/mycorp/config.git
# Week 5: Too many to manage individually
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
# The exact routes still work — they have higher priority
How It Works Under the Hood
When a developer runs go get go.mycorp.com/auth, the Go toolchain sends a request to https://go.mycorp.com/auth?go-get=1. gvu’s server:
- Checks exact routes first — is there a route for
go.mycorp.com/auth? - Checks subdir wildcards (W2) — does any W2 rule’s prefix match?
- Checks repo wildcards (W1) — does any W1 rule’s pattern match?
When a wildcard matches, the server generates the go-import meta tag on the fly:
<!-- W1 match: go.mycorp.com/auth → github.com/mycorp/auth -->
<meta name="go-import" content="go.mycorp.com/auth git https://github.com/mycorp/auth">
<!-- W2 match: go.mycorp.com/libs/auth → github.com/mycorp/mono subdir go/auth -->
<meta name="go-import" content="go.mycorp.com/libs/auth git https://github.com/mycorp/mono go/auth">
No static HTML files. No Nginx config. The meta tags are generated dynamically from your wildcard rules.
Prerequisites
Wildcard routing is a Pro feature. Here’s what you need:
| Requirement | Details |
|---|---|
| Pro account | Run gvu subscribe — 50 routes, custom domains, wildcards |
| Custom domain | go.mycorp.com with DNS CNAME pointing to gvu |
gomod.io not supported | Wildcards require your own domain |
The custom domain requirement makes sense — a wildcard under gomod.io would let anyone claim any path under that domain, which is a security risk.
Setting up the custom domain
# 1. Add a CNAME record
go.mycorp.com CNAME gomodvanityurls.com
# 2. Verify with gvu
gvu domain add go.mycorp.com
gvu domain verify go.mycorp.com
# 3. Create your wildcard route
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
HTTPS certificates are provisioned automatically — no cert management needed.
Limits and Gotchas
A few things to keep in mind:
- One
*per rule — you can’t dogo.mycorp.com/*/*(two wildcards in the path) - W1 and W2 are mutually exclusive per route — a single route uses either repo wildcard or subdir wildcard, not both
- Each wildcard counts as 1 route against your Pro quota (50 total)
- Shell quoting matters — always wrap arguments containing
*in single quotes:
# ✅ Correct
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
# ❌ Wrong — shell expands the glob
gvu add go.mycorp.com/* https://github.com/mycorp/*.git
Before and After
| Aspect | Individual routes | Wildcard routing |
|---|---|---|
| Setup | One command per module | One command per org/monorepo |
| New repo | Must add route first | Works immediately |
| Route count | N repos = N routes | N orgs = N rules |
| Maintenance | Update on every new service | Set and forget |
| Override | N/A | Exact routes take priority |
Summary
If you have more than 5 Go modules, wildcard routing pays for itself immediately:
# Multi-repo org: one rule covers everything
gvu add 'go.mycorp.com/*' 'https://github.com/mycorp/*.git'
# Monorepo: one rule covers all sub-packages
gvu add go.mycorp.com/libs https://github.com/mycorp/mono.git '--subdir=*'
# Mix and match with exact overrides
gvu add go.mycorp.com/special https://github.com/other/special.git
One rule per org. One rule per monorepo. Exact routes for exceptions. That’s the entire routing config for a 50-module organization.
Upgrade to Pro and set up wildcard routing in under 5 minutes.