Agent SkillsMelvynx/Parler › intercepted-route-using-tanstack-start

intercepted-route-using-tanstack-start

GitHub

实现 TanStack Start 中的拦截路由模式,支持客户端导航时以模态框展示但 URL 保持真实路径,刷新后渲染完整页面。

web-app/.agents/skills/intercepted-route-using-tanstack-start/SKILL.md Melvynx/Parler

Trigger Scenarios

需要实现类似 Next.js 的拦截路由功能 要求浏览器 URL 显示真实路径同时 UI 以模态框呈现

Install

npx skills add Melvynx/Parler --skill intercepted-route-using-tanstack-start -g -y
More Options

Non-standard path

npx skills add https://github.com/Melvynx/Parler/tree/main/web-app/.agents/skills/intercepted-route-using-tanstack-start -g -y

Use without installing

npx skills use Melvynx/Parler@intercepted-route-using-tanstack-start

指定 Agent (Claude Code)

npx skills add Melvynx/Parler --skill intercepted-route-using-tanstack-start -a claude-code -g -y

安装 repo 全部 skill

npx skills add Melvynx/Parler --all -g -y

预览 repo 内 skill

npx skills add Melvynx/Parler --list

SKILL.md

Frontmatter
{
    "name": "intercepted-route-using-tanstack-start",
    "description": "Implement intercepted\/modal routes in NowStack TanStack Start using route masks, real shareable URLs, refresh-to-full-page behavior, and browser verification."
}

Intercepted Route Using TanStack Start

Use this skill when a UI should open as a modal during client navigation while the browser URL shows the real destination path, and a refresh or direct visit should render the destination as a full page.

This is the TanStack Start equivalent of a Next.js intercepted route. Do not use ?modal=... as the visible URL for this UX.

Desired Behavior

For a list-to-detail flow:

  1. User starts on /items.
  2. User clicks an item.
  3. Browser URL becomes /items/$id.
  4. UI still shows /items context with a modal on top.
  5. Back closes the modal and returns to /items.
  6. Refresh or direct visit on /items/$id renders the full detail page.

For a global modal like sign-in:

  1. User starts on /docs.
  2. User clicks Sign in.
  3. Browser URL becomes /auth/signin.
  4. UI shows the sign-in modal over /docs.
  5. Back or close returns to /docs.
  6. Refresh on /auth/signin renders the full sign-in page.

Core Pattern

Navigate to the source route internally, store modal state in that source route's search, and mask the browser URL as the real destination route.

<Link
  to="/changelog"
  search={{ entry: changelog.slug }}
  mask={{
    to: "/changelog/$slug",
    params: { slug: changelog.slug },
    unmaskOnReload: true,
  }}
  resetScroll={false}
>
  {changelog.attributes.title}
</Link>

The source route owns the modal state:

type ChangelogSearch = {
  entry?: string;
};

export const Route = createFileRoute("/(layout)/changelog/")({
  validateSearch: (search: Record<string, unknown>): ChangelogSearch => ({
    entry:
      typeof search.entry === "string" && search.entry.length > 0
        ? search.entry
        : undefined,
  }),
  loader: changelogLoader,
  component: ChangelogPage,
  pendingComponent: ChangelogPageSkeleton,
});

function ChangelogPage() {
  const { changelogs } = Route.useLoaderData();
  const { entry } = Route.useSearch();

  return <ChangelogTimeline changelogs={changelogs} selectedSlug={entry} />;
}

The destination route remains a normal route. It must be usable on direct visit and refresh:

export const Route = createFileRoute("/(layout)/changelog/$slug/")({
  loader: async ({ params }) => {
    const item = await loadItem(params.slug);
    if (!item) throw notFound();
    return item;
  },
  component: ChangelogDetailPage,
  pendingComponent: ChangelogDetailPageSkeleton,
});

Closing the Modal

Do not blindly call router.history.back() for every close. Only do that when the current location is masked.

const router = useRouter();
const location = useLocation();

const closeModal = () => {
  if (location.maskedLocation) {
    router.history.back();
    return;
  }

  void router.navigate({
    to: "/changelog",
    search: {},
    replace: true,
  });
};

Use this close handler for dialog onOpenChange.

Opening the Full Page from the Modal

If the modal has an "Open page" action, avoid firing the close handler first. Closing a masked modal usually calls history.back(), which races with the full-page navigation and can return to the source page.

void router.navigate({
  to: "/changelog/$slug",
  params: { slug },
  replace: true,
});

If the same dialog component is also used outside an intercepted route, add explicit props:

<ChangelogDialog
  changelog={selectedChangelog}
  openPageReplace
  closeOnOpenPage={false}
  onOpenChange={(open) => {
    if (!open) closeModal();
  }}
/>

Global Modal Variant

For global modals mounted at the root, keep the visible URL masked but the internal state on the current route:

<Link
  to="."
  search={(previous) => ({ ...previous, modal: "signin" })}
  mask={{ to: "/auth/signin", unmaskOnReload: true }}
>
  Sign in
</Link>

The root/global dialog reads the internal search state:

const search = useSearch({ strict: false }) as { modal?: string };
const isOpen = search.modal === "signin";

Close with the same masked-location guard:

const closeDialog = () => {
  if (location.maskedLocation) {
    router.history.back();
    return;
  }

  void router.navigate({
    to: ".",
    search: (previous) => ({ ...previous, modal: undefined }),
    replace: true,
  });
};

Rules

  • Always keep the destination route as a real route with its own loader, SEO/head metadata when needed, and pendingComponent.
  • Use mask={{ to: realDestination, unmaskOnReload: true }} for refresh-to-full-page behavior.
  • Keep modal state internal to the source route search, not visible as ?modal=... in the browser URL.
  • Use location.maskedLocation before deciding whether close should call history.back().
  • Do not call a modal close handler immediately before "open full page" navigation from inside the modal.
  • Preserve callback/search data in the internal route search when the modal flow needs it.
  • Add or update e2e coverage for click, close/back, refresh, and direct destination visit.

Verification

For routes/UI flows, use the repo workflow:

pnpm ts
pnpm lint:ci
pnpm start-all -p <port>
PLAYWRIGHT_TEST_BASE_URL=http://localhost:<port> HEADLESS=TRUE pnpm exec playwright test e2e/<spec>.ts

Also verify manually with dev-browser when behavior matters:

  1. Visit the source page.
  2. Click the masked/intercepted link.
  3. Confirm the browser URL is the destination URL.
  4. Confirm the modal is visible.
  5. Refresh.
  6. Confirm the modal is gone and the full destination page is visible.

Existing References

  • Changelog route mask: src/features/changelog/changelog-timeline.tsx
  • Changelog source route search: src/routes/(layout)/changelog/index.tsx
  • Changelog destination route: src/routes/(layout)/changelog/$slug/index.tsx
  • Sign-in global modal mask: src/features/auth/sign-in-button.tsx
  • Sign-in global dialog close behavior: src/features/auth/sign-in-dialog.tsx
  • E2E examples: e2e/changelog.spec.ts, e2e/signin-modal.spec.ts

Version History

  • 1aaa38b Current 2026-08-20 07:35

Same Skill Collection

web-app/.agents/skills/add-documentation/SKILL.md
web-app/.agents/skills/apex/SKILL.md
web-app/.agents/skills/convex-cost-optimizer/SKILL.md
web-app/.agents/skills/convex-create-component/SKILL.md
web-app/.agents/skills/convex-migration-helper/SKILL.md
web-app/.agents/skills/convex-performance-audit/SKILL.md
web-app/.agents/skills/convex-quickstart/SKILL.md
web-app/.agents/skills/convex-setup-auth/SKILL.md
web-app/.agents/skills/convex/SKILL.md
web-app/.agents/skills/create-saas-idea/SKILL.md
web-app/.agents/skills/create-tests/SKILL.md
web-app/.agents/skills/dev-browser/SKILL.md
web-app/.agents/skills/init-project/SKILL.md
web-app/.agents/skills/optimizer/SKILL.md
web-app/.agents/skills/org-api-route/SKILL.md
web-app/.agents/skills/publish-to-production/SKILL.md
web-app/.agents/skills/setup-stripe/SKILL.md
web-app/.agents/skills/sync-with-nowstack/SKILL.md
web-app/.agents/skills/exa-search/SKILL.md
web-app/.agents/skills/find-docs/SKILL.md

Metadata

Files
0
Version
1aaa38b
Hash
1f9c74ca
Indexed
2026-08-20 07:35

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-22 07:29
浙ICP备14020137号-1 $mapa de visitantes$