perf(banner): smoother deep-link from banner to /browse post
All checks were successful
Deploy to Frontend Servers / deploy (push) Successful in 27s
All checks were successful
Deploy to Frontend Servers / deploy (push) Successful in 27s
- MessageStream: drop the mount-time scroll lock and the 80ms-delayed
custom rAF; engage the lock only while the smooth animation runs and
use native scrollTo({behavior:'smooth'}) so the page never feels frozen
during pagination and the easing is buttery.
- PublicLayout: fire the default /browse prefetch immediately on mount
(banner / Home tile destination) so a fast tap hits a warm cache;
popular / latest stay deferred to idle.
- FigmaBanner: prefetch the all-scope stream on mount and on pointerdown
as safety nets, and ignore empty / '#' / javascript: link URLs so a
contentless banner renders as a non-interactive image.
- usePostStream: dedupe in-flight prefetches by key so concurrent
callers (layout + banner) collapse into a single network request.
This commit is contained in:
@@ -12,6 +12,7 @@ import { assetUrl, getJSON, itemsOrEmpty, readJSONCache } from "../api";
|
||||
import { EASE_OUT } from "../motion";
|
||||
import { langQuery, useI18n, type Lang } from "../i18n";
|
||||
import { localizePath, stripLangPrefix } from "../languageRoutes";
|
||||
import { prefetchPostStream } from "./messageStream/hooks/usePostStream";
|
||||
|
||||
const FIGMA_ASSET_BASE = "/assets/ark-library/figma";
|
||||
|
||||
@@ -87,12 +88,17 @@ function toSlides(items: BannerApiItem[]): BannerSlide[] {
|
||||
.slice(0, MAX_BANNERS)
|
||||
.map((item) => {
|
||||
const imageUrl = assetUrl(item.imageUrl);
|
||||
const raw = (item.linkUrl ?? "").trim();
|
||||
// Treat empty, placeholder, or javascript: URLs as "no link" so the
|
||||
// slide renders as a non-interactive image rather than a dead anchor.
|
||||
const linkUrl =
|
||||
raw && raw !== "#" && !/^javascript:/i.test(raw) ? raw : undefined;
|
||||
return {
|
||||
id: String(item.id),
|
||||
mobile: imageUrl,
|
||||
desktop: imageUrl,
|
||||
alt: "",
|
||||
linkUrl: item.linkUrl || undefined,
|
||||
linkUrl,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -138,6 +144,32 @@ export function FigmaBanner() {
|
||||
};
|
||||
}, [lang]);
|
||||
|
||||
// Banner clicks land on /browse?post=<id>. Warm the all-scope stream cache
|
||||
// the moment the banner is on screen so the destination renders instantly
|
||||
// instead of showing a skeleton / loading state before scrolling to the post.
|
||||
useEffect(() => {
|
||||
prefetchPostStream({
|
||||
scope: { kind: "all" },
|
||||
type: "all",
|
||||
q: "",
|
||||
sort: "",
|
||||
lang,
|
||||
});
|
||||
}, [lang]);
|
||||
|
||||
// Safety net: if the user taps quickly before mount-prefetch completes,
|
||||
// pointerdown re-issues the prefetch. Same-key requests are deduped by the
|
||||
// stream cache, so this is free when the warm-up already ran.
|
||||
const prefetchAllStream = useCallback(() => {
|
||||
prefetchPostStream({
|
||||
scope: { kind: "all" },
|
||||
type: "all",
|
||||
q: "",
|
||||
sort: "",
|
||||
lang,
|
||||
});
|
||||
}, [lang]);
|
||||
|
||||
const goTo = useCallback((index: number, behavior: ScrollBehavior) => {
|
||||
const scroller = scrollerRef.current;
|
||||
if (!scroller) return;
|
||||
@@ -217,6 +249,7 @@ export function FigmaBanner() {
|
||||
}, [hasMultiple, autoplayPaused, publicMenuOpen, slides.length, goTo]);
|
||||
|
||||
const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
||||
prefetchAllStream();
|
||||
if (event.pointerType !== "mouse") return;
|
||||
const scroller = scrollerRef.current;
|
||||
if (!scroller) return;
|
||||
|
||||
Reference in New Issue
Block a user