Skip to content

Commit

Permalink
Add Next.js 13 compatible version of Link adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Sep 4, 2023
1 parent c1c3647 commit 1d3877c
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 50 deletions.
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/api-reference/next/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 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 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;

0 comments on commit 1d3877c

Please sign in to comment.