Skip to content

Commit

Permalink
remove unnecessary handling in forked Link components (#73020)
Browse files Browse the repository at this point in the history
This follows up the work from #73019 to update the implementations for
the forked Link components to remove Pages specific handling in the App
link, and App specific handling in the Pages link.
  • Loading branch information
ztanner authored and wyattjoh committed Nov 28, 2024
1 parent 2b49109 commit a7fe58f
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 365 deletions.
224 changes: 33 additions & 191 deletions packages/next/src/client/app-dir/link.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
'use client'

import type {
NextRouter,
PrefetchOptions as RouterPrefetchOptions,
} from '../../shared/lib/router/router'
import type { NextRouter } from '../../shared/lib/router/router'

import React from 'react'
import type { UrlObject } from 'url'
import { resolveHref } from '../resolve-href'
import { isLocalURL } from '../../shared/lib/router/utils/is-local-url'
import { formatUrl } from '../../shared/lib/router/utils/format-url'
import { isAbsoluteUrl } from '../../shared/lib/utils'
import { addLocale } from '../add-locale'
import { RouterContext } from '../../shared/lib/router-context.shared-runtime'
import { AppRouterContext } from '../../shared/lib/app-router-context.shared-runtime'
import type {
AppRouterInstance,
PrefetchOptions as AppRouterPrefetchOptions,
} from '../../shared/lib/app-router-context.shared-runtime'
import type { AppRouterInstance } from '../../shared/lib/app-router-context.shared-runtime'
import type { PrefetchOptions } from '../../shared/lib/app-router-context.shared-runtime'
import { useIntersection } from '../use-intersection'
import { getDomainLocale } from '../get-domain-locale'
import { addBasePath } from '../add-base-path'
import { PrefetchKind } from '../components/router-reducer/router-reducer-types'
import { useMergedRef } from '../use-merged-ref'
import { isAbsoluteUrl } from '../../shared/lib/utils'
import { addBasePath } from '../add-base-path'

type Url = string | UrlObject
type RequiredKeys<T> = {
Expand Down Expand Up @@ -120,70 +110,22 @@ export type LinkProps<RouteInferType = any> = InternalLinkProps
type LinkPropsRequired = RequiredKeys<LinkProps>
type LinkPropsOptional = OptionalKeys<InternalLinkProps>

const prefetched = new Set<string>()

type PrefetchOptions = RouterPrefetchOptions & {
/**
* bypassPrefetchedCheck will bypass the check to see if the `href` has
* already been fetched.
*/
bypassPrefetchedCheck?: boolean
}

function prefetch(
router: NextRouter | AppRouterInstance,
router: AppRouterInstance,
href: string,
as: string,
options: PrefetchOptions,
appOptions: AppRouterPrefetchOptions,
isAppRouter: boolean
options: PrefetchOptions
): void {
if (typeof window === 'undefined') {
return
}

// app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router.
if (!isAppRouter && !isLocalURL(href)) {
return
}

// We should only dedupe requests when experimental.optimisticClientCache is
// disabled & when we're not using the app router. App router handles
// reusing an existing prefetch entry (if it exists) for the same URL.
// If we dedupe in here, we will cause a race where different prefetch kinds
// to the same URL (ie auto vs true) will cause one to be ignored.
if (!options.bypassPrefetchedCheck && !isAppRouter) {
const locale =
// Let the link's locale prop override the default router locale.
typeof options.locale !== 'undefined'
? options.locale
: // Otherwise fallback to the router's locale.
'locale' in router
? router.locale
: undefined

const prefetchedKey = href + '%' + as + '%' + locale

// If we've already fetched the key, then don't prefetch it again!
if (prefetched.has(prefetchedKey)) {
return
}

// Mark this URL as prefetched.
prefetched.add(prefetchedKey)
}

const doPrefetch = async () => {
if (isAppRouter) {
// note that `appRouter.prefetch()` is currently sync,
// so we have to wrap this call in an async function to be able to catch() errors below.
return (router as AppRouterInstance).prefetch(href, appOptions)
} else {
return (router as NextRouter).prefetch(href, as, options)
}
// note that `appRouter.prefetch()` is currently sync,
// so we have to wrap this call in an async function to be able to catch() errors below.
return router.prefetch(href, options)
}

// Prefetch the JSON page if asked (only in the client)
// Prefetch the 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
Expand Down Expand Up @@ -216,20 +158,14 @@ function linkClicked(
replace?: boolean,
shallow?: boolean,
scroll?: boolean,
locale?: string | false,
isAppRouter?: boolean
locale?: string | false
): void {
const { nodeName } = e.currentTarget

// anchors inside an svg have a lowercase nodeName
const isAnchorNodeName = nodeName.toUpperCase() === 'A'

if (
isAnchorNodeName &&
(isModifiedEvent(e) ||
// app-router supports external urls out of the box so it shouldn't short-circuit here as support for e.g. `replace` is added in the app-router.
(!isAppRouter && !isLocalURL(href)))
) {
if (isAnchorNodeName && isModifiedEvent(e)) {
// ignore click for browser’s default behavior
return
}
Expand All @@ -252,11 +188,7 @@ function linkClicked(
}
}

if (isAppRouter) {
React.startTransition(navigate)
} else {
navigate()
}
React.startTransition(navigate)
}

type LinkPropsReal = React.PropsWithChildren<
Expand Down Expand Up @@ -310,12 +242,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
children = <a>{children}</a>
}

const pagesRouter = React.useContext(RouterContext)
const appRouter = React.useContext(AppRouterContext)
const router = pagesRouter ?? appRouter

// We're in the app directory if there is no pages router.
const isAppRouter = !pagesRouter
const router = React.useContext(AppRouterContext)

const prefetchEnabled = prefetchProp !== false
/**
Expand Down Expand Up @@ -439,7 +366,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
}

if (process.env.NODE_ENV !== 'production') {
if (isAppRouter && !asProp) {
if (!asProp) {
let href: string | undefined
if (typeof hrefProp === 'string') {
href = hrefProp
Expand All @@ -465,27 +392,12 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
}

const { href, as } = React.useMemo(() => {
if (!pagesRouter) {
const resolvedHref = formatStringOrUrl(hrefProp)
return {
href: resolvedHref,
as: asProp ? formatStringOrUrl(asProp) : resolvedHref,
}
}

const [resolvedHref, resolvedAs] = resolveHref(
pagesRouter,
hrefProp,
true
)

const resolvedHref = formatStringOrUrl(hrefProp)
return {
href: resolvedHref,
as: asProp
? resolveHref(pagesRouter, asProp)
: resolvedAs || resolvedHref,
as: asProp ? formatStringOrUrl(asProp) : resolvedHref,
}
}, [pagesRouter, hrefProp, asProp])
}, [hrefProp, asProp])

const previousHref = React.useRef<string>(href)
const previousAs = React.useRef<string>(as)
Expand Down Expand Up @@ -573,27 +485,10 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
}

// Prefetch the URL.
prefetch(
router,
href,
as,
{ locale },
{
kind: appPrefetchKind,
},
isAppRouter
)
}, [
as,
href,
isVisible,
locale,
prefetchEnabled,
pagesRouter?.locale,
router,
isAppRouter,
appPrefetchKind,
])
prefetch(router, href, {
kind: appPrefetchKind,
})
}, [as, href, isVisible, locale, prefetchEnabled, router, appPrefetchKind])

const childProps: {
onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
Expand Down Expand Up @@ -632,17 +527,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
return
}

linkClicked(
e,
router,
href,
as,
replace,
shallow,
scroll,
locale,
isAppRouter
)
linkClicked(e, router, href, as, replace, shallow, scroll, locale)
},
onMouseEnter(e) {
if (!legacyBehavior && typeof onMouseEnterProp === 'function') {
Expand All @@ -661,28 +546,13 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
return
}

if (
(!prefetchEnabled || process.env.NODE_ENV === 'development') &&
isAppRouter
) {
if (!prefetchEnabled || process.env.NODE_ENV === 'development') {
return
}

prefetch(
router,
href,
as,
{
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true,
},
{
kind: appPrefetchKind,
},
isAppRouter
)
prefetch(router, href, {
kind: appPrefetchKind,
})
},
onTouchStart: process.env.__NEXT_LINK_NO_TOUCH_START
? undefined
Expand All @@ -703,25 +573,13 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
return
}

if (!prefetchEnabled && isAppRouter) {
if (!prefetchEnabled) {
return
}

prefetch(
router,
href,
as,
{
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true,
},
{
kind: appPrefetchKind,
},
isAppRouter
)
prefetch(router, href, {
kind: appPrefetchKind,
})
},
}

Expand All @@ -735,23 +593,7 @@ const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
passHref ||
(child.type === 'a' && !('href' in child.props))
) {
const curLocale =
typeof locale !== 'undefined' ? locale : pagesRouter?.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 =
pagesRouter?.isLocaleDomain &&
getDomainLocale(
as,
curLocale,
pagesRouter?.locales,
pagesRouter?.domainLocales
)

childProps.href =
localeDomain ||
addBasePath(addLocale(as, curLocale, pagesRouter?.defaultLocale))
childProps.href = addBasePath(as)
}

return legacyBehavior ? (
Expand Down
Loading

0 comments on commit a7fe58f

Please sign in to comment.