sveltejs / kit

web development, streamlined
https://svelte.dev/docs/kit
MIT License
18.76k stars 1.95k forks source link

Adapter Vercel Image Optimizations #8561

Open hartwm opened 1 year ago

hartwm commented 1 year ago

Describe the problem

Would be really awesome if adapter-vercel package accepted images option for using Vercel's native image optimization. It only requires simple addition of images object to build config file .vercel/output/config.json. I would submit a pull request but I am sure the options, naming conventions, etc would not be as the team would desire. Given it is such a straightforward task it probably wouldn't prove helpful.

Describe the proposed solution

Here are the options

type ImageFormat = 'image/avif' | 'image/webp';

type RemotePattern = {
  protocol?: 'http' | 'https';
  hostname: string;
  port?: string;
  pathname?: string;
};

type ImagesConfig = {
  sizes: number[];
  domains: string[];
  remotePatterns?: RemotePattern[];
  minimumCacheTTL?: number; // seconds
  formats?: ImageFormat[];
  dangerouslyAllowSVG?: boolean;
  contentSecurityPolicy?: string;
};

https://vercel.com/docs/concepts/image-optimization https://vercel.com/docs/build-output-api/v3#build-output-configuration/supported-properties/images

Alternatives considered

No response

Importance

nice to have

Additional Information

No response

dummdidumm commented 1 year ago

I'm not sure just adding the options is enough, there's also work involved to create a specific image component I guess? Related to #241

hartwm commented 1 year ago

I'm not sure just adding the options is enough, there's also work involved to create a specific image component I guess? Related to #241

you can leave that to the community, because thats more subjective and that would be separate from an adapter package anyway, but without the ability to put these options in the build config then you can't use it, so this shouldn't be the breakpoint of using features

hartwm commented 1 year ago

Also all the solutions I have seen are for local images, not remote images.

lettucebowler commented 1 year ago

I'm not sure just adding the options is enough, there's also work involved to create a specific image component I guess? Related to #241

Next and Nuxt have supported image components, but from my understanding once enabled you could also just update your image srcs manually from eg /public/images/logo.png to /_vercel/image?url=/public/image/logo.png?w=320&q=80. A fancy image component could come later if the community wants it, but just enabling this would be a great enhancement IMO

hartwm commented 1 year ago

Next and Nuxt have supported image components, but from my understanding once enabled you could also just update your image srcs manually from eg /public/images/logo.png to /_vercel/image?url=/public/image/logo.png?w=320&q=80. A fancy image component could come later if the community wants it, but just enabling this would be a great enhancement IMO

Exactly

izznat commented 1 year ago

@lettucebowler has made a PR for this #8667.

benmccann commented 1 year ago

Example usage as of today shared on Discord by OP:

// Image.svelte

<script lang="ts">  
  import { dev } from '$app/environment'; 

  let className = ""
  export { className as class };
  export let image:WpImage
  export let showTitle = false
  export let lazy = true
  export let height  = 1280
  export let quality = 70
  export let width = 720
  export let unoptimized = false 

  const sizeArr = [640, 768, 1024, 1280, 1536, 2048]  

  const vercelImg = (sourceUrl:string,size:number) => 
  (`/_vercel/image?url=${encodeURIComponent(sourceUrl)}&w=${size}&q=${quality} ${size}w`)

  $:({ srcSet, sourceUrl, title, altText} = image) 
  $: vercelSrcSet = sizeArr.map(size=>vercelImg(image?.sourceUrl,size)).join(',') 

  // dyanmic srcset only for optimized (w/vercel) 
  $: imageSrcSet = (unoptimized || dev) ? srcSet : vercelSrcSet

  let element 
</script>

<img   
  {width}
  {height}
  title={showTitle ? title : undefined}
  srcset={imageSrcSet}
  src={sourceUrl}
  alt={altText ?? title} 
  class={className ?? undefined}
  loading={lazy ? 'lazy' : 'eager'}
  {...$$restProps} 
  bind:this={element}
/>
// scripts/vercel-images.js
import fs from 'node:fs';
import dotenv from 'dotenv'
dotenv.config()
// sizes should be shared variable with Image component
import {sizes} from './theme.cjs'

const file = '.vercel/output/config.json';

const config = {
  ...JSON.parse(fs.readFileSync(file, 'utf-8')),
        images: {
                "sizes": sizes,
                "domains": [],
                "minimumCacheTTL": 60,
                "formats": ["image/avif", "image/webp"],
                "remotePatterns": [ 
                        { 
                            "hostname": "yourcdn\\.cloudfront\\.net$"
                        }, 
                ]
            }
};

fs.writeFileSync(file, JSON.stringify(config, null, 2));
// package.json

"build": "vite build && node scripts/vercel-images",
aakash14goplani commented 1 year ago

@hartwm @benmccann do you have working example hoisted anywhere (e.g. GitHub, Code sandbox etc. ). I wanted to see the configuration details. Thanks.

hartwm commented 1 year ago

@hartwm @benmccann do you have working example hoisted anywhere (e.g. GitHub, Code sandbox etc. ). I wanted to see the configuration details. Thanks.

https://github.com/hartwm/vercel-images-sveltekit https://vercel-images-sveltekit.vercel.app/

benmccann commented 1 year ago

There are a couple PRs out for this:

benmccann commented 1 year ago

Hi @hartwm, thanks so much for providing these examples! I do have one question I wonder if anyone knows the answer to.

Vite lets you import images like this:

<script>
    import logo from '$images/example.png';
</script>

That means that a hash gets put in their name so that they can be cached forever. Is it possible to use such an image with the Vercel image solution?

When I go to https://vercel-images-sveltekit.vercel.app/ I notice that the local image can't be cached as strongly as the Vite processed images we have today. If you hit the page a second time, it returns a 304 response as opposed to avoiding the request entirely. I wonder if it's possible to use an imported image so that Vite processes the request before Vercel takes over possibly allowing us to skip the request entirely still. If it's possible, it'd be a great example to add to https://github.com/hartwm/vercel-images-sveltekit

leoj3n commented 9 months ago

@benmccann You can set Cache-Control headers with Vercel...

https://github.com/leoj3n/svelte-vercel-optimized-images?tab=readme-ov-file#avoiding-304-network-requests

It looks like you merged https://github.com/sveltejs/kit/pull/8667#pullrequestreview-1730990777 which relates to the OP... Considering the ability to set Cache-Control with Vercel, your remaining question may be answered, and so you might now be able to close this open issue.

leoj3n commented 9 months ago

@benmccann Regarding "Vite processes the request before Vercel takes over"...

It kind of defeats the purpose of using Vercel to send the correct image type (avif/webp) based on headers sent from the browser, as well as the (pre-defined) dynamic image sizes. If you circumvent Vercel you would have to generate all those permutations ahead of time yourself which could be prohibitive for user uploaded images, as mentioned.

I do believe Next.js implements their own code that emulates what the Vercel image optimization endpoint does, so it may be possible to add such functionality to SvelteKit itself, but you probably will have a very similar approach.