没有不值得去解决的问题,也没有不值得去学习的技术!

Having an English Page Doesn’t Mean a Local Page Should Exist: How v2 Handles Local Content Overrides, Canonicals, and True 404s

Having an English Page Doesn't Mean a Local Page Should Exist: How v2 Handles Local Content Overrides, Canonicals, and True 404s

作者:

在

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:

Plaintext
https://go.dev/some-path

then it seems the Chinese site should also have:

Plaintext
https://chinese-site/some-path

Even 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:

Plaintext
English page exists → create corresponding local URL

As 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:

Plaintext
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 validation

Therefore:

“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:

Plaintext
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:

Plaintext
https://local-site/<canonical>

just to “make all links look local”.

Instead, we should keep linking to:

Plaintext
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:

Plaintext
Number of local pages = 100

But the actual translation coverage is only:

Plaintext
Official local content = 60

At 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:

Plaintext
Access local URL → 302 redirect to go.dev English page

This 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:

Plaintext
https://local-site/not-yet-covered-path

returning 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:

Plaintext
No official content → real 404

is 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:

Plaintext
Page does not exist
but it is in the sitemap
canonical also declares it exists
and navigation still links to it

This 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:

Plaintext
sitemap declares page exists
→ search engine visits
→ no real local content

Or:

Plaintext
sitemap declares local page
→ page then redirects to English site

These 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:

Plaintext
Navigation → local URL → 404 or redirect to English

Second:

Plaintext
Navigation → official English go.dev

I 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:

Plaintext
Official local coverage exists → link to local page
No official local coverage → link to go.dev official page

This 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:

Plaintext
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 page

If 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:

Plaintext
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:

Plaintext
Two simple states: Two simple states: Two simple states: Two simple states: Two simple states: true / false

It is closer to:

Plaintext
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 effective

None 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:

Plaintext
Integrated site review PASS ≠ ready to declare production environment correct

Production 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:

Plaintext
should return a real 404

instead of:

Plaintext
200 empty page

and not:

Plaintext
302 redirect back to English page

This 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:

Plaintext
New content enters official translation process
→ local coverage established after completion
→ enters local site after activation

then 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:

Plaintext
use local URL
→ local page actually exists
→ canonical uses correct local official URL
→ sitemap can include it
→ navigation can link to local page

If the answer is “no”, then:

Plaintext
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 page

This 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:

Plaintext
all URLs exist
all links are local domains
all pages return 200

It looks very nice.

But a large number of those pages might not have actually been localized.

And what v2 wants to pursue is:

Plaintext
completed → clearly exists
not completed → clearly does not exist

If 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.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系