DEV Community

Xiaohongshu share links have two domains, and one of them will fail silently

Xiaohongshu Share Links Have Two Domains, and One of Them Will Fail Silently

Originally published at linkdigest.dev, where I build this. Every number here came from a live link on 2026-09-08 and 2026-09-10. The failure described is one this service shipped to a real user. If you have a host table that maps domains to platforms, and Xiaohongshu is in it, check what you wrote. There is a good chance it says xhslink.com and nothing else. That was true here, and it cost us the first person who ever signed up.

Two Domains, Same Platform

Share a note from the Xiaohongshu iOS app today and the clipboard gets something like:

https://xhslink.cn/o/AxnRePgIokn

Older shares, and links that travel through the desktop site, use .com:

https://xhslink.com/o/1WiQ1QI6Uc0

Both are real. Both resolve to the same place.

Following the .cn one

$ curl -sIL 'https://xhslink.cn/o/AxnRePgIokn' -A '<desktop UA>'

...

https://www.xiaohongshu.com/discovery/item/6a9d0eb2000000002a024480?app_platform=ios&app_version=9.45&xsec_source=app_share&type=normal&xsec_token=CB7rCz...&share_id=310c7d81...

The second link we tested carried type=video instead of type=normal, which matters later.

Why the Miss is Invisible

A URL router usually looks like this, and ours did:

(re.compile(r"(^|\.)(xiaohongshu\.com|xhslink\.com)$"), Platform.xiaohongshu)

A .cn share does not match. In most systems it does not raise - it falls through to whatever generic handler sits at the end of the chain. Ours fetches the page and extracts what it can from the HTML. And that works, in the sense that it returns 200. Xiaohongshu's page ships a <title> and a meta description containing the whole caption, so a generic extractor comes back with a title, an author of "小纒书", and the caption. Enough to look like an answer. It is not, and the reason is covered at length in what it actually takes to read a Xiaohongshu post from a server: strip the <script> tags from that page and 264 characters of navigation chrome are left. The note body is not in the rendered DOM at all. A generic extractor is reading the wrapper, not the post.

Comparison of Extracted Data

Here is what the same link returned before and after the domain was added to the table:

| | .cn unrecognised | .cn recognised | |---|---|---| | platform | web | xiaohongshu | | images described | 0 | 1 | | on-screen text fragments | 0 | 4 | | transcript | 0 | 0 | | author | 小纒书 | the actual account name | | posted date | empty | 2026-09-06 | degraded | ["extracted from page HTML only - no media, transcript, or platform metadata"] | []

And the second link, the type=video one:

| | before | after | |---|---|---| | platform | web | xiaohongshu | | transcript source | none | asr | | on-screen text fragments | 0 | 56 |

A transcript existed the whole time. Nothing errored. The request succeeded in seven seconds and returned a post with no transcript in it. That is the part worth taking away. A silent downgrade is worse than a crash, because a crash tells the user to retry and a downgrade tells them this is what your product can do. The person who hit this did not file a bug. They opened their usage page, their settings, the docs, and left.

Fixing the Issue

Check your own table If you maintain anything that classifies Xiaohongshu URLs, the fix is one character class:

(re.compile(r"(^|\.)(xiaohongshu\.com|xhslink\.(com|cn))$"), Platform.xiaohongshu)

And separately, if you expand short links before identifying content - you should, because the note id is not in the short form - xhslink.cn has to be in that set too. Ours was missing from both. The reason a full test suite did not catch it is worth saying out loud: every Xiaohongshu URL in our fixtures, evals, docs and demo scripts was a .com one, because those were the links we had pasted. The test suite was green and tested the wrong domain.

Your Cache May Outlive Your Fix

We deployed the routing fix and the very next request came back wrong anyway. The cache key was computed from the raw URL by the layer in front, which had already stored the bad result under the key for xhslink.cn/o/AxnRePgIokn. The lookup answered before the fixed code ran. Deploying a parser fix does not repair anything you have already cached under the same key. If you cache digests, decide deliberately which layer expands short links. Expanding before the key is computed also means two share links to the same note stop producing two entries, and stop billing twice.

What This Service Does Now

xhslink.cn and xhslink.com both resolve to the Xiaohongshu path, image notes and video notes both, and a link that cannot be read fully says so in degraded rather than returning a thin result quietly. Still not supported, stated where you can see it before paying: Bilibili returns HTTP 412 to our address, and Instagram is wired but not verified end to end. If you are wiring this into an agent rather than calling it by hand, reading links your agent can't open covers the tool call and what each field contains. Three digests are free and there is no card.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.