Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[examples] Add Next.js 13 compatible version of Link adapter #38797

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 51 additions & 50 deletions docs/data/material/guides/routing/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,53 +97,54 @@ const LinkBehavior = React.forwardRef((props, ref) => (

{{"demo": "ListRouter.js"}}

## More examples

### Next.js Pages Router

The [example folder](https://github.com/mui/material-ui/tree/HEAD/examples/material-ui-nextjs-pages-router-ts) provides an adapter for the use of [Next.js's Link component](https://nextjs.org/docs/pages/api-reference/components/link) with Material UI.

- The first version of the adapter is the [`NextLinkComposed`](https://github.com/mui/material-ui/blob/-/examples/material-ui-nextjs-pages-router-ts/src/Link.tsx) component.
This component is unstyled and only responsible for handling the navigation.
The prop `href` was renamed `to` to avoid a naming conflict.
This is similar to react-router's Link component.

```tsx
import Button from '@mui/material/Button';
import { NextLinkComposed } from '../src/Link';

export default function Index() {
return (
<Button
component={NextLinkComposed}
to={{
pathname: '/about',
query: { name: 'test' },
}}
>
Button link
</Button>
);
}
```

- The second version of the adapter is the `Link` component.
This component is styled.
It uses the [Material UI Link component](/material-ui/react-link/) with `NextLinkComposed`.

```tsx
import Link from '../src/Link';

export default function Index() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
Link
</Link>
);
}
```
## Next.js

The example repos provide adapter components for the use of [Next.js's Link component](https://nextjs.org/docs/api-reference/next/link) with Material UI:

- [Next.js App Router example repo](https://github.com/mui/material-ui/tree/HEAD/examples/material-ui-nextjs-ts)
- [Next.js Pages Router example repo](https://github.com/mui/material-ui/tree/HEAD/examples/material-ui-nextjs-pages-router-ts)

The first version of the adapter is the [`NextLinkComposed`](https://github.com/mui/material-ui/blob/-/examples/material-ui-nextjs-ts/src/Link.tsx) component.
This component is unstyled and only responsible for handling the navigation.
The `href` prop from Next.js's Link is renamed to `to` in order to distinguish from `@mui/material/Link`'s `href` prop.
The resulting API is similar to react-router's Link component:

```tsx
import Button from '@mui/material/Button';
import { NextLinkComposed } from '../src/Link';

export default function App() {
return (
<Button
component={NextLinkComposed}
to={{
pathname: '/about',
query: { name: 'test' },
}}
>
Button link
</Button>
);
}
```

The second version of the adapter is the `Link` component.
This component is styled.
It uses the [Material UI Link component](/material-ui/react-link/) with `NextLinkComposed`.

```tsx
import Link from '../src/Link';

export default function App() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
Link
</Link>
);
}
```
98 changes: 98 additions & 0 deletions examples/material-ui-nextjs-ts/src/components/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client';
import * as React from 'react';
import clsx from 'clsx';
import { usePathname } from 'next/navigation';
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
import MaterialLink, { LinkProps as MaterialLinkProps } from '@mui/material/Link';
import { styled } from '@mui/material/styles';

// Add support for the sx prop for consistency with the other branches.
const Anchor = styled('a')({});

interface NextLinkComposedProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>,
Omit<NextLinkProps, 'href' | 'as' | 'passHref' | 'onMouseEnter' | 'onClick' | 'onTouchStart'> {
to: NextLinkProps['href'];
linkAs?: NextLinkProps['as'];
}

export const NextLinkComposed = React.forwardRef<HTMLAnchorElement, NextLinkComposedProps>(
function NextLinkComposed(props, ref) {
const { to, linkAs, ...other } = props;

return <NextLink as={linkAs} ref={ref} {...other} href={to} />;
},
);

export type LinkProps = {
activeClassName?: string;
as?: NextLinkProps['as'];
href: NextLinkProps['href'];
linkAs?: NextLinkProps['as']; // Useful when the as prop is shallow by styled().
noLinkStyle?: boolean;
} & Omit<NextLinkComposedProps, 'to' | 'linkAs' | 'href'> &
Omit<MaterialLinkProps, 'href'>;

// A styled version of the Next.js Link component:
// https://nextjs.org/docs/app/api-reference/components/link
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link(props, ref) {
const {
activeClassName = 'active',
as,
className: classNameProp,
href,
linkAs: linkAsProp,
locale,
noLinkStyle,
prefetch,
replace,
role, // Link don't have roles.
scroll,
shallow,
...other
} = props;

const routerPathname = usePathname();
const pathname = typeof href === 'string' ? href : href.pathname;
const className = clsx(classNameProp, {
[activeClassName]: routerPathname === pathname && activeClassName,
});

const isExternal =
typeof href === 'string' && (href.startsWith('http') || href.startsWith('mailto:'));

if (isExternal) {
if (noLinkStyle) {
return <Anchor className={className} href={href} ref={ref} {...other} />;
}

return <MaterialLink className={className} href={href} ref={ref} {...other} />;
}

const linkAs = linkAsProp || as;
const nextjsProps = {
to: href,
linkAs,
replace,
scroll,
shallow,
prefetch,
locale,
};

if (noLinkStyle) {
return <NextLinkComposed className={className} ref={ref} {...nextjsProps} {...other} />;
}

return (
<MaterialLink
component={NextLinkComposed}
className={className}
ref={ref}
{...nextjsProps}
{...other}
/>
);
});

export default Link;