Next.js
Next.js is a popular React framework that provides a lot of features out of the box. Ladle works well with Next.js, but there are some caveats.
next/image
and next/link
This component relies on a build-time transformation that Next.js provides. However, Ladle has its own build process. To make next/image
work, we need to replace it with a simple <img />
element.
You'll need to customize Vite's config. Place it in the root directory of your project.
import path from "path";
import { defineConfig } from "vite";
export default defineConfig({
resolve: {
alias: {
"next/image": path.resolve(__dirname, "./.ladle/UnoptimizedImage.tsx"),
"next/link": path.resolve(__dirname, "./.ladle/UnoptimizedLink.tsx"),
},
},
});
const UnoptimizedLink = (props: any) => {
return <a {...props} />;
};
export default UnoptimizedLink;
import React from 'react';
interface UnoptimizedImageProps
extends React.ImgHTMLAttributes<HTMLImageElement> {
fill?: boolean;
}
const UnoptimizedImage: React.FC<UnoptimizedImageProps> = ({
fill,
...props
}) => {
const style: React.CSSProperties = fill
? {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
}
: {};
return <img {...props} style={style} />;
};
export default UnoptimizedImage;
This solution is inspired by blog post.
next/navigation
In the Stories file, using useRouter()
of next/navigation
may cause the following error. Uncaught Error: invariant expected app router to be mounted.
You could solve it by setting Providers.
import { GlobalProvider } from "@ladle/react";
import { AppRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
export const Provider: GlobalProvider = ({ children }) => {
return (
<AppRouterContext.Provider
value={{
back: () => {
// Do nothing
},
forward: () => {
// Do nothing
},
prefetch: () => {
// Do nothing
},
push: () => {
// Do nothing
},
refresh: () => {
// Do nothing
},
replace: () => {
// Do nothing
},
}}
>
{children}
</AppRouterContext.Provider>
);
};
Or if you want to set it in each file, you could use Decorators.
import type { StoryDefault, Story } from "@ladle/react";
import { AppRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { useRouter } from "next/navigation";
export default {
decorators: [
(Component) => {
return (
<AppRouterContext.Provider
value={{
back: () => {
// Do nothing
},
forward: () => {
// Do nothing
},
prefetch: () => {
// Do nothing
},
push: () => {
// Do nothing
},
refresh: () => {
// Do nothing
},
replace: () => {
// Do nothing
},
}}
>
<Component />
</AppRouterContext.Provider>
);
},
],
} satisfies StoryDefault;
export const Hello: Story = () => {
const router = useRouter();
return (
<>
<h1>Hello Next.js App Router</h1>
<button onClick={() => router.push("/example")}>Route</button>
</>
);
};
If you want to wrap all stories with AppRouterContext
, you can add it to the global provider instead.
Using environment variables
To make use of your current .env
files, you simply need to let Vite know and ensure they are passed to the browser. As Next.js requires you to add NEXT_PUBLIC_
as a prefix to your frontend environment variables, we also need to inform Vite about this.
To achieve this, you will need to customize Vite's configuration further.
import { defineConfig, loadEnv } from "vite";
export default defineConfig(({ mode }) => ({
// resolve: {...},
define: {
"process.env": loadEnv(mode, process.cwd(), "NEXT_PUBLIC_"),
},
}));