-
Notifications
You must be signed in to change notification settings - Fork 27.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image component does not work with Storybook #18393
Comments
The import is fixed when adding However, this only solves the problem for 3rd party loaders like imgix, cloudinary, etc. For the default loader, we still need a way to expose the API at For example, storybook is running on 6006 and next dev is running on 3000, so we need to change the |
Is there any workaround for this? |
This comment has been minimized.
This comment has been minimized.
@steoneill I don't until it's stable. I use my own implementation of dynamic slices. I am also experiencing very low google pagespeed score, looks like because of a big amount of props (because of slices). Do you experience the same? |
@SZharkov yeah im seeing that too sadly. |
@aippili-asp I believe I have a workaround.
What I did was the following:
|
I found a solution, and it works fine in my environment. I'll share this solution.
This option will change "_next/image" directory to "/public" directory
I hope this solution will solve it. |
Thanks! This workaround works for me! and I've added babel-plugin-react-remove-properties in my project to remove unoptimized attribute when building or in production mode. |
There's a simpler workaround which is to override Add following code in import * as nextImage from "next/image"
Object.defineProperty(nextImage, "default", {
configurable: true,
value: props => {
const { width, height } = props
const ratio = (height / width) * 100
return (
<div
style={{
paddingBottom: `${ratio}%`,
position: "relative",
}}
>
<img
style={{
objectFit: "cover",
position: "absolute",
minWidth: "100%",
minHeight: "100%",
maxWidth: "100%",
maxHeight: "100%",
}}
{...props}
/>
</div>
)
},
}) If you prefer to render the same output as Object.defineProperty(nextImage, "default", {
configurable: true,
value: props => {
const height = props.height
const width = props.width
const quotient = height / width
const paddingTop = isNaN(quotient) ? "100%" : `${quotient * 100}%`
let wrapperStyle
let sizerStyle
let sizerSvg
let toBase64
let imgStyle = {
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
boxSizing: "border-box",
padding: 0,
border: "none",
margin: "auto",
display: "block",
width: 0,
height: 0,
minWidth: "100%",
maxWidth: "100%",
minHeight: "100%",
maxHeight: "100%",
objectFit: props.objectFit ? props.objectFit : undefined,
objectPosition: props.objectPosition ? props.objectPosition : undefined,
}
if (width !== undefined && height !== undefined && props.layout !== "fill") {
if (props.layout === "responsive") {
wrapperStyle = {
display: "block",
overflow: "hidden",
position: "relative",
boxSizing: "border-box",
margin: 0,
}
sizerStyle = {
display: "block",
boxSizing: "border-box",
paddingTop,
}
} else if (props.layout === "intrinsic" || props.layout === undefined) {
wrapperStyle = {
display: "inline-block",
maxWidth: "100%",
overflow: "hidden",
position: "relative",
boxSizing: "border-box",
margin: 0,
}
sizerStyle = {
boxSizing: "border-box",
display: "block",
maxWidth: "100%",
}
sizerSvg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" version="1.1"/>`
toBase64 = Buffer.from(sizerSvg).toString("base64")
} else if (props.layout === "fixed") {
wrapperStyle = {
overflow: "hidden",
boxSizing: "border-box",
display: "inline-block",
position: "relative",
width,
height,
}
}
} else if (width === undefined && height === undefined && props.layout === "fill") {
wrapperStyle = {
display: "block",
overflow: "hidden",
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
boxSizing: "border-box",
margin: 0,
}
} else {
throw new Error(
`Image with src "${props.src}" must use "width" and "height" properties or "layout='fill'" property.`,
)
}
return (
<div style={wrapperStyle}>
{sizerStyle ? (
<div style={sizerStyle}>
{sizerSvg ? (
<img
style={{ maxWidth: "100%", display: "block" }}
alt={props.alt}
aria-hidden={true}
role="presentation"
src={`data:image/svg+xml;base64,${toBase64}`}
/>
) : null}
</div>
) : null}
<img {...props} decoding="async" style={imgStyle} />
</div>
)
},
}) |
@josbroers Thank you for this it works exactly as expected. However, you have a tiny error in your Image Component code.
That should be Cheers! |
Glad it works as expected and thanks @sklawren for mentioning the error. I've implemented the change in the code above. |
One other issue. The imgStyle object overrides any className prop passed to the Image component. For example, if you pass a css class with margin or padding, they get overridden by the imgStyle margin and padding. |
team member (@kn0ll) came up with a really elegant solution we use at our company. import NextBaseImage, { ImageProps } from 'next/image'
const StorybookNextImage: React.FC<ImageProps> = props => (
<NextBaseImage
{...props}
loader={({ src }) => {
return src
}}
/>
)
export const NextImage = !isStorybook ? NextBaseImage : (StorybookNextImage as typeof NextBaseImage) |
And where do you get |
we set it at runtime in the environment. something like |
In case you have the same problem with
|
The following will keep the default behaviour of
// preview.tsx
import * as NextImage from 'next/image'
const OriginalNextImage = NextImage.default
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (props) => (
<OriginalNextImage {...props} unoptimized loader={({ src }) => src} />
),
}) |
@JCQuintas This works but I'm still struggling to setup storybook with Next.js 11 and being able to use image imports like I would in Next. Next's webpack config uses When using storybook it defaults to it's |
@jeroenroumen Hi, I would suspect you need to change the file loaders in storybook to use Make sure that the |
@JCQuintas yeah that's one of the first things I tried. Sorry forgot to mention that. Basically it throws an error that webpack can't resolve the My main problem now is to make the storybook Did you ever run in this use case or do you always provide |
By doing this getting the error: @JCQuintas Objects are not valid as a React child (found: object with keys {type, props, key, ref, __k, __, __b, __e, __d, __c, __h, constructor, __v}). If you meant to render a collection of children, use an array instead. |
I am running into similar problems trying to use storybook with For me, I was able to solve this by putting the following code in // other code omitted for brevity
import * as NextImage from 'next/image'
const OriginalNextImage = NextImage.default
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: props => (
<OriginalNextImage {...props} unoptimized blurDataURL={props.src} />
)
}) If you are looking for a more type safe solution (but probably not necessary):
// other code omitted for brevity
import * as NextImage from 'next/image'
const OriginalNextImage = NextImage.default
// eslint-disable-next-line no-import-assign
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (/** @type {import('next/image').ImageProps} */ props) => {
if (typeof props.src === 'string') {
return (
<OriginalNextImage {...props} unoptimized blurDataURL={props.src} />
)
} else {
// don't need blurDataURL here since it is already defined on the StaticImport type
return <OriginalNextImage {...props} unoptimized />
}
}
}) I hope this helps |
@RyanClementsHax How are you handling the image importing in the Storybook webpack configuration? In our project with Next.js 11 when you import a This is due too the |
when you say
I actually don't touch any webpack configuration regarding static imports of images. I only configure webpack to handle css/scss/css module/postcss configuration. It seems that Storybook's default webpack config already handles statically importing images as static paths rather than static objects, even though the later is what nextjs does. Below is my /* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path')
/**
* @typedef {import('next').NextConfig} NextConfig
* @typedef {import('webpack').Configuration} WebpackConfig
*/
module.exports = {
stories: ['../**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'storybook-addon-next-router',
'@storybook/addon-a11y',
'@storybook/addon-storysource',
'storybook-dark-mode'
],
core: {
builder: 'webpack5'
},
/**
* @param {WebpackConfig} baseConfig
* @return {Promise<WebpackConfig>}
*/
async webpackFinal(baseConfig) {
const nextConfig = require('../next.config.js')([], baseConfig)
configureRootAbsoluteImport(baseConfig)
configureCss(baseConfig, nextConfig)
return baseConfig
}
}
/**
* @param {WebpackConfig} baseConfig
* @return {void}
*/
const configureRootAbsoluteImport = baseConfig => {
baseConfig.resolve?.modules?.push(path.resolve(__dirname, '..'))
}
/**
* @param {WebpackConfig} baseConfig
* @param {NextConfig} nextConfig
* @return {void}
*/
const configureCss = (baseConfig, nextConfig) => {
baseConfig.module?.rules?.push({
test: /\.(s*)css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: { auto: true }
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader',
options: {
additionalData: nextConfig.sassOptions?.prependData
}
}
]
})
} Bellow is my // I'd love to convert this to .mjs but typescript doesn't suport .mjs yet https://github.com/microsoft/TypeScript/issues/15416
/* eslint-disable @typescript-eslint/no-var-requires */
// @ts-expect-error: there are no typings for this module
const withPlugins = require('next-compose-plugins')
// @ts-expect-error: there are no typings for this module
const withBundleAnalyzer = require('@next/bundle-analyzer')
/**
* @type {import('next').NextConfig}
**/
const config = {
experimental: { esmExternals: true }
}
module.exports = withPlugins(
[
withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true'
})
],
config
) This is the page that statically imports the image import { Layout } from 'components/landing/Layout'
import Head from 'next/head'
import Image from 'next/image'
import banner from 'public/banner.jpg'
export const Index: React.FC = () => {
return (
<Layout>
<Head>
<title>Ryan Clements</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<section className="h-screen w-100 p-8">
<div className="h-full grid gap-4 content-center md:container md:mx-auto md:grid-cols-2 lg:grid-cols-5">
<div className="grid content-center lg:col-span-2">
<h1 className="text-4xl font-bold mb-4">
Hiya 👋
<br />
I’m Ryan Clements
</h1>
<h2 className="text-2xl text-gray-600">
I 💖 God, my wife and daughter 👨👩👧, and making dope
software 👨💻
</h2>
</div>
<div className="relative h-[500px] hidden md:block shadow-md lg:col-span-3">
<Image
src={banner}
layout="fill"
objectFit="cover"
objectPosition="center"
placeholder="blur"
priority
alt="My wife, me, and our wedding party being silly"
/>
</div>
</div>
</section>
</Layout>
)
}
export default Index It should be noted that I'm using Storybook at version All of this code is from my personal website if you want to go dig around more.
|
@RyanClementsHax Thank you for the great summary of what you're using! Yes, I meant that npm package. It's published there but it's actually the default image loader since Next.js 11. |
Building on the previous ideas here, might be possible to hack around and get a default image W/H
The hackery: statically output size for every image of /public into a json, have it imported in preview.js to provide a default W/H
|
Is this Babel plugin even necessary when deploying in production node since this "unoptimized" attribute is only in storybook? |
Hi everyone, Storybook just released a no config addon that amongst other things make Check it out here https://storybook.js.org/addons/storybook-addon-next#supported-features so I believe this can be closed for now? |
@balazsorban44 Thanks for the shoutout! I'm the creator and maintainer of that addon. The addon mentioned attempts to create a "zero config" experience to get nextjs to "just work" with storybook, Side note: |
Thank you for your hard work! 💚🙏 I'm going to close this issue for now. |
This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
Bug report
Describe the bug
using
next/image
component in storybook throw Error.To Reproduce
yarn create next-app --example with-storybook with-storybook-app
next/image
Expected behavior
show Image without Error as plane image tag do.
Screenshots
System information
The text was updated successfully, but these errors were encountered: