Tanstack Notebook

Path Params

Learn how to work with path parameters (dynamic route segments) in TanStack Router.

Path parameters capture dynamic segments from URLs and provide them as named variables. They use the $ prefix followed by a variable name and match content until the next / character.

How path parameters work

Path parameters are defined using the $ syntax in route paths:

File: routes/posts.$postId.tsx
Route: /posts/$postId
Matches: /posts/123, /posts/my-post, etc.

Good to know: Path parameters are automatically URL-decoded and available as strings. Validate and convert them as needed (e.g., to numbers).

Basic path parameters

Defining path parameters

Create a route file with a path parameter in the filename:

// routes/posts.$postId.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/posts/$postId")({
  component: PostPage,
});

function PostPage() {
  const { postId } = Route.useParams();
  return <div>Post {postId}</div>;
}

Common use cases:

  • Blog posts: /posts/$postId
  • User profiles: /users/$userId
  • Product pages: /shop/$productId

Accessing path parameters

Use Route.useParams() for type-safe parameter access in route components:

export const Route = createFileRoute("/posts/$postId")({
  component: PostPage,
});

function PostPage() {
  const { postId } = Route.useParams();
  return <div>Post {postId}</div>;
}

Using parameters in loaders

Access path parameters in loaders through the params object:

export const Route = createFileRoute("/posts/$postId")({
  loader: async ({ params }) => {
    // params.postId contains the URL segment value
    return fetchPost(params.postId);
  },
  component: PostPage,
});

Validating parameters in beforeLoad

Validate parameter format before loading data:

export const Route = createFileRoute("/posts/$postId")({
  beforeLoad: async ({ params }) => {
    // Validate postId format
    z.number().parse(params.postId);
  },
  loader: async ({ params }) => {
    return fetchPost(params.postId);
  },
});

Good to know: Always validate path parameters in beforeLoad since they come from user input (the URL).

Accessing parameters outside route components

Using Route API

Access parameters from any component using the Route API:

import { getRouteApi } from "@tanstack/react-router";

const routeApi = getRouteApi("/posts/$postId");

function PostSidebar() {
  const { postId } = routeApi.useParams();
  return <div>Related to post {postId}</div>;
}

Using useParams hook

Use the useParams hook with the from prop for type safety:

import { useParams } from "@tanstack/react-router";

function PostSidebar() {
  // Type-safe with from
  const { postId } = useParams({ from: "/posts/$postId" });
  return <div>Post {postId}</div>;
}

Good to know: Always provide the from prop when using useParams for better type safety and autocomplete.

Parameter inheritance

Child routes automatically inherit parameters from parent routes:

// Parent: /posts/$postId
export const Route = createFileRoute("/posts/$postId")({
  component: PostLayout,
});

function PostLayout() {
  const { postId } = Route.useParams();
  return (
    <div>
      <h1>Post {postId}</h1>
      <Outlet />
    </div>
  );
}

// Child: /posts/$postId/comments
export const Route = createFileRoute("/posts/$postId/comments")({
  beforeLoad: ({ params }) => {
    // postId inherited from parent ✅
    console.log(params.postId);
  },
  component: CommentsPage,
});

Child routes can access parent parameters without redefining them.

Object style parameters

Pass parameters as objects when navigating:

import { Link } from "@tanstack/react-router";

function PostList() {
  return (
    <Link to="/posts/$postId" params={{ postId: "123" }}>
      View Post 123
    </Link>
  );
}

Function style parameters

Use functions to modify existing parameters while preserving others:

function PostNavigation() {
  return (
    <Link
      to="/posts/$postId/edit"
      params={(prev) => ({ ...prev, postId: "456" })}
    >
      Edit Post
    </Link>
  );
}

Use cases:

  • Preserving other route parameters
  • Modifying specific parameters while keeping others

Using useNavigate

Navigate programmatically with path parameters:

import { useNavigate } from "@tanstack/react-router";

function PostList() {
  const navigate = useNavigate({ from: "/posts" });

  const handleViewPost = (postId: string) => {
    navigate({
      to: "/posts/$postId",
      params: { postId },
    });
  };

  return <button onClick={() => handleViewPost("123")}>View Post</button>;
}

Optional path parameters

Optional parameters use {-$paramName} syntax for flexible URL patterns. They allow routes to work with or without certain path segments.

Basic optional parameters

// routes/posts.{-$category}.tsx
// Matches: /posts and /posts/tech
export const Route = createFileRoute("/posts/{-$category}")({
  component: PostsPage,
});

function PostsPage() {
  const { category } = Route.useParams();
  return <div>{category ? `Posts in ${category}` : "All Posts"}</div>;
}

Multiple optional parameters

// routes/posts.{-$category}.{-$slug}.tsx
// Matches: /posts, /posts/tech, /posts/tech/hello-world
export const Route = createFileRoute("/posts/{-$category}/{-$slug}")({
  component: PostPage,
});

function PostPage() {
  const { category, slug } = Route.useParams();

  if (!category) return <div>Select a category</div>;
  if (!slug) return <div>Posts in {category}</div>;

  return (
    <div>
      Post: {slug} in {category}
    </div>
  );
}

Mixed required and optional

Combine required and optional parameters:

// routes/users.$id.{-$tab}.tsx
// $id required, $tab optional
export const Route = createFileRoute("/users/$id/{-$tab}")({
  component: UserPage,
});

function UserPage() {
  const { id, tab } = Route.useParams();

  return (
    <div>
      <h1>User {id}</h1>
      {tab && <div>Tab: {tab}</div>}
    </div>
  );
}

Handling optional parameters in loaders

Always handle undefined values for optional parameters:

export const Route = createFileRoute("/posts/{-$category}")({
  loader: async ({ params }) => {
    // params.category might be undefined
    return fetchPosts({
      category: params.category, // undefined means "all categories"
    });
  },
});

Control which optional parameters to include:

function Navigation() {
  return (
    <div>
      {/* Include optional parameter */}
      <Link to="/posts/{-$category}" params={{ category: "tech" }}>
        Tech Posts
      </Link>

      {/* Exclude optional parameter */}
      <Link to="/posts/{-$category}" params={{ category: undefined }}>
        All Posts
      </Link>

      {/* Multiple optional parameters */}
      <Link
        to="/posts/{-$category}/{-$slug}"
        params={{
          category: "tech",
          slug: "react-tips",
        }}
      >
        Specific Post
      </Link>
    </div>
  );
}

Good to know: Use optional parameters for flexible routing patterns like language codes, categories, or tabs that may or may not be present in the URL.

Advanced patterns

Prefixes and suffixes

Define complex routing patterns with prefixes and suffixes:

// Prefix: /posts/post-{$postId}
// Matches: /posts/post-123
export const Route = createFileRoute("/posts/post-{$postId}")({
  component: PostPage,
});

// Suffix: /files/{$fileName}.txt
// Matches: /files/document.txt
export const Route = createFileRoute("/files/{$fileName}.txt")({
  component: FilePage,
});

// Both: /users/user-{$userId}.json
// Matches: /users/user-123.json
export const Route = createFileRoute("/users/user-{$userId}.json")({
  component: UserPage,
});

Features:

  • Prefix patterns: post-{$id} matches post-123
  • Suffix patterns: {$name}.txt matches document.txt
  • Combined patterns: user-{$id}.json matches user-123.json

Wildcards

Combine prefixes/suffixes with wildcard routes:

// routes/storage.drive-{$driveId}.$.tsx
// Matches: /storage/drive-1/path/to/file
export const Route = createFileRoute("/storage/drive-{$driveId}/$")({
  component: StoragePage,
});

function StoragePage() {
  const { driveId } = Route.useParams();
  const { _splat } = Route.useParams();

  return (
    <div>
      Drive: {driveId}
      Path: /{_splat}
    </div>
  );
}

Internationalization (i18n) patterns

Use optional parameters for clean language routing without affecting URLs.

Basic i18n setup

// routes/{-$locale}.about.tsx
// Matches: /about, /en/about, /fr/about
export const Route = createFileRoute("/{-$locale}/about")({
  component: AboutPage,
});

function AboutPage() {
  const { locale } = Route.useParams();
  const currentLocale = locale || "en";

  const content = {
    en: { title: "About Us" },
    fr: { title: "À Propos" },
    es: { title: "Acerca de" },
  };

  return <h1>{content[currentLocale]?.title}</h1>;
}

Complex i18n patterns

// routes/{-$locale}.blog.{-$category}.$slug.tsx
export const Route = createFileRoute("/{-$locale}/blog/{-$category}/$slug")({
  beforeLoad: async ({ params }) => {
    const locale = params.locale || "en";
    const validLocales = ["en", "fr", "es", "de"];

    if (locale && !validLocales.includes(locale)) {
      throw new Error("Invalid locale");
    }

    return { locale };
  },
  loader: async ({ params, context }) => {
    return fetchBlogPost({
      slug: params.slug,
      category: params.category,
      locale: context.locale,
    });
  },
});

Language switching

Create language switchers with optional parameters:

function LanguageSwitcher() {
  const languages = [
    { code: "en", name: "English" },
    { code: "fr", name: "Français" },
    { code: "es", name: "Español" },
  ];

  return (
    <div>
      {languages.map(({ code, name }) => (
        <Link
          key={code}
          to="/{-$locale}/blog/{-$category}/$slug"
          params={(prev) => ({
            ...prev,
            locale: code === "en" ? undefined : code,
          })}
        >
          {name}
        </Link>
      ))}
    </div>
  );
}

SEO for i18n routes

Handle SEO properly for internationalized routes:

export const Route = createFileRoute("/{-$locale}/products/$id")({
  head: ({ params, loaderData }) => {
    const locale = params.locale || "en";
    const product = loaderData;

    return {
      title: product.title[locale],
      meta: [
        {
          property: "og:locale",
          content: locale,
        },
      ],
      links: [
        {
          rel: "canonical",
          href: `https://example.com/products/${params.id}`,
        },
        {
          rel: "alternate",
          hreflang: "fr",
          href: `https://example.com/fr/products/${params.id}`,
        },
      ],
    };
  },
});

Configuration

Allowed characters

Configure which characters are allowed in path parameters:

import { createRouter } from "@tanstack/react-router";

const router = createRouter({
  pathParamsAllowedCharacters: ["@", "+"],
});

Supported characters:

  • ; - Semicolon
  • : - Colon
  • @ - At symbol
  • & - Ampersand
  • = - Equals
  • + - Plus
  • $ - Dollar
  • , - Comma

Use cases:

  • Email addresses in URLs: /users/@email
  • Special identifiers: /items/item+123
  • Complex routing patterns

Good to know: By default, path parameters only allow alphanumeric characters and hyphens. Configure additional characters only when needed.

Common patterns

User profiles

// routes/users.$userId.{-$tab}.tsx
<Link to="/users/$userId/{-$tab}" params={{ userId: "123", tab: "settings" }}>
  User Settings
</Link>

Product catalog

// routes/shop.{-$category}.$productId.tsx
<Link
  to="/shop/{-$category}/$productId"
  params={{ category: "electronics", productId: "laptop-123" }}
>
  Laptop
</Link>

File management

// routes/files.{$fileName}.{$extension}.tsx
<Link
  to="/files/{$fileName}.{$extension}"
  params={{ fileName: "document", extension: "pdf" }}
>
  View PDF
</Link>

Date-based URLs

// routes/{-$year}.{-$month}.{-$day}.tsx
<Link
  to="/{-$year}/{-$month}/{-$day}"
  params={{ year: "2023", month: "12", day: "25" }}
>
  Christmas 2023
</Link>

Best practices

Use descriptive parameter names

Choose clear, descriptive names for path parameters:

// ✅ Good - Descriptive
export const Route = createFileRoute("/users/$userId")({
  // ...
});

// ❌ Bad - Unclear
export const Route = createFileRoute("/users/$id")({
  // ...
});

Always validate parameters

Validate parameter format in beforeLoad since they come from user input:

// ✅ Good - Validated
export const Route = createFileRoute("/posts/$postId")({
  beforeLoad: async ({ params }) => {
    if (!/^\d+$/.test(params.postId)) {
      throw new Error("Invalid post ID");
    }
  },
});

// ❌ Bad - Not validated
export const Route = createFileRoute("/posts/$postId")({
  loader: async ({ params }) => {
    // Could be anything from the URL!
    return fetchPost(params.postId);
  },
});

Handle undefined optional parameters

Always provide fallbacks for optional parameters:

// ✅ Good - Handles undefined
function PostsPage() {
  const { category } = Route.useParams();
  const displayCategory = category || "all";

  return <div>Category: {displayCategory}</div>;
}

// ❌ Bad - Assumes value exists
function PostsPage() {
  const { category } = Route.useParams();
  return <div>Category: {category}</div>; // Could be undefined!
}

Use Route.useParams() for type safety

Always use Route.useParams() in route components for type safety:

// ✅ Good - Type-safe
function PostPage() {
  const { postId } = Route.useParams();
  return <div>{postId}</div>;
}

// ⚠️ Works but less type-safe
function PostPage() {
  const { postId } = useParams({ strict: false });
  return <div>{postId}</div>;
}

Use from prop for type safety

Always provide the from prop when using hooks outside route components:

// ✅ Good - Type-safe
const { postId } = useParams({ from: "/posts/$postId" });

// ⚠️ Works but less type-safe
const { postId } = useParams({ strict: false });

Consider URL encoding

Configure allowed characters only when necessary:

// ✅ Good - Only configure what you need
const router = createRouter({
  pathParamsAllowedCharacters: ["@"], // For email addresses
});

// ❌ Bad - Overly permissive
const router = createRouter({
  pathParamsAllowedCharacters: ["@", "+", "&", "=", "$", ",", ";", ":"],
});

Use optional parameters for i18n

Use optional parameters for clean language URLs:

// ✅ Good - Clean URLs
// /about, /en/about, /fr/about
export const Route = createFileRoute("/{-$locale}/about")({
  // ...
});

// ❌ Bad - Always requires locale
// /en/about, /fr/about (no /about)
export const Route = createFileRoute("/$locale/about")({
  // ...
});

Provide proper canonical URLs for i18n

Handle SEO properly for internationalized routes:

// ✅ Good - Proper SEO
export const Route = createFileRoute("/{-$locale}/products/$id")({
  head: ({ params }) => ({
    links: [
      {
        rel: "canonical",
        href: `https://example.com/products/${params.id}`,
      },
    ],
  }),
});