Skip to content

⏱️ Outpost Timeout

Prevent outposts from hanging navigation indefinitely.

⚙️ Configuration

typescript
const citadel = createNavigationCitadel(router, {
  defaultTimeout: 5000, // 5 seconds for all outposts
  onTimeout: (name, ctx) => ({ name: 'error' }), // redirect on timeout
});

📊 How Outpost Timeout is Determined

📋 Priority Table

outpost.timeoutdefaultTimeoutResult
undefinedundefinedNo timeout
undefined50005 seconds
10000500010 seconds (override)
05000No timeout (disabled)

💡 Examples

No Timeout (Default)

typescript
const citadel = createNavigationCitadel(router);
// defaultTimeout = undefined — no timeouts

citadel.deployOutpost({
  name: 'slow-api',
  handler: async () => {
    await fetch('/api/slow'); // can hang forever
    return verdicts.ALLOW;
  },
});

WARNING

If API doesn't respond — navigation hangs indefinitely.

Global Timeout

typescript
const citadel = createNavigationCitadel(router, {
  defaultTimeout: 5000, // 5 seconds for all outposts
});

citadel.deployOutpost({
  name: 'slow-api',
  handler: async () => {
    await fetch('/api/slow'); // takes 10 seconds
    return verdicts.ALLOW;
  },
});

Result after 5 seconds: navigation blocked (BLOCK).

Global Timeout with Custom Handler

typescript
const citadel = createNavigationCitadel(router, {
  defaultTimeout: 5000,
  onTimeout: (outpostName, ctx) => {
    console.log(`${outpostName} timed out, redirecting to /error`);
    return { name: 'error' }; // redirect instead of BLOCK
  },
});

Result after 5 seconds: redirect to /error.

Per-Outpost Override

typescript
const citadel = createNavigationCitadel(router, {
  defaultTimeout: 5000, // global 5 seconds
});

// auth — uses defaultTimeout (5s)
citadel.deployOutpost({
  name: 'auth',
  handler: async ({ verdicts }) => {
    await checkAuth(); // must complete within 5 seconds
    return verdicts.ALLOW;
  },
});

// data-loader — needs more time (30s)
citadel.deployOutpost({
  name: 'data-loader',
  timeout: 30000, // override: 30 seconds
  handler: async ({ verdicts }) => {
    await loadHeavyData(); // can take up to 30 seconds
    return verdicts.ALLOW;
  },
});

// analytics — no timeout (runs in afterEach, shouldn't block)
citadel.deployOutpost({
  name: 'analytics',
  timeout: 0, // disabled
  hooks: [NavigationHooks.AFTER_EACH],
  handler: async ({ verdicts }) => {
    await sendAnalytics(); // can take as long as needed
    return verdicts.ALLOW;
  },
});

Per-Outpost onTimeout

Override the citadel-level timeout handler for a specific outpost — useful when one outpost should hard-block while another should silently allow.

typescript
const citadel = createNavigationCitadel(router, {
  defaultTimeout: 5000,
  onTimeout: (_, ctx) => ({ name: 'error' }), // global default: redirect
});

citadel.deployOutpost({
  name: 'auth',
  handler: authCheck,
  // Hard-block on auth timeout, ignoring global redirect
  onTimeout: (_, ctx) => ctx.verdicts.BLOCK,
});

citadel.deployOutpost({
  name: 'preload',
  handler: preloadData,
  // Allow navigation even if preload times out
  onTimeout: (_, ctx) => ctx.verdicts.ALLOW,
});

Resolution order:

Diagram Legend
ColorMeaning
🟢Success, ALLOW, continue
🟡Warning, redirect, deduplicate
🔴Error, BLOCK, cancel
🔵Logging (when logger is enabled)
🟣Named debug breakpoint (debug: true)

Diagrams rendered with vitepress-mermaid-viewer.

Released under the MIT License.