amannn / next-intl

🌐 Internationalization (i18n) for Next.js
https://next-intl-docs.vercel.app
MIT License
2.37k stars 214 forks source link

Link component fails to render when parent uses `asChild` prop #1322

Closed MeesEgberts closed 3 weeks ago

MeesEgberts commented 3 weeks ago

Description

The Link component from next-intl is not rendering when used within a parent component that has the asChild prop set. This issue occurs specifically when trying to use the Link component inside a DropdownMenuItem from shadcn/ui or @radix-ui/react-dropdown-menu.

Verifications

Mandatory reproduction URL

https://www.radix-ui.com/primitives/docs/components/dropdown-menu#dropdown-menu

Reproduction description

import ...

export async function DashboardHeaderAccountDropdown() {
  const t = await getTranslations("app.header.account-dropdown");

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="secondary" size="icon" className="rounded-full">
          <CircleUser className="h-5 w-5" />
          <span className="sr-only">{t("toggle")}</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuLabel>{t("title")}</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem asChild>
          <Link
            className="flex items-center"
            href={{ pathname: "/app/account" }}
          >
            <Settings className="text-muted-foreground mr-2 h-4 w-4" />
            {t("settings")}
          </Link>
        </DropdownMenuItem>
        <DropdownMenuItem asChild>
          <Link href={{ pathname: "/app/account/security" }}>
            <Lock className="text-muted-foreground mr-2 h-4 w-4" />
            {t("security")}
          </Link>
        </DropdownMenuItem>
        <DropdownMenuSeparator />
        {/*<DropdownMenuItem>Logout</DropdownMenuItem>*/}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Expected behaviour

I've tried using the next/link component and it renders just fine, but here nothing gets rendered.

amannn commented 3 weeks ago

This issue was discussed before in https://github.com/amannn/next-intl/issues/665 and https://github.com/amannn/next-intl/issues/1271.

What causes the issue is that Radix uses cloneElement in a component that is marked with 'use client'. The component tries to modify the child, but in your case <Link /> is a Server Component, therefore the props can't be modified anymore in the Client Components render pass (esp. if you consider that they run on the client side).

Link from next-intl is currently either a Server or a Client Component, depending on the environment you're importing it from. The reason for this is that we can avoid shipping some code to the client if you're calling Link in a Server Component.

Due to this, a possible workaround is to call Link in a Client Component.

I'm currently working on an overhaul for the navigation APIs and will have a look at this topic to see if we can do something about this to improve the compatibility with libraries like Radix that use cloneElement.

I'm closing this in favor of https://github.com/amannn/next-intl/issues/1271.