Make a component that uses next/link for internal links and <a> for other links. It also adds the custom link attributes:
components/smart-link.tsx
import type { LinkParamValue } from "@uniformdev/canvas";
import { linkParamValueToAnchorProps } from "@uniformdev/richtext";
import Link from "next/link";
import type { ReactNode } from "react";
type SmartLinkProps = {
link: LinkParamValue;
className?: string;
children: ReactNode;
};
// True for a path in this site, for example "/about", but not for "//cdn.example.com".
const isInternalPath = (href: string) => href.startsWith("/") && !href.startsWith("//");
export const SmartLink = ({ link, className, children }: SmartLinkProps) => {
// href, plus the custom link attributes that the author set (target, rel, title, and so on).
const { href, className: authorClassName, ...attributes } = linkParamValueToAnchorProps(link);
const classes = [className, authorClassName].filter(Boolean).join(" ") || undefined;
// No link, or a link that the helper removed for security: render the text only.
if (!href) return <span className={classes}>{children}</span>;
// Project map links and site paths: client-side navigation with next/link.
if (link?.type === "projectMapNode" || (link?.type === "url" && isInternalPath(href))) {
return (
<Link {...attributes} href={href} className={classes}>
{children}
</Link>
);
}
// External URLs, email (mailto:) and phone (tel:) links: a plain anchor.
return (
<a {...attributes} href={href} className={classes}>
{children}
</a>
);
};
Step 3: Use the link component in a Uniform component#
components/navigation-link.tsx
import type { LinkParamValue } from "@uniformdev/canvas";
import {
type ComponentParameter,
type ComponentProps,
UniformText,
} from "@uniformdev/next-app-router/component";
import { SmartLink } from "./smart-link";
type NavigationLinkParameters = {
label?: ComponentParameter<string>;
link?: ComponentParameter<LinkParamValue>;
};
export const NavigationLink = ({
parameters: { label, link },
component,
}: ComponentProps<NavigationLinkParameters>) => (
<SmartLink link={link?.value} className="hover:underline">
{label ? <UniformText component={component} parameter={label} placeholder="Link text" /> : null}
</SmartLink>
);
Register NavigationLink in components/resolveComponent.tsx. You can use it in the navigation slot of a global header.
Internal and external links: a projectMapNode link always goes to a page of your site. A url link can go to your site or to a different site. The example uses next/link only for a url link that starts with /. next/link prefetches the page and changes pages without a full reload. For other links, a plain <a> element is better.
Security: the helpers remove event handler attributes (on…), the attributes href, src, style and formaction, and values with the javascript:, data: or vbscript: scheme. If the path has one of these schemes, the href is undefined.
target="_blank": if the author sets target="_blank" and no rel, the helpers add rel="noopener noreferrer".
Class names: an author can set a class attribute. The example adds it to the class names of the component.
Links in rich text:UniformRichText renders links as plain <a> elements with the custom link attributes. You do not have to do the steps on this page for rich text. Refer to Components and slots in code.