Skip to content

Commit

Permalink
feat(next,docs): rework useRouter to support custom router options
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyleen77 committed Feb 28, 2025
1 parent e603dee commit 30fd320
Show file tree
Hide file tree
Showing 24 changed files with 629 additions and 241 deletions.
90 changes: 61 additions & 29 deletions apps/docs/content/docs/next/hooks/use-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ title: useRouter (app dir)
description: Use `useRouter` hook
---

import { Callout } from 'fumadocs-ui/components/callout';
<Callout type="warn" title="Breaking Change">
As of version `3.2.0`, router `push` and `replace` methods now have only 2 parameters (`href` and `options`).
You can modify your router calls as follows:
```diff
- router.push('/dashboard', { scroll: false }, { startPosition: 0.3 })
+ router.push('/dashboard', { scroll: false, startPosition: 0.3 })
```
</Callout>

The app directory router doesn't manage routes like the page directory router. So we need a custom router that extends the Next.js router and enables the progress bar.

Expand Down Expand Up @@ -45,7 +52,7 @@ useRouter(options?: RouterProgressOptions): AppRouterProgressInstance;

### RouterProgressOptions

```tsx
```ts
interface RouterProgressOptions {
showProgress?: boolean;
startPosition?: number;
Expand All @@ -57,24 +64,26 @@ interface RouterProgressOptions {

### AppRouterProgressInstance

```tsx
interface AppRouterProgressInstance {
back(progressOptions?: RouterActionsProgressOptions): void;
forward(progressOptions?: RouterActionsProgressOptions): void;
refresh(progressOptions?: RouterActionsProgressOptions): void;
push(
href: string,
options?: NavigateOptions,
progressOptions?: RouterActionsProgressOptions,
): void;
replace(
href: string,
options?: NavigateOptions,
progressOptions?: RouterActionsProgressOptions,
): void;
```ts
export interface AppRouterProgressInstance<
ROpts = NavigateOptions,
POtps = PrefetchOptions,
> {
push(href: string, options?: CombinedRouterOptions<ROpts>): void;
replace(href: string, options?: CombinedRouterOptions<ROpts>): void;
prefetch(href: string, options?: POtps): void;
back(options?: CombinedRouterOptions<ROpts>): void;
refresh(options?: CombinedRouterOptions<ROpts>): void;
forward(options?: CombinedRouterOptions<ROpts>): void;
}
```

### CombinedRouterOptions

```ts
export type CombinedRouterOptions<ROpts> = ROpts & RouterActionsProgressOptions;
```

## Methods

### push
Expand All @@ -83,14 +92,14 @@ interface AppRouterProgressInstance {

Pushes a new entry onto the history stack.

```tsx
```ts
router.push('/about');
```

#### Types

```tsx
router.push(url: string, options?: NavigateOptions, progressOptions?: RouterProgressOptions)
```ts
router.push(url: string, options?:CombinedRouterOptions<ROpts>)
```

### replace
Expand All @@ -99,14 +108,14 @@ router.push(url: string, options?: NavigateOptions, progressOptions?: RouterProg

Replaces the current entry on the history stack.

```tsx
```ts
router.replace('/?counter=10');
```

#### Types

```tsx
router.replace(url: string, options?: NavigateOptions, progressOptions?: RouterProgressOptions)
```ts
router.replace(url: string, options?: CombinedRouterOptions<ROpts>)
```

### back
Expand All @@ -115,14 +124,14 @@ router.replace(url: string, options?: NavigateOptions, progressOptions?: RouterP

Goes back in the history stack.

```tsx
```ts
router.back();
```

#### Types

```tsx
router.back(progressOptions?: RouterProgressOptions)
router.back(options?: CombinedRouterOptions<ROpts>)
```

### forward
Expand All @@ -138,7 +147,7 @@ router.forward();
#### Types

```tsx
router.forward(progressOptions?: RouterProgressOptions)
router.forward(options?: CombinedRouterOptions<ROpts>)
```

### refresh
Expand All @@ -154,16 +163,16 @@ router.refresh();
#### Types

```tsx
router.refresh(progressOptions?: RouterProgressOptions)
router.refresh(options?: CombinedRouterOptions<ROpts>)
```

## Options

### Usage

```tsx
router.push('/about', undefined, { disableSameURL: true });
router.replace('/?counter=10', undefined, { disableSameURL: true });
router.push('/about', { disableSameURL: true });
router.replace('/?counter=10', { disableSameURL: true });
router.back({ startPosition: 0.3 });
router.forward({ startPosition: 0.3 });
router.refresh({ startPosition: 0.3 });
Expand All @@ -178,6 +187,7 @@ router.refresh({ startPosition: 0.3 });
| startPosition | number | undefined | The start position of the progress bar. |
| disableSameURL | boolean | `true` | Disable the progress bar when the URL is the same. |
| basePath | string | undefined | The base path if you use a `basePath` in your app. |
| _...customRouterOptions_ | any | undefined | Any custom options for the router, by default we use the next `NavigateOptions`. |

<Callout title="Note">
The `showProgress` option set to `false` disables the progress bar. However, if you don't want to see the progress bar during a router action, we recommend you use the Next.js router directly.
Expand All @@ -193,3 +203,25 @@ interface RouterProgressOptions {
basePath?: string;
}
```

## Example with `next-intl` custom router

```tsx
import { useRouter } from '@bprogress/next/app';
import { useRouter as useNextIntlRouter } from '@/i18n/navigation';

export default function Home() {
const router = useRouter({
customRouter: useNextIntlRouter,
});

return (
<button onClick={() => router.push('/about', {
startPosition: 0.3,
locale: 'en',
})}>
Go to about page
</button>
);
}
```
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
39 changes: 39 additions & 0 deletions apps/next-app-dir/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Providers from './providers';
import './globals.css';
import { notFound } from 'next/navigation';
import { getMessages } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { NextIntlClientProvider } from 'next-intl';

export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
// Ensure that the incoming `locale` is valid
const { locale } = await params;
if (!routing.locales.includes(locale as any)) {
notFound();
}

// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();

return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
<Providers>{children}</Providers>
</NextIntlClientProvider>
</body>
</html>
);
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
'use client';

import Link from 'next/link';
import { Link } from '@/i18n/navigation';
import { useProgress } from '@bprogress/next';
import { useRouter } from '@bprogress/next/app';
import { useRouter as useNextIntlRouter } from '@/i18n/navigation';

export default function Home() {
const router = useRouter();
const router = useRouter({
customRouter: useNextIntlRouter,
});
const nextIntlRouter = useNextIntlRouter();
const { start, stop, pause, resume, getOptions, setOptions } = useProgress();

return (
Expand Down Expand Up @@ -40,6 +44,10 @@ export default function Home() {
Resume progress
</button>

<button onClick={() => nextIntlRouter.push('/dashboard')}>
Dashboard
</button>

<Link href="/">Same route</Link>
<Link href="/?test=param">Sallow</Link>
<Link href="/dashboard">Dashboard</Link>
Expand Down Expand Up @@ -71,39 +79,50 @@ export default function Home() {
<button onClick={() => router.forward()}>Forward</button>
<button
onClick={() =>
router.push('/', undefined, {
router.push('/', {
disableSameURL: false,
i18nPath: true,
})
}
>
Push disableSameURL false
</button>
<button onClick={() => router.push('/dashboard')}>Push Dashboard</button>
<button
onClick={() =>
router.push('/dashboard', {
locale: 'en',
i18nPath: true,
})
}
>
Push Dashboard
</button>
<button onClick={() => router.refresh()}>Refresh</button>
<button
onClick={() =>
router.push('/', undefined, {
router.push('/', {
disableSameURL: true,
i18nPath: true,
})
}
>
Push with disableSameURL true
</button>
<button className="replace" onClick={() => router.replace('/dashboard')}>
<button
className="replace"
onClick={() => router.replace('/dashboard', { i18nPath: true })}
>
Replace Dashboard
</button>
<button onClick={() => router.push('/?test=param')}>
<button onClick={() => router.push('/?test=param', { i18nPath: true })}>
Push with param
</button>
<button
onClick={() =>
router.push(
'/dashboard',
{},
{
showProgress: false,
},
)
router.push('/dashboard', {
showProgress: false,
i18nPath: true,
})
}
>
Push Dashboard without progress bar
Expand Down
File renamed without changes.
21 changes: 0 additions & 21 deletions apps/next-app-dir/app/layout.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions apps/next-app-dir/i18n/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';

export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
17 changes: 17 additions & 0 deletions apps/next-app-dir/i18n/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';

export default getRequestConfig(async ({ requestLocale }) => {
// This typically corresponds to the `[locale]` segment
let locale = await requestLocale;

// Ensure that a valid locale is used
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}

return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
9 changes: 9 additions & 0 deletions apps/next-app-dir/i18n/routing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineRouting } from 'next-intl/routing';

export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'fr'],

// Used when no locale matches
defaultLocale: 'en',
});
6 changes: 6 additions & 0 deletions apps/next-app-dir/messages/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"HomePage": {
"title": "Hello world!",
"about": "Go to the about page"
}
}
6 changes: 6 additions & 0 deletions apps/next-app-dir/messages/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"HomePage": {
"title": "Bonjour le monde!",
"about": "Aller à la page À propos"
}
}
9 changes: 9 additions & 0 deletions apps/next-app-dir/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

export default createMiddleware(routing);

export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(fr|en)/:path*'],
};
Loading

0 comments on commit 30fd320

Please sign in to comment.