Skip to main content
logto-authkit supports custom navigation to integrate seamlessly with your routing library, preventing full page reloads during authentication flows.

Why Custom Navigation?

By default, Logto uses window.location.href for navigation, which causes full page reloads. Custom navigation allows you to:
  • Use client-side routing (React Router, Next.js, etc.)
  • Maintain application state during auth flows
  • Provide a smoother user experience
  • Control navigation behavior in SPAs

Basic Setup

Pass a customNavigate function to the AuthProvider:
import { AuthProvider } from '@ouim/logto-authkit'

const config = {
  endpoint: 'https://your-logto-endpoint.com',
  appId: 'your-app-id',
}

function App() {
  const customNavigate = (url, options) => {
    // Your navigation logic here
    console.log('Navigating to:', url, options)
  }

  return (
    <AuthProvider
      config={config}
      callbackUrl="http://localhost:3000/callback"
      customNavigate={customNavigate}
    >
      <YourApp />
    </AuthProvider>
  )
}

React Router Integration

React Router v6

import { AuthProvider } from '@ouim/logto-authkit'
import { BrowserRouter, useNavigate } from 'react-router-dom'

function AppProviders({ children }) {
  const navigate = useNavigate()

  const customNavigate = (url, options) => {
    // Handle both relative and absolute URLs
    if (url.startsWith('http://') || url.startsWith('https://')) {
      // External URL - use window.location
      window.location.href = url
    } else {
      // Internal URL - use React Router
      navigate(url, { replace: options?.replace })
    }
  }

  return (
    <AuthProvider
      config={logtoConfig}
      callbackUrl="/callback"
      customNavigate={customNavigate}
    >
      {children}
    </AuthProvider>
  )
}

function App() {
  return (
    <BrowserRouter>
      <AppProviders>
        <YourApp />
      </AppProviders>
    </BrowserRouter>
  )
}

React Router v5

import { AuthProvider } from '@ouim/logto-authkit'
import { BrowserRouter, useHistory } from 'react-router-dom'

function AppProviders({ children }) {
  const history = useHistory()

  const customNavigate = (url, options) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      window.location.href = url
    } else {
      if (options?.replace) {
        history.replace(url)
      } else {
        history.push(url)
      }
    }
  }

  return (
    <AuthProvider
      config={logtoConfig}
      callbackUrl="/callback"
      customNavigate={customNavigate}
    >
      {children}
    </AuthProvider>
  )
}

Next.js Integration

App Router (Next.js 13+)

'use client'

import { AuthProvider } from '@ouim/logto-authkit'
import { useRouter } from 'next/navigation'

function Providers({ children }) {
  const router = useRouter()

  const customNavigate = (url, options) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      window.location.href = url
    } else {
      if (options?.replace) {
        router.replace(url)
      } else {
        router.push(url)
      }
    }
  }

  return (
    <AuthProvider
      config={logtoConfig}
      callbackUrl="/callback"
      customNavigate={customNavigate}
    >
      {children}
    </AuthProvider>
  )
}

Pages Router (Next.js 12 and earlier)

import { AuthProvider } from '@ouim/logto-authkit'
import { useRouter } from 'next/router'

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  const customNavigate = (url, options) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      window.location.href = url
    } else {
      if (options?.replace) {
        router.replace(url)
      } else {
        router.push(url)
      }
    }
  }

  return (
    <AuthProvider
      config={logtoConfig}
      callbackUrl="/callback"
      customNavigate={customNavigate}
    >
      <Component {...pageProps} />
    </AuthProvider>
  )
}
The customNavigate function receives two parameters:
url
string
required
The URL to navigate to (can be relative or absolute)
options
NavigationOptions
Navigation options object:

How It Works

1

Provider Registration

When you pass customNavigate to AuthProvider, it registers your navigation function globally for the library.
2

Internal Navigation Calls

When logto-authkit needs to navigate (e.g., after sign-in, during callback), it uses your custom function instead of window.location.href.
3

Cleanup on Unmount

When the AuthProvider unmounts, it automatically unregisters the custom navigation function.

Implementation Details

The custom navigation is set using the setCustomNavigate utility from the library:
// From context.tsx (line 360-365)
useEffect(() => {
  setCustomNavigate(customNavigate || null)

  // Cleanup on unmount
  return () => setCustomNavigate(null)
}, [customNavigate])
This ensures that:
  • The navigation function is available throughout the library
  • It’s properly cleaned up when the component unmounts
  • You can dynamically change the navigation function if needed

Best Practices

Always check if the URL is external (starts with http:// or https://) and use window.location.href for those cases. Authentication flows may redirect to external Logto servers.
const customNavigate = (url, options) => {
  if (url.startsWith('http://') || url.startsWith('https://')) {
    window.location.href = url
  } else {
    // Use your router
  }
}
Respect the options.replace parameter to allow the library to replace history entries when appropriate.
const customNavigate = (url, options) => {
  if (options?.replace) {
    router.replace(url)
  } else {
    router.push(url)
  }
}
Make sure your customNavigate function is stable (use useCallback if needed) to prevent unnecessary re-renders.
const customNavigate = useCallback((url, options) => {
  // navigation logic
}, [router])
Custom navigation works seamlessly with popup sign-in mode:
import { AuthProvider } from '@ouim/logto-authkit'
import { useNavigate } from 'react-router-dom'

function AppProviders({ children }) {
  const navigate = useNavigate()

  const customNavigate = (url, options) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      window.location.href = url
    } else {
      navigate(url, { replace: options?.replace })
    }
  }

  return (
    <AuthProvider
      config={logtoConfig}
      callbackUrl="/callback"
      customNavigate={customNavigate}
      enablePopupSignIn={true} // Popup mode
    >
      {children}
    </AuthProvider>
  )
}
When using popup sign-in, the main window navigation is handled by your custom function, while the popup window uses its own navigation context.

Troubleshooting

Make sure your customNavigate function is properly handling relative URLs. Check that you’re not falling back to window.location.href for internal routes.
Check that your callback URL matches the route where you render <CallbackPage />. Mismatched URLs can cause redirect loops.

Next Steps

AuthProvider

Learn more about AuthProvider configuration

useAuth Hook

Learn more about the useAuth hook