Links

Developer preview

This feature is in developer preview. Use with caution as it may change unexpectedly. For more information, contact us.

Goal: render a link that an author sets in a link parameter. Use next/link for links in your site, and a plain <a> element for other links.

  • A Next.js app with the SDK. Refer to Next.js App Router SDK.

  • A component definition with a parameter of the type Link, for example link.

  • The @uniformdev/canvas and @uniformdev/richtext packages, at the same version as the SDK:

    v=20.81.1-alpha.25.sha-f076f9b857 npm install @uniformdev/canvas@$v @uniformdev/richtext@$v

The type of a link parameter value is LinkParamValue from @uniformdev/canvas:

type LinkParamValue = | { type: "projectMapNode"; // a page of the project map projectMapId: string; nodeId: string; path: string; dynamicInputValues?: Record<string, string>; attributes?: Record<string, string>; } | { type: "url" | "tel" | "email"; path: string; attributes?: Record<string, string>; } | undefined;

attributes contains the custom link attributes that the author set, for example target or title.

@uniformdev/richtext has three helpers for this value:

HelperReturns
linkParamValueToHref(link)The href as string | undefined. It adds mailto: to email links and tel: to phone links.
linkParamValueToHtmlAttributes(link)href and the custom link attributes, as HTML attributes.
linkParamValueToAnchorProps(link)The same as linkParamValueToHtmlAttributes, but for React: it changes class to className.

If you need only the href, use linkParamValueToHref:

components/button.tsx

import type { LinkParamValue } from "@uniformdev/canvas"; import type { ComponentParameter, ComponentProps } from "@uniformdev/next-app-router/component"; import { linkParamValueToHref } from "@uniformdev/richtext"; import Link from "next/link"; type ButtonParameters = { label?: ComponentParameter<string>; link?: ComponentParameter<LinkParamValue>; }; export const Button = ({ parameters: { label, link } }: ComponentProps<ButtonParameters>) => { const href = linkParamValueToHref(link?.value); if (!href) return null; return ( <Link href={href} className="rounded bg-black px-4 py-2 text-white"> {label?.value ?? "Read more"} </Link> ); };

This example ignores the custom link attributes. Use step 2 to keep them.

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> ); };

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.