2021-07-14 20:12:04 +02:00
import React from 'react'
2020-07-13 18:08:12 +02:00
import { UrlObject } from 'url'
2020-08-05 20:12:17 +02:00
import {
isLocalURL ,
2020-08-18 18:36:40 +02:00
NextRouter ,
PrefetchOptions ,
resolveHref ,
2021-06-30 11:43:31 +02:00
} from '../shared/lib/router/router'
2022-05-30 20:19:37 +02:00
import { addLocale } from './add-locale'
2022-05-29 20:53:12 +02:00
import { RouterContext } from '../shared/lib/router-context'
2022-07-14 18:51:57 +02:00
import {
AppRouterContext ,
AppRouterInstance ,
} from '../shared/lib/app-router-context'
2020-11-07 00:03:15 +01:00
import { useIntersection } from './use-intersection'
2022-05-30 20:19:37 +02:00
import { getDomainLocale } from './get-domain-locale'
import { addBasePath } from './add-base-path'
2020-07-13 18:08:12 +02:00
2022-06-01 13:52:57 +02:00
// @ts-ignore useTransition exist
const hasUseTransition = typeof React . useTransition !== 'undefined'
2019-05-30 03:19:32 +02:00
type Url = string | UrlObject
2020-08-18 18:36:40 +02:00
type RequiredKeys < T > = {
[ K in keyof T ] - ? : { } extends Pick < T , K > ? never : K
} [ keyof T ]
type OptionalKeys < T > = {
[ K in keyof T ] - ? : { } extends Pick < T , K > ? K : never
} [ keyof T ]
2018-08-07 07:44:18 +02:00
2022-04-26 00:01:30 +02:00
type InternalLinkProps = {
2019-05-30 03:19:32 +02:00
href : Url
2019-07-11 20:52:21 +02:00
as ? : Url
2019-05-30 03:19:32 +02:00
replace? : boolean
scroll? : boolean
shallow? : boolean
passHref? : boolean
prefetch? : boolean
2020-10-22 17:08:01 +02:00
locale? : string | false
2022-04-26 00:01:30 +02:00
legacyBehavior? : boolean
2022-04-26 13:46:09 +02:00
// e: any because as it would otherwise overlap with existing types
2022-04-26 00:01:30 +02:00
/ * *
* requires experimental . newNextLinkBehavior
* /
2022-04-26 13:46:09 +02:00
onMouseEnter ? : ( e : any ) = > void
// e: any because as it would otherwise overlap with existing types
2022-07-25 21:04:03 +02:00
/ * *
* requires experimental . newNextLinkBehavior
* /
onTouchStart ? : ( e : any ) = > void
// e: any because as it would otherwise overlap with existing types
2022-04-26 00:01:30 +02:00
/ * *
* requires experimental . newNextLinkBehavior
* /
2022-04-26 13:46:09 +02:00
onClick ? : ( e : any ) = > void
2019-04-25 21:31:53 +02:00
}
2022-04-26 00:01:30 +02:00
2022-07-12 18:32:27 +02:00
// TODO-APP: Include the full set of Anchor props
2022-04-26 13:46:09 +02:00
// adding this to the publicly exported type currently breaks existing apps
export type LinkProps = InternalLinkProps
2020-08-18 18:36:40 +02:00
type LinkPropsRequired = RequiredKeys < LinkProps >
2022-04-26 00:01:30 +02:00
type LinkPropsOptional = OptionalKeys < InternalLinkProps >
2019-04-25 21:31:53 +02:00
2020-03-01 00:06:18 +01:00
const prefetched : { [ cacheKey : string ] : boolean } = { }
2019-05-01 15:14:27 +02:00
2020-07-07 06:52:26 +02:00
function prefetch (
router : NextRouter ,
href : string ,
as : string ,
options? : PrefetchOptions
) : void {
2021-01-06 17:19:57 +01:00
if ( typeof window === 'undefined' || ! router ) return
2020-08-05 20:12:17 +02:00
if ( ! isLocalURL ( href ) ) return
2020-07-02 06:53:17 +02:00
// Prefetch the JSON page if asked (only in the client)
// We need to handle a prefetch error here since we may be
// loading with priority which can reject but we don't
// want to force navigation since this is only a prefetch
2022-09-06 19:29:09 +02:00
Promise . resolve ( router . prefetch ( href , as , options ) ) . catch ( ( err ) = > {
2019-08-12 06:26:25 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
2020-07-02 06:53:17 +02:00
// rethrow to show invalid URL errors
throw err
2019-08-12 06:26:25 +02:00
}
2020-07-02 06:53:17 +02:00
} )
2020-11-17 19:04:07 +01:00
const curLocale =
options && typeof options . locale !== 'undefined'
? options . locale
: router && router . locale
2020-07-02 06:53:17 +02:00
// Join on an invalid URI character
2020-11-17 19:04:07 +01:00
prefetched [ href + '%' + as + ( curLocale ? '%' + curLocale : '' ) ] = true
2020-07-02 06:53:17 +02:00
}
2019-05-01 15:14:27 +02:00
2021-01-05 16:11:37 +01:00
function isModifiedEvent ( event : React.MouseEvent ) : boolean {
2020-08-05 20:12:17 +02:00
const { target } = event . currentTarget as HTMLAnchorElement
return (
( target && target !== '_self' ) ||
event . metaKey ||
event . ctrlKey ||
event . shiftKey ||
2020-08-10 22:32:47 +02:00
event . altKey || // triggers resource download
2020-08-05 20:12:17 +02:00
( event . nativeEvent && event . nativeEvent . which === 2 )
)
}
2020-07-02 06:53:17 +02:00
function linkClicked (
e : React.MouseEvent ,
2022-07-14 18:51:57 +02:00
router : NextRouter | AppRouterInstance ,
2020-07-02 06:53:17 +02:00
href : string ,
2020-07-07 06:52:26 +02:00
as : string ,
2020-07-02 06:53:17 +02:00
replace? : boolean ,
shallow? : boolean ,
2020-10-15 10:58:26 +02:00
scroll? : boolean ,
2022-06-01 13:52:57 +02:00
locale? : string | false ,
2022-09-06 19:29:09 +02:00
startTransition ? : ( cb : any ) = > void ,
prefetchEnabled? : boolean
2020-07-02 06:53:17 +02:00
) : void {
2020-08-05 20:12:17 +02:00
const { nodeName } = e . currentTarget
2018-08-07 05:53:06 +02:00
2022-02-06 21:53:03 +01:00
// anchors inside an svg have a lowercase nodeName
const isAnchorNodeName = nodeName . toUpperCase ( ) === 'A'
if ( isAnchorNodeName && ( isModifiedEvent ( e ) || ! isLocalURL ( href ) ) ) {
2020-08-10 22:32:47 +02:00
// ignore click for browser’ s default behavior
2020-07-02 06:53:17 +02:00
return
2019-12-20 22:30:58 +01:00
}
2020-07-02 06:53:17 +02:00
e . preventDefault ( )
2018-08-07 05:53:06 +02:00
2022-06-01 13:52:57 +02:00
const navigate = ( ) = > {
2022-09-06 19:29:09 +02:00
// If the router is an NextRouter instance it will have `beforePopState`
if ( 'beforePopState' in router ) {
2022-07-14 18:51:57 +02:00
router [ replace ? 'replace' : 'push' ] ( href , as , {
shallow ,
locale ,
scroll ,
} )
2022-09-06 19:29:09 +02:00
} else {
// If `beforePopState` doesn't exist on the router it's the AppRouter.
const method : keyof AppRouterInstance = replace ? 'replace' : 'push'
router [ method ] ( href , { forceOptimisticNavigation : ! prefetchEnabled } )
2022-07-14 18:51:57 +02:00
}
2022-06-01 13:52:57 +02:00
}
if ( startTransition ) {
startTransition ( navigate )
} else {
navigate ( )
}
2020-07-02 06:53:17 +02:00
}
2016-10-06 09:07:41 +02:00
2022-04-28 11:32:32 +02:00
type LinkPropsReal = React . PropsWithChildren <
Omit < React.AnchorHTMLAttributes < HTMLAnchorElement > , keyof LinkProps > &
LinkProps
>
const Link = React . forwardRef < HTMLAnchorElement , LinkPropsReal > (
2022-05-24 18:00:22 +02:00
function LinkComponent ( props , forwardedRef ) {
2022-04-28 11:32:32 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
function createPropError ( args : {
key : string
expected : string
actual : string
} ) {
return new Error (
` Failed prop type: The prop \` ${ args . key } \` expects a ${ args . expected } in \` <Link> \` , but got \` ${ args . actual } \` instead. ` +
( typeof window !== 'undefined'
? "\nOpen your browser's console to view the Component stack trace."
: '' )
)
2020-08-18 18:36:40 +02:00
}
2022-04-28 11:32:32 +02:00
// TypeScript trick for type-guarding:
const requiredPropsGuard : Record < LinkPropsRequired , true > = {
href : true ,
} as const
const requiredProps : LinkPropsRequired [ ] = Object . keys (
requiredPropsGuard
) as LinkPropsRequired [ ]
requiredProps . forEach ( ( key : LinkPropsRequired ) = > {
if ( key === 'href' ) {
if (
props [ key ] == null ||
( typeof props [ key ] !== 'string' && typeof props [ key ] !== 'object' )
) {
throw createPropError ( {
key ,
expected : '`string` or `object`' ,
actual : props [ key ] === null ? 'null' : typeof props [ key ] ,
} )
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _ : never = key
2020-10-15 10:58:26 +02:00
}
2022-04-28 11:32:32 +02:00
} )
// TypeScript trick for type-guarding:
const optionalPropsGuard : Record < LinkPropsOptional , true > = {
as : true ,
replace : true ,
scroll : true ,
shallow : true ,
passHref : true ,
prefetch : true ,
locale : true ,
onClick : true ,
onMouseEnter : true ,
2022-07-25 21:04:03 +02:00
onTouchStart : true ,
2022-04-28 11:32:32 +02:00
legacyBehavior : true ,
} as const
const optionalProps : LinkPropsOptional [ ] = Object . keys (
optionalPropsGuard
) as LinkPropsOptional [ ]
optionalProps . forEach ( ( key : LinkPropsOptional ) = > {
const valType = typeof props [ key ]
if ( key === 'as' ) {
if ( props [ key ] && valType !== 'string' && valType !== 'object' ) {
throw createPropError ( {
key ,
expected : '`string` or `object`' ,
actual : valType ,
} )
}
} else if ( key === 'locale' ) {
if ( props [ key ] && valType !== 'string' ) {
throw createPropError ( {
key ,
expected : '`string`' ,
actual : valType ,
} )
}
2022-07-25 21:04:03 +02:00
} else if (
key === 'onClick' ||
key === 'onMouseEnter' ||
key === 'onTouchStart'
) {
2022-04-28 11:32:32 +02:00
if ( props [ key ] && valType !== 'function' ) {
throw createPropError ( {
key ,
expected : '`function`' ,
actual : valType ,
} )
}
} else if (
key === 'replace' ||
key === 'scroll' ||
key === 'shallow' ||
key === 'passHref' ||
key === 'prefetch' ||
key === 'legacyBehavior'
) {
if ( props [ key ] != null && valType !== 'boolean' ) {
throw createPropError ( {
key ,
expected : '`boolean`' ,
actual : valType ,
} )
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _ : never = key
2020-08-18 18:36:40 +02:00
}
2022-04-28 11:32:32 +02:00
} )
2020-08-18 18:36:40 +02:00
2022-04-28 11:32:32 +02:00
// This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasWarned = React . useRef ( false )
if ( props . prefetch && ! hasWarned . current ) {
hasWarned . current = true
console . warn (
'Next.js auto-prefetches automatically based on viewport. The prefetch attribute is no longer needed. More: https://nextjs.org/docs/messages/prefetch-true-deprecated'
)
}
2016-10-06 09:07:41 +02:00
}
2022-04-26 00:01:30 +02:00
2022-04-28 11:32:32 +02:00
let children : React.ReactNode
const {
href : hrefProp ,
as : asProp ,
children : childrenProp ,
prefetch : prefetchProp ,
passHref ,
replace ,
shallow ,
scroll ,
locale ,
onClick ,
onMouseEnter ,
2022-07-25 21:04:03 +02:00
onTouchStart ,
2022-05-30 16:09:14 +02:00
legacyBehavior = Boolean ( process . env . __NEXT_NEW_LINK_BEHAVIOR ) !== true ,
2022-04-28 11:32:32 +02:00
. . . restProps
} = props
children = childrenProp
2022-06-25 22:07:40 +02:00
if (
legacyBehavior &&
( typeof children === 'string' || typeof children === 'number' )
) {
2022-04-28 11:32:32 +02:00
children = < a > { children } < / a >
2020-07-07 06:52:26 +02:00
}
2016-10-06 09:07:41 +02:00
2022-04-28 11:32:32 +02:00
const p = prefetchProp !== false
2022-06-01 13:52:57 +02:00
const [ , /* isPending */ startTransition ] = hasUseTransition
? // Rules of hooks is disabled here because the useTransition will always exist with React 18.
// There is no difference between renders in this case, only between using React 18 vs 17.
// @ts-ignore useTransition exists
// eslint-disable-next-line react-hooks/rules-of-hooks
React . useTransition ( )
: [ ]
2022-05-29 20:53:12 +02:00
let router = React . useContext ( RouterContext )
2022-07-12 18:32:27 +02:00
// TODO-APP: type error. Remove `as any`
Implement new client-side router (#37551)
## Client-side router for `app` directory
This PR implements the new router that leverages React 18 concurrent features like Suspense and startTransition.
It also integrates with React Server Components and builds on top of it to allow server-centric routing that only renders the part of the page that has to change.
It's one of the pieces of the implementation of https://nextjs.org/blog/layouts-rfc.
## Details
I'm going to document the differences with the current router here (will be reworked for the upgrade guide)
### Client-side cache
In the current router we have an in-memory cache for getStaticProps data so that if you prefetch and then navigate to a route that has been prefetched it'll be near-instant. For getServerSideProps the behavior is different, any navigation to a page with getServerSideProps fetches the data again.
In the new model the cache is a fundamental piece, it's more granular than at the page level and is set up to ensure consistency across concurrent renders. It can also be invalidated at any level.
#### Push/Replace (also applies to next/link)
The new router still has a `router.push` / `router.replace` method.
There are a few differences in how it works though:
- It only takes `href` as an argument, historically you had to provide `href` (the page path) and `as` (the actual url path) to do dynamic routing. In later versions of Next.js this is no longer required and in the majority of cases `as` was no longer needed. In the new router there's no way to reason about `href` vs `as` because there is no notion of "pages" in the browser.
- Both methods now use `startTransition`, you can wrap these in your own `startTransition` to get `isPending`
- The push/replace support concurrent rendering. When a render is bailed by clicking a different link to navigate to a completely different page that still works and doesn't cause race conditions.
- Support for optimistic loading states when navigating
##### Hard/Soft push/replace
Because of the client-side cache being reworked this now allows us to cover two cases: hard push and soft push.
The main difference between the two is if the cache is reused while navigating. The default for `next/link` is a `hard` push which means that the part of the cache affected by the navigation will be invalidated, e.g. if you already navigated to `/dashboard` and you `router.push('/dashboard')` again it'll get the latest version. This is similar to the existing `getServerSideProps` handling.
In case of a soft push (API to be defined but for testing added `router.softPush('/')`) it'll reuse the existing cache and not invalidate parts that are already filled in. In practice this means it's more like the `getStaticProps` client-side navigation because it does not fetch on navigation except if a part of the page is missing.
#### Back/Forward navigation
Back and Forward navigation ([popstate](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event)) are always handled as a soft navigation, meaning that the cache is reused, this ensures back/forward navigation is near-instant when it's in the client-side cache. This will also allow back/forward navigation to be a high priority update instead of a transition as it is based on user interaction. Note: in this PR it still uses `startTransition` as there's no way to handle the high priority update suspending which happens in case of missing data in the cache. We're working with the React team on a solution for this particular case.
### Layouts
Note: this section assumes you've read [The layouts RFC](https://nextjs.org/blog/layouts-rfc) and [React Server Components RFC](https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html)
React Server Components rendering leverages the Flight streaming mechanism in React 18, this allows sending a serializable representation of the rendered React tree on the server to the browser, the client-side React can use this serialized representation to render components client-side without the JavaScript being sent to the browser. This is one of the building blocks of Server Components. This allows a bunch of interesting features but for now I'll keep it to how it affects layouts.
When you have a `app/dashboard/layout.js` and `app/dashboard/page.js` the page will render as children of the layout, when you add another page like `app/dashboard/integrations/page.js` that page falls under the dashboard layout as well. When client-side navigating the new router automatically figures out if the page you're navigating to can be a smaller render than the whole page, in this case `app/dashboard/page.js` and `app/dashboard/integrations/page.js` share the `app/dashboard/layout.js` so instead of rendering the whole page we render below the layout component, this means the layout itself does not get re-rendered, the layout's `getServerSideProps` would not be called, and the Flight response would only hold the result of `app/dashboard/integrations/page.js`, effectively giving you the smallest patch for the UI.
---
Note: the commits in this PR were mostly work in progress to ensure it wasn't lost along the way. The implementation was reworked a bunch of times to where it is now.
Co-authored-by: Jiachi Liu <4800338+huozhi@users.noreply.github.com>
Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
2022-07-06 23:16:47 +02:00
const appRouter = React . useContext ( AppRouterContext ) as any
2022-05-29 20:53:12 +02:00
if ( appRouter ) {
router = appRouter
}
2022-04-04 20:18:49 +02:00
2022-04-28 11:32:32 +02:00
const { href , as } = React . useMemo ( ( ) = > {
const [ resolvedHref , resolvedAs ] = resolveHref ( router , hrefProp , true )
return {
href : resolvedHref ,
as : asProp ? resolveHref ( router , asProp ) : resolvedAs || resolvedHref ,
2022-04-26 00:01:30 +02:00
}
2022-04-28 11:32:32 +02:00
} , [ router , hrefProp , asProp ] )
const previousHref = React . useRef < string > ( href )
const previousAs = React . useRef < string > ( as )
// This will return the first child, if multiple are provided it will throw an error
let child : any
if ( legacyBehavior ) {
if ( process . env . NODE_ENV === 'development' ) {
if ( onClick ) {
console . warn (
` "onClick" was passed to <Link> with \` href \` of \` ${ hrefProp } \` but "legacyBehavior" was set. The legacy behavior requires onClick be set on the child of next/link `
)
}
if ( onMouseEnter ) {
console . warn (
` "onMouseEnter" was passed to <Link> with \` href \` of \` ${ hrefProp } \` but "legacyBehavior" was set. The legacy behavior requires onMouseEnter be set on the child of next/link `
)
}
try {
child = React . Children . only ( children )
} catch ( err ) {
if ( ! children ) {
throw new Error (
` No children were passed to <Link> with \` href \` of \` ${ hrefProp } \` but one child is required https://nextjs.org/docs/messages/link-no-children `
)
}
2022-04-26 00:01:30 +02:00
throw new Error (
2022-04-28 11:32:32 +02:00
` Multiple children were passed to <Link> with \` href \` of \` ${ hrefProp } \` but only one child is supported https://nextjs.org/docs/messages/link-multiple-children ` +
( typeof window !== 'undefined'
? " \nOpen your browser's console to view the Component stack trace."
: '' )
2022-04-26 00:01:30 +02:00
)
}
2022-04-28 11:32:32 +02:00
} else {
child = React . Children . only ( children )
2022-03-22 19:58:55 +01:00
}
2021-05-31 21:41:57 +02:00
}
2022-04-26 00:01:30 +02:00
2022-04-28 11:32:32 +02:00
const childRef : any = legacyBehavior
? child && typeof child === 'object' && child . ref
: forwardedRef
2020-11-01 04:37:28 +01:00
2022-04-28 11:32:32 +02:00
const [ setIntersectionRef , isVisible , resetVisible ] = useIntersection ( {
rootMargin : '200px' ,
} )
2022-04-04 20:18:49 +02:00
2022-04-28 11:32:32 +02:00
const setRef = React . useCallback (
( el : Element ) = > {
// Before the link getting observed, check if visible state need to be reset
if ( previousAs . current !== as || previousHref . current !== href ) {
resetVisible ( )
previousAs . current = as
previousHref . current = href
}
2022-04-04 20:18:49 +02:00
2022-04-28 11:32:32 +02:00
setIntersectionRef ( el )
if ( childRef ) {
if ( typeof childRef === 'function' ) childRef ( el )
else if ( typeof childRef === 'object' ) {
childRef . current = el
}
2020-11-01 04:37:28 +01:00
}
2022-04-28 11:32:32 +02:00
} ,
[ as , childRef , href , resetVisible , setIntersectionRef ]
)
React . useEffect ( ( ) = > {
const shouldPrefetch = isVisible && p && isLocalURL ( href )
const curLocale =
typeof locale !== 'undefined' ? locale : router && router . locale
const isPrefetched =
prefetched [ href + '%' + as + ( curLocale ? '%' + curLocale : '' ) ]
if ( shouldPrefetch && ! isPrefetched ) {
prefetch ( router , href , as , {
locale : curLocale ,
} )
2020-11-01 04:37:28 +01:00
}
2022-04-28 11:32:32 +02:00
} , [ as , href , isVisible , locale , p , router ] )
const childProps : {
2022-07-25 21:04:03 +02:00
onTouchStart : React.TouchEventHandler
2022-04-28 11:32:32 +02:00
onMouseEnter : React.MouseEventHandler
onClick : React.MouseEventHandler
href? : string
ref? : any
} = {
ref : setRef ,
onClick : ( e : React.MouseEvent ) = > {
if ( process . env . NODE_ENV !== 'production' ) {
if ( ! e ) {
throw new Error (
` Component rendered inside next/link has to pass click event to "onClick" prop. `
)
}
2022-02-07 01:04:37 +01:00
}
2022-04-26 00:01:30 +02:00
2022-04-28 11:32:32 +02:00
if ( ! legacyBehavior && typeof onClick === 'function' ) {
onClick ( e )
}
if (
legacyBehavior &&
child . props &&
typeof child . props . onClick === 'function'
) {
child . props . onClick ( e )
}
if ( ! e . defaultPrevented ) {
2022-06-01 13:52:57 +02:00
linkClicked (
e ,
router ,
href ,
as ,
replace ,
shallow ,
scroll ,
locale ,
2022-09-06 19:29:09 +02:00
appRouter ? startTransition : undefined ,
p
2022-06-01 13:52:57 +02:00
)
2022-04-28 11:32:32 +02:00
}
} ,
onMouseEnter : ( e : React.MouseEvent ) = > {
if ( ! legacyBehavior && typeof onMouseEnter === 'function' ) {
onMouseEnter ( e )
}
if (
legacyBehavior &&
child . props &&
typeof child . props . onMouseEnter === 'function'
) {
child . props . onMouseEnter ( e )
}
2022-09-06 19:29:09 +02:00
// Check for not prefetch disabled in page using appRouter
if ( ! ( ! p && appRouter ) ) {
if ( isLocalURL ( href ) ) {
prefetch ( router , href , as , { priority : true } )
}
2022-04-28 11:32:32 +02:00
}
} ,
2022-07-25 21:04:03 +02:00
onTouchStart : ( e : React.TouchEvent < HTMLAnchorElement > ) = > {
if ( ! legacyBehavior && typeof onTouchStart === 'function' ) {
onTouchStart ( e )
}
if (
legacyBehavior &&
child . props &&
typeof child . props . onTouchStart === 'function'
) {
child . props . onTouchStart ( e )
}
2022-09-06 19:29:09 +02:00
// Check for not prefetch disabled in page using appRouter
if ( ! ( ! p && appRouter ) ) {
if ( isLocalURL ( href ) ) {
prefetch ( router , href , as , { priority : true } )
}
2022-07-25 21:04:03 +02:00
}
} ,
2022-04-28 11:32:32 +02:00
}
2016-10-06 09:07:41 +02:00
2022-04-28 11:32:32 +02:00
// If child is an <a> tag and doesn't have a href attribute, or if the 'passHref' property is
// defined, we specify the current 'href', so that repetition is not needed by the user
if (
! legacyBehavior ||
passHref ||
( child . type === 'a' && ! ( 'href' in child . props ) )
) {
const curLocale =
typeof locale !== 'undefined' ? locale : router && router . locale
// we only render domain locales if we are currently on a domain locale
// so that locale links are still visitable in development/preview envs
const localeDomain =
router &&
router . isLocaleDomain &&
2022-05-30 20:19:37 +02:00
getDomainLocale ( as , curLocale , router . locales , router . domainLocales )
2016-10-06 09:07:41 +02:00
2022-04-28 11:32:32 +02:00
childProps . href =
localeDomain ||
addBasePath ( addLocale ( as , curLocale , router && router . defaultLocale ) )
}
return legacyBehavior ? (
React . cloneElement ( child , childProps )
) : (
< a { ...restProps } { ...childProps } >
{ children }
< / a >
)
}
)
2016-10-06 09:07:41 +02:00
2018-08-07 05:23:28 +02:00
export default Link