Skip to content

fix(track): render unavailable tracks as a 404 instead of a custom tombstone - #14572

Merged
dylanjeffers merged 1 commit into
mainfrom
fix/track-404-skeleton
Aug 24, 2026
Merged

dylanjeffers merged 1 commit into
mainfrom
fix/track-404-skeleton

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Follow-up to #14570, per Ray's feedback in the #eng thread on the original report:

It should render as a 404 skeleton probbaly but this is fine.

#14570 gave tracks whose owner is no longer active — a self deactivation, or an account delisted by the trusted notifier — a bespoke "Track Unavailable" page. This makes them a plain 404 instead.

That's also the more consistent answer. AudiusProject/api#1023 deliberately returns 404 rather than 403 for these tracks so they can't be told apart from a track that never existed; the web page was the one surface still announcing that something specific used to be there.

Change

  • Client: reuse the existing navigate(NOT_FOUND_PAGE) path that a failed track fetch already takes, rather than importing a page component.
  • SSR: render a not-found skeleton with 404 meta tags (Not Found / 404 - Page not found), still noIndex, still no embed player.
  • Removes UnavailableTrackPage; moves its server twin to not-found-page/ServerNotFound with the 404 copy.

The render-time guard stays but returns null instead of the tombstone. The redirect fires from an effect, which runs after first paint — without the guard the track's title and artwork would flash on screen before the redirect landed. That was the one thing the old return <UnavailableTrackPage /> got for free.

Scope

Mobile (React Native) and the embed player are unchanged. Neither has a distinct 404 screen to route to, so their unavailable state already is the not-found equivalent — pointing them at a "404" would just mean different copy for the same thing.

Verification

Against prod data via the SSR dev server, using rehoxx/just-for-tonight-wmellark-hoonds (the track from the original report):

  • Crawler fetch: og:title = Not Found • Audius, og:description = 404 - Page not found, robots: noindex, no twitter:player, no signed cidstream URL
  • Browser: lands on /404 with the standard 404 page; track title never appears in the rendered body
  • A normal trending track still renders its full page

tsc --noEmit and eslint both clean.

Note

The SSR hydration payload still carries the track's title and orig_filename, since it's the raw API response. Pre-existing and unchanged by this PR — flagging it as a separate thing worth deciding on.

🤖 Generated with Claude Code

…mbstone

#14570 gave tracks whose owner is no longer active - a self deactivation, or an
account delisted by the trusted notifier - a bespoke "Track Unavailable" page.
Per Ray's feedback on the original report, these should look like any other
missing page instead.

The API already returns 404 for these tracks specifically so they can't be told
apart from one that never existed (api#1023). Making the web page a real 404
finishes that: the client reuses the existing `navigate(NOT_FOUND_PAGE)` path
that a failed track fetch already takes, and SSR renders a not-found skeleton
with 404 meta tags.

The render-time guard stays, returning null rather than the tombstone. The
redirect fires from an effect, which runs after the first paint, so without it
the track's title and artwork would flash on screen before the redirect landed.

Removes the now-unused UnavailableTrackPage, and moves its server twin to
not-found-page/ServerNotFound with the 404 copy.

Mobile (React Native) and the embed player keep their existing treatment:
neither has a distinct 404 screen to route to, so their unavailable state
already is the not-found equivalent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bc61854

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dylanjeffers
dylanjeffers merged commit 837570c into main Aug 24, 2026
17 checks passed
@dylanjeffers
dylanjeffers deleted the fix/track-404-skeleton branch August 24, 2026 17:15
dylanjeffers added a commit that referenced this pull request Sep 24, 2026
Follow-ups from reviewing #14561, #14562, #14567, #14568, #14569,
#14570, #14571 and #14572.

**Logic fixes**
- **Wallet auth, reconnect race (#14567):** a lazily loaded wagmi config
starts out `disconnected`, and only WagmiProvider's re-render kicks off
`reconnect()`. If the SDK read the status first, an external-wallet user
was cached with an empty Hedgehog client. `getAudiusWalletClient` now
starts the reconnect itself.
- **Wallet auth, failed chunk load (#14567):** if the AppKit chunk
failed to load, `initSdk` threw and left `inProgress` stuck, so every
later `audiusSdk()` call hung. It now falls back to Hedgehog, and the
memoized import resets so it can be retried.
- **Direct AppKit imports (#14567):** importing `ReownAppKitModal`
directly (Buy/Sell, sign-in) never marked AppKit as loaded. wagmi hooks
kept reading the placeholder config, and sign-out skipped the wallet
disconnect. The module now registers itself when it is evaluated.
- **Placeholder wagmi config (#14567):** it silently reconnected any
injected wallet the site had been authorized for, because
injected-wallet discovery was on and there was no storage. Discovery is
now off and it has no connectors.
- **Duplicate chain definition (#14567):** `audiusChain` was defined
twice; there is now one definition.
- **Upload seed (#14561):** the upload form re-applied `initialMetadata`
(contest genre/remix/artwork, coin-gate conditions) on every
reinitialize. Example: switching a coin-gated upload to Public,
cancelling the confirm modal and continuing again brought the gate back.
The seed is now applied once, when leaving the select step.
- **Lazy Lottie playback (#14568):** effects that call
`lottieRef.current.play()` ran before the lazy chunk loaded and never
re-ran, so the play-bar loading spinner could stay frozen. `LazyLottie`
now reports when the ref is ready (callers re-run their effect) and
renders a sized placeholder while the chunk loads.
- **Unavailable tracks (#14570/#14572):** owners are exempt from the
non-streamable redirect, so an artist can reach their own track once the
API also flags no-audio tracks (api#1032). The redirect to 404 now
replaces history, so Back doesn't loop.
- **Empty-feed suggestions (#14562):** mobile web now uses the
personalized follow suggestions, and the native empty feed switches its
copy when suggestions are personalized.

**Comment cleanup:** shortened or removed long history-style comments
from these PRs, and dropped the no-`track_cid` claim, since api#1032
isn't merged.

**Tests**
- `tsc` passes for web, common, harmony, mobile and sdk.
- vitest passes: `UploadTrackForm.test.ts`, the new `SelectPage.test.ts`
and `trackAvailability.test.ts`, `FanClubDetailPage.test.tsx`,
`ProfilePage.test.tsx`.
- eslint passes on changed files.
- Local dev server:
  - A signed-out visitor with no persisted wallet doesn't load AppKit.
- With a fake persisted `wagmi.store`, AppKit loads, the reconnect runs,
and the SDK initializes (falls back to Hedgehog, no hang).
  - Importing `ReownAppKitModal` directly marks AppKit as loaded.
- Not tested with a real external wallet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant