Replies: 21 comments 12 replies
-
I tried to use an image loader based on query params in imports with vite.js and it was a pain because this kind of imports is impossible to type in typescript. So not sure how I feel about them. But it is definitely a problem worth solving 👍 |
Beta Was this translation helpful? Give feedback.
-
@sergiodxa yeah that was the plan when we had the image importer. Always thinking on the same wavelength! We're not sure that the compiler is the right place to process something as expensive as images so we decided to remove it and put it on the backburner until v1 is humming along. We have some rough ideas about how to help out with images w/o making the compiler do it. I hope we can get to sooner than later! |
Beta Was this translation helpful? Give feedback.
-
I'm not sure how Next.js did it but when you import an image file the object you import has a specific type interface StaticImageData {
src: string
height: number
width: number
blurDataURL?: string
} I saw they have this: declare module '*.png' {
const content: StaticImageData
export default content
} In a |
Beta Was this translation helpful? Give feedback.
-
Yes |
Beta Was this translation helpful? Give feedback.
-
Next.js also has a way to handle dynamic images with some custom loader without affecting the build time Someone created this gist for a possible way to build it. it's just a POC https://gist.github.com/olikami/236e3c57ca73d145984ec6c127416340 Or maybe as initial implementation you could provide an integration with popular services such as cloudinary or imgix |
Beta Was this translation helpful? Give feedback.
-
Also extremely interested by this feature. Gatsby and Next JS provide out of the box. Is it a way to do it in React without meta framework like Gatsby and Next JS? |
Beta Was this translation helpful? Give feedback.
-
Really need image optimizations! |
Beta Was this translation helpful? Give feedback.
-
Jacob from the team created a POC of the component using a resource route to do the optimization https://gist.github.com/jacob-ebey/3a37a86307de9ef22f47aae2e593b56f |
Beta Was this translation helpful? Give feedback.
-
@sergiodxa I just released the package https://www.npmjs.com/package/remix-image Still in trial, and many more features need to be added to meet the functionality of |
Beta Was this translation helpful? Give feedback.
-
Here's my 50 cents. I optimize everything static with Squoosh tailored to the size I want to display it. My main issue was dealing with images that come from the CMS. Clients should not need to worry about performance. I've taken @jacob-ebey 's example and stripped it down to only serve external images with a predefined width set. For caching, I rely on the CDN instead of caching in the file system. Gonna use this until remix lands its official solution. // entry.server.tsx
import * as serverSharp from "sharp";
export const sharp = serverSharp.default;
// routes/images.tsx
import React, { ComponentPropsWithoutRef } from "react";
import { LoaderFunction } from "remix";
import { sharp } from "~/entry.server";
const BadImageResponse = () => {
const buffer = Buffer.from(
"R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
"base64"
);
return new Response(buffer, {
status: 500,
headers: {
"Cache-Control": "max-age=0",
"Content-Type": "image/gif;base64",
"Content-Length": buffer.length.toFixed(0),
},
});
};
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
const src = url.searchParams.get("src");
const width = url.searchParams.get("w");
const quality = url.searchParams.get("q");
if (!src || !width || !quality) {
return BadImageResponse();
}
try {
const image = await fetch(src);
if (!image.ok || !image.body) {
throw new Error(
`fetching image failed. src: ${src}, status: ${image.status}`
);
}
const imageBuffer = Buffer.from(await image.arrayBuffer());
let resizedImage = await sharp(imageBuffer)
.resize(parseInt(width))
.webp({
quality: parseInt(quality),
})
.toBuffer();
return new Response(resizedImage, {
status: 200,
headers: {
"Cache-Control": `max-age=${60 * 60 * 24 * 365}, public`,
"Content-Type": "image/webp",
"Content-Length": resizedImage.length.toFixed(0),
},
});
} catch (error) {
console.error(error);
return BadImageResponse();
}
};
const widths = [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
const quality = 75;
export const Img: React.FC<ComponentPropsWithoutRef<"img">> = (props) => {
if (!props.src) {
throw new Error("no src provided to Img component");
}
if (props.srcSet) {
console.warn("srcSet will be overwritten by srcSetGenerator");
}
const srcSetParts = widths.map(
(width) => `/images?src=${props.src}&w=${width}&q=${quality} ${width}w`
);
return (
<img {...props} srcSet={srcSetParts.join(", ")} src={srcSetParts[0]} />
);
}; I think this is also a very basic & understandable approach, which others can use and extend to fit their needs, e.g. providing values for width, height and sizes via props. For me, this fire and forget |
Beta Was this translation helpful? Give feedback.
-
@sergiodxa Next’s |
Beta Was this translation helpful? Give feedback.
-
Because there’s no Head component in Remix that will not be possible I imagine, if you want to preload the image you can add it to the LinksFunction of the route. |
Beta Was this translation helpful? Give feedback.
-
How does the |
Beta Was this translation helpful? Give feedback.
-
I think it should be done at component level. Otherwise it will be too hard to implement it. |
Beta Was this translation helpful? Give feedback.
-
It renders the <link rel="preload" as="image" href="/something.png" />
<img src="/something.png" /> Will not give you a lot of benefits because it's going to trigger the preload just when it's about to find the img tag so don't optimize anything. The point of preload would be to put it in the |
Beta Was this translation helpful? Give feedback.
-
Makes sense. Surely we can figure out a way to allow components down tree to jam themselves up into |
Beta Was this translation helpful? Give feedback.
-
@ryanflorence any news on this? Thanks for your great work! |
Beta Was this translation helpful? Give feedback.
-
Honestly, never liked the Next.js Image component. Besides that, developers always try to abstract everything by moving everything in a custom component leaving no wiggle room to edit or change the In my whole career as a Front-end Developer, I have never found a distinct and 100% correct component that generates this out of the box. What I always end up doing is just using a service (like imgIX) and creating helper functions to generate srcsets based on widths developers provide themselves based on the An example of some helper function I use in a project: export function imgixSrc(url, width, aspectRatio = null, fit = 'crop') {
if (!url) {
throw new Error('No `url` argument available.');
}
if (!width) {
console.warn(
"You did not include a `width` argument, the image will be source size, that's probably not what you're intending to do.",
);
return url;
}
if (!aspectRatio) {
return `${url}?w=${width}`;
}
if (!(typeof aspectRatio === 'string')) {
console.warn('aspectRatio should be of type string.');
return `${url}?w=${width}`;
}
return `${url}?w=${width}&ar=${aspectRatio}&fit=${fit}`;
}
export function imgixSrcSet(url, widths, aspectRatio = null, fit = 'crop') {
return widths
.sort((a, b) => a - b)
.map((width) => {
return `${imgixSrc(url, width, aspectRatio, fit)} ${width}w`;
});
} With these functions, you end up building your import { imgixSrc, imgixSrcSet } from '@/lib/helpers';
const imageSizes = `
(min-width: 394px) 346px
calc(100vw - 48px)
`;
const imageWidths = [
272, // (320px - 48px)
327, // (375px - 48px)
346, // 346px is the max-width
544, // (320px - 48px) * 2
654, // (375px - 48px) * 2
692, // 692px is the max-width in dpr 2
];
const SomeComponent = ({ image }) => (
<img
alt={image.alt ?? ''}
loading="lazy"
sizes={imageSizes}
src={imgixSrc(image.url, 346, '5:4')}
srcSet={imgixSrcSet(image.url, imageWidths, '5:4')}
/>
); Please note that these functions are in JavaScript but do not hesitate to copy-paste these and rewrite them in TypeScript ✌️ |
Beta Was this translation helpful? Give feedback.
-
Also interested where Remix Image optimization is at. |
Beta Was this translation helpful? Give feedback.
-
@ryanflorence any news on this? |
Beta Was this translation helpful? Give feedback.
-
Hi, also mention that I write a gist for image component with capability of caching , stream base , using sharp |
Beta Was this translation helpful? Give feedback.
-
Next has this actually useful Image component which wraps the
img
tag to optimize how you use it and enforce good practices like providing a width and height to avoid layout shifts.Remix used to have this
img:
prefix on the imports to get an object instead of a string and that object contained data like the width, height, srcset, placeholder, etc. based on query params. This was removed on the migration to esbuild.If it's hard to add that way to import adding the component could be a good fallback to have a way to optimize image load.
Component Interface
The component should be used as a drop-in replacement of the
img
tag, like Form withform
.And just with that it should be enough to start getting the benefit. Other options could be:
The loader should also be configured in the remix.config file so you don't need to add it individually. Remix could come with some built-in image loaders for services like Cloudinary, Cloudflare Image Resizing (when creating a new project with Cloudflare adapter this could come as the default) and maybe a custom one with some hidden endpoint created by Remix.
Special import
If adding back the support to
img:
imports, or usingassert { type: "image" }
in a future, is possible the object returned by the import could come with the same interface the Image component expects, that way you can use it like this:In this case the usage will be simplified a lot, you import and pass the imported to the component and it should magically work.
Beta Was this translation helpful? Give feedback.
All reactions