Set up Storybook to work with Next.js's Link tag
Asked Answered
L

2

7

I'm trying to set up Storybook for a Next.js project. I have a component that render the Link tag from Next.js. My problem is that when I load this component, Storybook throws the following error:

Cannot read property 'pageLoader' of null
   at Link.handleRef

What does one have to do to get Storybook working with Next.js Routing, specifically rendering the Link tag?

Update: Code that causes the error:

// button-component.js
import Link from 'next/link.js';
import t from 'prop-types';
import React from 'react';

function Button({ as, children, href, ...props }) {
  const isExternal = href && href.startsWith('http');
  const a = (
    <a href={href} {...props}>
      {children}
    </a>
  );

  if (href) {
    return isExternal ? (
      a
    ) : (
      <Link href={href} as={as}>
        {a}
      </Link>
    );
  }

  return (
    <button type="button" {...props}>
      {children}
    </button>
  );
}

Button.propTypes = {
  as: t.string,
  children: t.node,
  href: t.string,
};

export default React.memo(Button);
// button.stories.js
import React from 'react';

import Button from './button-component';

export default {
  title: 'Button',
};

export const standardButton = () => <Button>Standard Button</Button>;

export const internalLink = () => <Button href='/buy'>
  Internal Link
</Button>;

export const externalLink = () => (
  <Button target="_blank" href="https://www.hopin.to">
    External Link
  </Button>
);
Leoni answered 13/1, 2020 at 7:53 Comment(12)
Can you post some code?Stribling
Which next version?Accountancy
@Stribling Sure, done!Leoni
@Accountancy Next.js: "9.1.7", Storybook: "5.3.1"Leoni
Pls add Error Stack-traceAccountancy
@Accountancy The full error stack trace is useless, because it points to the compiled modules by Storebook.Leoni
Can you setup a small repo that reproduces this issue?Accountancy
What's your file structure for this project? Is button.stories.js a next.js page?Stribling
@Stribling No. Button.stories.js is simply for Storybook.Leoni
Did you resolve the issue?Stribling
Did upgrading to the canary release of next.js not work?Stribling
And also, Next.js 9.2 came out yesterday. I imagine updating to that would have the same effect.Stribling
S
5

I found an issue reported about this on Next.js's github: https://github.com/zeit/next.js/issues/9951

It was reported only 5 days ago, so you could be having the same issue. The resolution is to upgrade to nextjs v9.1.8-canary.6. Reading more about this and looking at the source code, this is likely your problem. Also, there are more recent canary builds of nextjs, if you want to try something newer.

If that doesn't resolve it, my other guess is that you're getting errors because you're using Link outside of a Next.js page. Next.js may include dependencies for pages, behind the scenes. Link may rely on those dependencies and is throwing an error when they aren't found. If you want to test your components outside of Next.js pages, you could create a custom Link component that tests whether you're in Next.js and only renders Link if you are. For example:

import Link from 'next/link'
import Router from 'next/router'

const CustomLink = ({children, ...otherProps}) => {
  const isPage = () => {
    // if we're in a next.js route, then Router.router will be set
    return Boolean(Router.router)
  }

  return isPage()
    ? (<Link {...otherProps}>{children}</Link>)
    : children
}

Then use CustomLink instead of Link.

Stribling answered 13/1, 2020 at 19:55 Comment(1)
Instead of isPage() function you can use process.env.STORYBOOK === 'true'. This approach won't hide errors in case your code will run outside storybook and for some reason without Router contextBushelman
L
0

Another solution I found works similar as with next/image. To your .storybook/preview.js add following:

import Link from "next/link";

Object.defineProperty(Link, "default", {
  configurable: true,
  value: (props) => <a {...props} />,
});
Landgravine answered 1/2, 2023 at 22:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.