Building a Bilingual Information Site That Search Engines Can Actually Read
NepalInfoSpot serves every page in English and Nepali, with prices that change daily. The engineering problems that creates, and how we solved them.
We built and run NepalInfoSpot, an information hub covering the things people in Nepal look up constantly: today's gold and petrol prices, the Nepali date, how much tax you owe, what a passport application involves.
It sounds simple. Two requirements make it not simple:
- Every page exists in two languages. English at
/prices/gold, Nepali at/ne/prices/gold. - A lot of the content changes daily. Gold, silver, petrol and forex all move.
Those two together create most of the interesting engineering. Here is what they actually demand.
The bilingual problem is not translation
Translating strings is the easy part. The hard part is telling a search engine that two URLs are the same page in different languages, rather than two pages competing with each other.
Get this wrong and one of two things happens: Google picks one version and drops the other, or it serves the English page to someone searching in Nepali. Both are silent failures — nothing errors, traffic is just quietly worse than it should be.
The mechanism is hreflang, and the rule people miss is that the annotations have to be reciprocal and self-referential. Every page in a language cluster must list every version including itself. So the English gold price page carries all three of these, and the Nepali page carries the identical set:
<link rel="alternate" hreflang="en" href="https://nepalinfospot.com/prices/gold" />
<link rel="alternate" hreflang="ne" href="https://nepalinfospot.com/ne/prices/gold" />
<link rel="alternate" hreflang="x-default" href="https://nepalinfospot.com/prices/gold" />
If the Nepali page points at the English one but the English page does not point back, Google discards the whole cluster. A one-way annotation is not a weak signal — it is no signal.
Two things that pair with it:
- A self-referential canonical on each version. The Nepali page canonicalises to itself, not to the English page. This trips people up constantly, because it feels like duplicate content. It is not — it is the same content in a different language, which is exactly what
hreflangexists to express. Canonicalising/ne/...to/...deletes your Nepali site from the index. - The right
langattribute.<html lang="ne">on Nepali pages,lang="en"on English ones. It matters for screen readers and for how browsers pick fonts for Devanagari.
In Next.js, the App Router expresses the first part through metadata:
export const metadata: Metadata = {
alternates: {
canonical: '/prices/gold',
languages: {
en: '/prices/gold',
ne: '/ne/prices/gold',
'x-default': '/prices/gold',
},
},
};
Daily data changes what "static" means
A gold price page is a static page for about a day. That is an awkward duration: too long to render on every request, too short to bake in at deploy time.
Rebuilding the whole site whenever a price moves is the obvious wrong answer — it couples your content freshness to your deploy pipeline, and it means a build failure becomes a stale-content incident.
Incremental static regeneration fits this shape well. Pages are served static and revalidated on an interval, so readers get a static-fast response and the data is never more than one interval stale:
export const revalidate = 3600;
The part worth thinking about carefully is what happens when the upstream source is unavailable. A price page that renders an error is worse than one showing yesterday's number with a visible timestamp. Prices should degrade to stale and labelled, never to missing — so the fetch layer keeps the last good value and the page states when it was captured.
That timestamp is not just engineering hygiene. For a page whose entire value is being current, saying "as of 8 September, 10:00" is the difference between a reader trusting the number and bouncing.
Structured data earns the real estate
For a question-shaped query — "what is the gold price in Nepal today" — the answer often gets read directly in the results page. You can either supply that answer in a form search engines can parse, or let them guess.
Every content page on the site carries FAQPage and BreadcrumbList schema. Breadcrumbs turn a bare URL in the result into a readable Home > Prices > Gold trail, which measurably helps click-through. The FAQ blocks mark up the questions people actually arrive with.
One rule: the marked-up answer must be visible on the page. Schema describing content a user cannot see is a structured data violation, and it is the fastest way to lose rich results entirely.
What I would tell anyone building this
- Decide your URL strategy before you write a line. Subdirectory (
/ne/), subdomain, or separate domain. We used subdirectories because they inherit the domain's authority. Changing later means redirecting every URL. - Ship
hreflangwith the second language, not after it. Retrofitting it means a period where your two versions compete, and recovery takes longer than the fix. - Give freshness a visible timestamp. It builds reader trust and it makes staleness a bug you can see rather than one you discover months later.
- Treat the upstream data source as unreliable, because it is. Every scraped or third-party feed eventually returns a 500, an empty body, or something absurd. Decide what the page shows in each case before it happens.
Bilingual sites are not twice the work of a single-language one. They are roughly 1.2x the work, plus a set of annotation rules that are unforgiving if you get them wrong — and completely reliable once you have.
If you are building something in this shape and want a second pair of eyes, get in touch.