As Go Learning & Documentation v2 gradually expanded from A Tour of Go to Learn, Docs, and the full site, I encountered a problem that was not very obvious before:
Does the existence of an English page on go.dev mean that every local language site should automatically have a corresponding local URL?
At first glance, this question seems easy to answer.
Since the English site has:
https://go.dev/some-paththen it seems the Chinese site should also have:
https://chinese-site/some-pathEven if the Chinese content is not yet ready, we could create a page first.
Or, when accessing this local URL, we could redirect to the English go.dev.
This way, at least there would be no 404.
That sounds reasonable.
But after putting Learn, Docs, navigation, canonical, sitemap, and search engine indexing together, I increasingly felt that:
“No 404s” does not equal “correct site structure”.
Sometimes, a real 404 is actually more correct than a fake local page.
This post organizes v2’s final thoughts on this issue.
The most common misconception: because the English page exists, the local page should exist too
Suppose there is an official page on go.dev.
The local language translation is not ready yet.
The most direct thought might be:
English page exists → create corresponding local URLAs for the content, it can be added later.
We could even have this local URL automatically redirect back to the English page first.
The benefits of doing this are obvious:
Users won’t see a 404.
Internal links are less likely to “break”.
The routing looks more complete.
When search engines crawl this URL, they also get a normal response.
But this actually confuses two different things:
The official English page exists.
and:
This local language site has officially covered this page.
These are not the same thing.
v2 starts treating “local page exists” as a formal commitment
Before, when it was just the Tour, the site’s content scope was relatively fixed.
But after expanding to the entire Go Learning & Documentation, the variety of pages increased.
Including:
- Tour content;
- Learn content;
- Docs content;
- Site shell;
- Security docs;
- Other content packages that may be added later.
At this point, whether a local URL exists can no longer be just a routing-level decision.
It actually represents a stronger commitment:
This page already belongs to the official content coverage of this language version.
That is, as long as a local route exists, users will naturally assume:
This page is part of this language site.
Search engines will also determine who this page belongs to based on the actual response, canonical, sitemap, and internal links.
So v2 later elevated “whether this local page exists” into a formal state problem:
A corresponding local page should only be created when there is official local content coverage.
What counts as “official local content coverage”
“Coverage” here does not simply mean having a copy of the content in the database or file system.
At least from v2’s workflow perspective, for content to truly become part of the local site, it must go through a complete quality chain.
This roughly includes:
Content enters translation scope
→ generate candidate translation
→ machine check
→ independent quality review
→ revision and re-review
→ final all-A sign-off
→ content package sealed
→ integrated site review
→ activation
→ preview and production environment validationTherefore:
“Having a translation draft” does not equal “having official local content coverage”.
Even:
“The translation unit has received an A” also does not automatically mean this URL is ready for production.
Because the page ultimately still needs to enter the content package, be integrated into the site, activated, and validated in production.
This is also what was mentioned in the previous post:
Each layer proves something different.
Without local coverage, we should continue linking to the official English page
The rule I ultimately agree with more is:
Only pages with official local coverage should use local links.
If there is no official local coverage yet, continue pointing to the official English page:
Official English URL: https://go.dev/<canonical>For example, the document corresponding to a navigation item has not been translated yet.
At this point, we shouldn’t forcefully change it to:
https://local-site/<canonical>just to “make all links look local”.
Instead, we should keep linking to:
Official English URL: https://go.dev/<canonical>Once this content has truly completed translation, review, integration, and activation, we then switch the link to the local site.
This means the local site’s content scope is not:
“Whatever the English site has, I’ll just copy all the URLs over first.”
but rather:
“Whatever content I have officially completed, those are the local URLs I make public.”
Why we shouldn’t create “fake local pages”
So-called fake local pages can take several forms.
For example:
The page URL exists, but the content is actually still in English.
Or:
The page only has one sentence:
This page has not been translated yet.
Or:
The page immediately redirects to go.dev.
These solutions all seem to avoid 404s.
But they all share a common problem:
The local URL exists, but there is no real corresponding local content.
This decouples the concepts of “URL exists” and “local coverage exists”.
Later, the system will find it hard to answer:
Which pages have actually been localized?
“Creating empty shells first” distorts coverage rates
Suppose the English site has 100 pages.
Only 60 have actually completed Chinese translation.
If, for routing completeness, we create all 100 local URLs first, then from a routing perspective:
Number of local pages = 100But the actual translation coverage is only:
Official local content = 60At this point, two types of “coverage rates” exist in the system simultaneously.
One is routing coverage.
One is content coverage.
Over time, this easily leads to:
The system thinks the pages already exist, so navigation, sitemap, and canonical all start treating them as local content.
But in reality, those 40 pages haven’t been localized at all.
So v2 prefers:
Whether a URL exists should itself reflect actual content coverage as closely as possible.
Automatically redirecting to the English page is not real local coverage either
Another common approach is:
Access local URL → 302 redirect to go.dev English pageThis way, users can at least see the content.
But semantically, this local URL is still not a real local page.
It is just a transit entry point.
This brings several problems.
First, the user sees a local language domain name.
They will naturally expect:
After clicking, they will still be on this language version.
But the result is a sudden jump to the English go.dev.
This experience is actually no more natural than directly linking to the English page.
Second, it creates a URL within the site that has no real content.
Later, navigation, sitemap, canonical, and search engine crawling all need extra rules for this “virtual page”.
Finally, the most troublesome part is:
The system can no longer easily use route existence to determine whether a page actually has local content.
So v2 chose a simpler and more honest approach:
If there is no local content, do not fake a local page.
A real 404 is not a failure; it expresses the true state
This is also one of my biggest shifts in understanding 404s recently.
It used to be easy to think:
If a website shows a 404, there is a problem.
But in reality, a 404 simply means:
The resource represented by this URL does not exist.
If a page has not officially entered local content coverage, then accessing the corresponding local URL:
https://local-site/not-yet-covered-pathreturning a real 404 is actually correct.
Because the system is accurately expressing:
This local page does not currently exist.
The real problem is not the 404 itself.
It is:
A page that should clearly exist returns a 404.
Or:
A page that clearly doesn’t exist pretends to exist.
These two problems must be distinguished.
This is actually the opposite of the “soft 404” concept
If a URL returns HTTP 200, but the page content is actually just:
No content yet.
Or:
Please visit the English page.
Then from the perspective of users and search engines, it is likely not a truly valuable page.
In contrast:
No official content → real 404is actually clearer.
So v2 does not pursue:
Having all possible local URLs return 200.
Instead, it pursues:
The returned status should accurately reflect whether the content actually exists.
canonical cannot be separated from actual content coverage
At this point, we run into another very important issue: canonical.
It can be simply understood as:
This page tells search engines which URL is its official version.
If a local page does exist and belongs to official local coverage, it should have a canonical that follows current site rules.
But if the page itself doesn’t exist at all, we cannot prematurely manufacture a canonical just for “structural completeness”.
Otherwise, a very strange state will appear:
Page does not exist
but it is in the sitemap
canonical also declares it exists
and navigation still links to itThis is actually sending contradictory information to different systems simultaneously.
So v2’s principle is:
canonical must be built on real, complete local content coverage.
sitemap also cannot become a “future plan list”
The same logic applies to sitemaps.
The purpose of a sitemap is not:
“These URLs might have content in the future.”
It should tell search engines:
These are the pages that currently truly want to be discovered and indexed.
So pages without official local coverage should not prematurely enter the local sitemap just because a page with the same path exists on go.dev.
Otherwise, the following will happen:
sitemap declares page exists
→ search engine visits
→ no real local contentOr:
sitemap declares local page
→ page then redirects to English siteThese states all make the site’s content boundaries blurry.
So v2 prefers:
sitemap only includes pages that have truly completed local coverage.
Navigation must also follow the same rules
Navigation is where users have the most direct contact.
Suppose the official English site navigation has a certain document.
The local version has not been translated yet.
At this point, there are actually two choices:
First:
Navigation → local URL → 404 or redirect to EnglishSecond:
Navigation → official English go.devI prefer the second option.
Because it is more direct.
Before clicking, the user sees an available navigation entry.
After clicking, they go directly to the content that actually exists.
Rather than first entering a fake local path, and then having the system decide where to redirect the user.
This also makes the link state very clear:
Official local coverage exists → link to local page
No official local coverage → link to go.dev official pageThis prevents a disconnect between “route exists” and “content exists”
I think this point is very important.
For a long-term maintained multilingual site, it is best to keep the following concepts as consistent as possible:
Local route exists
Local content exists
Local content has passed the official quality process
Navigation can link to local page
sitemap can include this page
canonical can declare this pageIf these states are out of sync for a long time, the system will become increasingly complex.
For example:
The route exists, but the content does not.
The content exists, but hasn’t been reviewed yet.
The review passed, but it hasn’t been activated.
It has been activated, but the sitemap hasn’t been updated yet.
And navigation links to future pages prematurely.
This way, every part requires extra judgment.
And the further along it goes, the harder it is to know:
Which layer actually represents the true state.
So v2 prefers to establish a clearer boundary:
Official local coverage is the prerequisite for a local page to enter the site surface.
/doc/security/** made me value the canonical rule itself more
During the Docs expansion, there was a more specific example:
The official canonical path for the Security docs is:
Official path: Official path: Official path: Official path: Official path: /doc/security/**This kind of rule looks like just a path difference.
But it made me further realize:
canonical cannot be guessed just by “looking about right”.
What canonical a content page truly corresponds to must obey the current official content structure.
Otherwise, the local site can easily:
- path concatenation;
- legacy old routes;
- page categorization changes;
- content package expansion;
generate a URL that seems reasonable but is actually not the official canonical.
So v2 tends to handle this kind of path rule by:
Reading the official content authority, rather than guessing based on page names.
Local coverage is actually a state machine
Reaching this point, I feel that “whether a page has a local version” can no longer be simply understood as:
Two simple states: Two simple states: Two simple states: Two simple states: Two simple states: true / falseIt is closer to:
English official content exists
→ not yet in local translation scope
→ entered translation process
→ translation and review completed
→ content package completed
→ integrated site passed
→ activated
→ local coverage officially effectiveNone of the intermediate stages can prematurely be equated to:
“An official local page already exists in the production environment.”
This is essentially the same logic as the discussions on current, historical reuse, and invalidation propagation in the previous blog posts.
The system needs to know:
What has been completed, what is just in progress, and what has truly become the current production state.
Surface PASS also does not mean Production ready
This point is especially obvious for this issue.
Suppose the integrated site review has passed.
This proves:
The currently assembled site content has no issues found within the review scope that need to block further progress.
But the production environment still has real routing, canonical, sitemap, and browser behavior.
Therefore:
Integrated site review PASS ≠ ready to declare production environment correctProduction validation still needs to confirm:
- local pages that should exist do exist;
- local pages that should not exist actually return 404;
- links point to local pages when local coverage exists;
- links point to official go.dev when there is no local coverage;
- canonical points correctly;
- sitemap does not prematurely expose unfinished pages.
So:
Integrated site review is an important gate before entering production, but it cannot replace production environment validation.
“Page does not exist” should also be tested
When testing sites before, it was natural to focus on testing:
Which pages should open successfully?
But reaching v2, I think we should also test another thing:
Which pages should clearly not exist?
For example:
A certain English page has not yet entered official local coverage.
Then its corresponding local URL:
should return a real 404instead of:
200 empty pageand not:
302 redirect back to English pageThis kind of “negative testing” is actually very important.
Because only this way can we confirm that the local content boundary hasn’t been unintentionally expanded.
A mature localized site shouldn’t pretend to have translated all content
I feel there is a more important issue behind this than the technical implementation.
Multilingual sites easily generate a kind of pressure:
Since it’s the Chinese version, all pages ideally should have a Chinese version URL.
But actual translation work must advance gradually.
Especially when:
- content is continuously updated;
- new content packages are constantly added;
- review requirements are high;
- independent re-review is required;
- production launch also requires multi-layer validation;
In such a system, it’s impossible to require all content to be completed simultaneously at all times.
So rather than “looking complete”, I prefer the site to achieve:
The completed parts are clearly localized.
The uncompleted parts clearly link to the official source.
Instead of creating an illusion:
“All content seems to have a local version.”
that isn’t real.
This also makes adding new content packages later simpler
Suppose a new Go content package is added in the future.
If the architecture requires:
All official English URLs must immediately exist locally.
then every time a content package is added, a large number of routing, redirect, canonical, and sitemap exceptions must be extended first.
And then content is slowly filled in.
The whole system will first generate a large batch of “future pages”.
But if the official coverage principle is adopted:
New content enters official translation process
→ local coverage established after completion
→ enters local site after activationthen expanding new content packages will be much more natural.
The site only makes public the completed parts.
Uncompleted parts continue using the official English source.
Local coverage, canonical, sitemap, and 404 must actually be one set of rules
Looking back now, I feel these problems that originally seemed independent of each other:
- local content coverage;
- routing;
- navigation;
- canonical links;
- sitemap;
- 404 status;
- official English fallback;
actually cannot be designed separately.
They must revolve around the same fact:
Does this page currently have official local coverage?
If the answer is “yes”, then:
use local URL
→ local page actually exists
→ canonical uses correct local official URL
→ sitemap can include it
→ navigation can link to local pageIf the answer is “no”, then:
do not fake local page
→ local path returns real 404
→ sitemap does not include it
→ do not generate fake local canonical
→ when content access is needed, link directly to go.dev official pageThis way, the whole system won’t conflict with itself.
True state is more important than “looking complete”
In the end, this post returns to the same problem encountered in many of v2’s designs:
Are we optimizing surface completeness, or are we maintaining the true state?
If we pursue surface completeness, it’s easy to end up with:
all URLs exist
all links are local domains
all pages return 200It looks very nice.
But a large number of those pages might not have actually been localized.
And what v2 wants to pursue is:
completed → clearly exists
not completed → clearly does not existIf there is no local coverage, honestly use the official English source.
If a local URL shouldn’t exist, return a real 404.
If a page has been officially completed, let navigation, canonical, sitemap, and the production environment all acknowledge it together.
This set of rules might not be as pretty as “all pages have local URLs”.
But it is more honest.
And it is also easier to maintain long-term.
The resulting principles
This part of the design can now be summarized into a few fairly stable rules.
First:
The existence of an official English page does not automatically mean a local page should exist.
Second:
Only when official local content coverage is completed should the corresponding local URL be created.
Third:
Without local coverage, continue linking directly to the official
https://go.dev/<canonical>.
Fourth:
Do not use fake local pages, and do not use 302 redirects to disguise local coverage.
Fifth:
Local URLs lacking official local coverage should return a real 404.
Sixth:
canonical and sitemap only acknowledge fully established local content coverage.
Seventh:
The official canonical must come from the current content authority, and cannot be guessed based on page names or historical paths.
Eighth:
Passing the integrated site review does not mean production environment validation is complete.
The ultimate goal is not:
making the local site look like it has exactly the same number of pages as go.dev.
But rather:
making every publicly existing local page truly represent a piece of local content that is completed, reviewed, and accountable to the user.

