TanStack
Router v1v1
Libraries
Framework
StartRC
Docs
Router
Docs
Data & State Management
Query
Docs
DBbeta
Docs
Storealpha
Docs
AIbeta
Docs
UI & UX
Table
Docs
Formnew
Docs
Hotkeysalpha
Docs
Performance
Virtual
Docs
Pacerbeta
Docs
Tooling
Devtoolsalpha
Docs
Config
Docs
CLIalpha
Docs
Intentalpha
Docs
Resources
BlogRelease notes, architecture notes, and essays.YouTubeThe official TanStack channel.
Workshops
Learn from maintainers

Remote and in-person TanStack workshops for teams that need depth.

Professional Workshops
Channels
DiscordCommunity support and real-time discussion.GitHubSource, issues, discussions, and releases.
People & Work
MaintainersMeet the people maintaining the stack.ContributorsCore, library, and community contributors.ShowcaseProducts and teams building with TanStack.
Tools
BuilderAlphaGenerate TanStack app starters.StatsNPM and ecosystem usage data.
Merch
Recent Products
All MerchBrowse all TanStack apparel, accessories, and stickers.
Support
Support OverviewFind the right support path.PartnersCompanies supporting TanStack.OSS SponsorsSponsors keeping TanStack open source.Enterprise SupportPrivate consulting and expert support.ContactGet in touch with the TanStack team.
About
EthosHow we think about open source and products.TenetsThe values that shape TanStack libraries.Brand GuideLogos, colors, and brand usage.
Partners
Work with TanStack

Sponsorships, placements, and partner pages.

Partnership Inquiry
Log In
Log In
All LibrariesBrowse the full set of public packages.
Framework
StartRC
Docs
Router
Docs
Data & State Management
Query
Docs
DBbeta
Docs
Storealpha
Docs
AIbeta
Docs
UI & UX
Table
Docs
Formnew
Docs
Hotkeysalpha
Docs
Performance
Virtual
Docs
Pacerbeta
Docs
Tooling
Devtoolsalpha
Docs
Config
Docs
CLIalpha
Docs
Intentalpha
Docs
Resources
BlogRelease notes, architecture notes, and essays.YouTubeThe official TanStack channel.
Professional Workshops
Channels
DiscordCommunity support and real-time discussion.GitHubSource, issues, discussions, and releases.
People & Work
MaintainersMeet the people maintaining the stack.ContributorsCore, library, and community contributors.ShowcaseProducts and teams building with TanStack.
Tools
BuilderAlphaGenerate TanStack app starters.StatsNPM and ecosystem usage data.
Recent Products
All MerchBrowse all TanStack apparel, accessories, and stickers.
Support
Support OverviewFind the right support path.PartnersCompanies supporting TanStack.OSS SponsorsSponsors keeping TanStack open source.Enterprise SupportPrivate consulting and expert support.ContactGet in touch with the TanStack team.
About
EthosHow we think about open source and products.TenetsThe values that shape TanStack libraries.Brand GuideLogos, colors, and brand usage.
Partnership Inquiry
Docs
Routing
  • Routing Concepts
  • Route Trees
  • Route Matching
  • File-Based Routing
  • Virtual File Routes
  • Code-Based Routing
  • File Naming Conventions
Guides
  • Code Splitting
  • Automatic Code Splitting
  • Creating a Router
  • Outlets
  • Navigation
  • Router Events
  • Path Params
  • Search Params
  • Link Options
  • Custom Links
  • Data Loading
  • Deferred Data Loading
  • External Data Loading
  • Data Mutations
  • Type Safety
  • Type Utilities
  • Preloading
  • Document Head Management
  • Route Masking
  • Navigation Blocking
  • Custom Search Param Serialization
  • History Types
  • Router Context
  • Not Found Errors
  • Authenticated Routes
  • Scroll Restoration
  • Static Route Data
  • SSR
  • Render Optimizations
Integrations
  • TanStack Query
ESLint
  • ESLint Plugin Router
  • Create Route Property Order
HomeGet StartedGuidesAPIExamples
Routing
  • Routing Concepts
  • Route Trees
  • Route Matching
  • File-Based Routing
  • Virtual File Routes
  • Code-Based Routing
  • File Naming Conventions
Guides
  • Code Splitting
  • Automatic Code Splitting
  • Creating a Router
  • Outlets
  • Navigation
  • Router Events
  • Path Params
  • Search Params
  • Link Options
  • Custom Links
  • Data Loading
  • Deferred Data Loading
  • External Data Loading
  • Data Mutations
  • Type Safety
  • Type Utilities
  • Preloading
  • Document Head Management
  • Route Masking
  • Navigation Blocking
  • Custom Search Param Serialization
  • History Types
  • Router Context
  • Not Found Errors
  • Authenticated Routes
  • Scroll Restoration
  • Static Route Data
  • SSR
  • Render Optimizations
Integrations
  • TanStack Query
ESLint
  • ESLint Plugin Router
  • Create Route Property Order
AI/LLM: This documentation page is available in plain markdown format at/router/v1/docs/guide/navigation-blocking.md
Guides

Navigation Blocking

Edit on GitHub
PreviousRoute Masking
NextCustom Search Param Serialization

On this page

  • How does navigation blocking work?
  • How do I use navigation blocking?
  • Hook/logical-based blocking
  • Component-based blocking
  • How can I show a custom UI?
  • Hook/logical-based custom UI with resolver
  • Hook/logical-based custom UI without resolver
  • Component-based custom UI
Blog
@Tan_Stack on X.com
@TannerLinsley on X.com
GitHub
YouTube
Nozzle.io - Keyword Rank Tracker
Ethos
Tenets
Privacy Policy
Terms of Service
© 2026 TanStack LLC
PartnersBecome a Partner
Gold
CodeRabbitCodeRabbit
CloudflareCloudflare
RailwayRailway
Silver
OpenRouterOpenRouter
AG GridAG Grid
ClerkClerk
WorkOSWorkOS
NetlifyNetlify
SerpAPISerpAPI
Bronze
UnkeyUnkey
ElectricElectric
SentrySentry
PrismaPrisma

    Navigation blocking is a way to prevent navigation from happening. This is typical if a user attempts to navigate while they:

    • Have unsaved changes
    • Are in the middle of a form
    • Are in the middle of a payment

    In these situations, a prompt or custom UI should be shown to the user to confirm they want to navigate away.

    • If the user confirms, navigation will continue as normal
    • If the user cancels, all pending navigations will be blocked

    How does navigation blocking work?#

    Navigation blocking adds one or more layers of "blockers" to the entire underlying history API. If any blockers are present, navigation will be paused via one of the following ways:

    • Custom UI
      • If the navigation is triggered by something we control at the router level, we can allow you to perform any task or show any UI you'd like to the user to confirm the action. Each blocker's blocker function will be asynchronously and sequentially executed. If any blocker function resolves or returns true, the navigation will be allowed and all other blockers will continue to do the same until all blockers have been allowed to proceed. If any single blocker resolves or returns false, the navigation will be canceled and the rest of the blocker functions will be ignored.
    • The onbeforeunload event
      • For page events that we cannot control directly, we rely on the browser's onbeforeunload event. If the user attempts to close the tab or window, refresh, or "unload" the page assets in any way, the browser's generic "Are you sure you want to leave?" dialog will be shown. If the user confirms, all blockers will be bypassed and the page will unload. If the user cancels, the unload will be cancelled, and the page will remain as is.

    How do I use navigation blocking?#

    There are 2 ways to use navigation blocking:

    • Hook/logical-based blocking
    • Component-based blocking

    Hook/logical-based blocking#

    Let's imagine we want to prevent navigation if a form is dirty. We can do this by using the useBlocker hook:

    tsx
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        shouldBlockFn: () => {
          if (!formIsDirty) return false
    
          const shouldLeave = confirm('Are you sure you want to leave?')
          return !shouldLeave
        },
      })
    
      // ...
    }
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        shouldBlockFn: () => {
          if (!formIsDirty) return false
    
          const shouldLeave = confirm('Are you sure you want to leave?')
          return !shouldLeave
        },
      })
    
      // ...
    }

    shouldBlockFn gives you type safe access to the current and next location:

    tsx
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      // always block going from /foo to /bar/123?hello=world
      const { proceed, reset, status } = useBlocker({
        shouldBlockFn: ({ current, next }) => {
          return (
            current.routeId === '/foo' &&
            next.fullPath === '/bar/$id' &&
            next.params.id === 123 &&
            next.search.hello === 'world'
          )
        },
        withResolver: true,
      })
    
      // ...
    }
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      // always block going from /foo to /bar/123?hello=world
      const { proceed, reset, status } = useBlocker({
        shouldBlockFn: ({ current, next }) => {
          return (
            current.routeId === '/foo' &&
            next.fullPath === '/bar/$id' &&
            next.params.id === 123 &&
            next.search.hello === 'world'
          )
        },
        withResolver: true,
      })
    
      // ...
    }

    Note that even if shouldBlockFn returns false, the browser's beforeunload event may still be triggered on page reloads or tab closing. To gain control over this, you can use the enableBeforeUnload option to conditionally register the beforeunload handler:

    tsx
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        {/* ... */}
        enableBeforeUnload: formIsDirty, // or () => formIsDirty
      })
    
      // ...
    }
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        {/* ... */}
        enableBeforeUnload: formIsDirty, // or () => formIsDirty
      })
    
      // ...
    }

    You can find more information about the useBlocker hook in the API reference.

    Component-based blocking#

    In addition to logical/hook based blocking, you can use the Block component to achieve similar results:

    tsx
    import { Block } from '@tanstack/solid-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      return (
        <Block
          shouldBlockFn={() => {
            if (!formIsDirty) return false
    
            const shouldLeave = confirm('Are you sure you want to leave?')
            return !shouldLeave
          }}
          enableBeforeUnload={formIsDirty}
        />
      )
    
      // OR
    
      return (
        <Block
          shouldBlockFn={() => formIsDirty}
          enableBeforeUnload={formIsDirty}
          withResolver
        >
          {({ status, proceed, reset }) => <>{/* ... */}</>}
        </Block>
      )
    }
    import { Block } from '@tanstack/solid-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      return (
        <Block
          shouldBlockFn={() => {
            if (!formIsDirty) return false
    
            const shouldLeave = confirm('Are you sure you want to leave?')
            return !shouldLeave
          }}
          enableBeforeUnload={formIsDirty}
        />
      )
    
      // OR
    
      return (
        <Block
          shouldBlockFn={() => formIsDirty}
          enableBeforeUnload={formIsDirty}
          withResolver
        >
          {({ status, proceed, reset }) => <>{/* ... */}</>}
        </Block>
      )
    }

    How can I show a custom UI?#

    In most cases, using window.confirm in the shouldBlockFn function with withResolver: false in the hook is enough since it will clearly show the user that the navigation is being blocked and resolve the blocking based on their response.

    However, in some situations, you might want to show a custom UI that is intentionally less disruptive and more integrated with your app's design.

    Note: The return value of shouldBlockFn does not resolve the blocking if withResolver is true.

    Hook/logical-based custom UI with resolver#

    tsx
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      const { proceed, reset, status } = useBlocker({
        shouldBlockFn: () => formIsDirty,
        withResolver: true,
      })
    
      // ...
    
      return (
        <>
          {/* ... */}
          {status === 'blocked' && (
            <div>
              <p>Are you sure you want to leave?</p>
              <button onClick={proceed}>Yes</button>
              <button onClick={reset}>No</button>
            </div>
          )}
        </>
    }
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      const { proceed, reset, status } = useBlocker({
        shouldBlockFn: () => formIsDirty,
        withResolver: true,
      })
    
      // ...
    
      return (
        <>
          {/* ... */}
          {status === 'blocked' && (
            <div>
              <p>Are you sure you want to leave?</p>
              <button onClick={proceed}>Yes</button>
              <button onClick={reset}>No</button>
            </div>
          )}
        </>
    }

    Hook/logical-based custom UI without resolver#

    tsx
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        shouldBlockFn: () => {
          if (!formIsDirty) {
            return false
          }
    
          const shouldBlock = new Promise<boolean>((resolve) => {
            // Using a modal manager of your choice
            modals.open({
              title: 'Are you sure you want to leave?',
              children: (
                <SaveBlocker
                  confirm={() => {
                    modals.closeAll()
                    resolve(false)
                  }}
                  reject={() => {
                    modals.closeAll()
                    resolve(true)
                  }}
                />
              ),
              onClose: () => resolve(true),
            })
          })
          return shouldBlock
        },
      })
    
      // ...
    }
    import { useBlocker } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      useBlocker({
        shouldBlockFn: () => {
          if (!formIsDirty) {
            return false
          }
    
          const shouldBlock = new Promise<boolean>((resolve) => {
            // Using a modal manager of your choice
            modals.open({
              title: 'Are you sure you want to leave?',
              children: (
                <SaveBlocker
                  confirm={() => {
                    modals.closeAll()
                    resolve(false)
                  }}
                  reject={() => {
                    modals.closeAll()
                    resolve(true)
                  }}
                />
              ),
              onClose: () => resolve(true),
            })
          })
          return shouldBlock
        },
      })
    
      // ...
    }

    Component-based custom UI#

    Similarly to the hook, the Block component returns the same state and functions as render props:

    tsx
    import { Block } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      return (
        <Block shouldBlockFn={() => formIsDirty} withResolver>
          {({ status, proceed, reset }) => (
            <>
              {/* ... */}
              {status === 'blocked' && (
                <div>
                  <p>Are you sure you want to leave?</p>
                  <button onClick={proceed}>Yes</button>
                  <button onClick={reset}>No</button>
                </div>
              )}
            </>
          )}
        </Block>
      )
    }
    import { Block } from '@tanstack/react-router'
    
    function MyComponent() {
      const [formIsDirty, setFormIsDirty] = useState(false)
    
      return (
        <Block shouldBlockFn={() => formIsDirty} withResolver>
          {({ status, proceed, reset }) => (
            <>
              {/* ... */}
              {status === 'blocked' && (
                <div>
                  <p>Are you sure you want to leave?</p>
                  <button onClick={proceed}>Yes</button>
                  <button onClick={reset}>No</button>
                </div>
              )}
            </>
          )}
        </Block>
      )
    }
    Latest Posts
    TypeScript Performance in TanStack Table V9Jun 14, 2026TanStack AI Beta: The Switzerland of AI Tooling Grows UpJun 9, 2026TanStack Table V9: Taking FormJun 7, 2026