Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: add missing <preload> for next/image in App Router
  • Loading branch information
styfle committed Jul 8, 2023
commit bb27851efad2ea002f055cbbd99347401a36ad5b
78 changes: 55 additions & 23 deletions packages/next/src/client/image-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, {
forwardRef,
version,
} from 'react'
import { preload } from 'react-dom'
import Head from '../shared/lib/head'
import { getImgProps } from '../shared/lib/get-img-props'
import type {
Expand All @@ -26,6 +27,7 @@ import type {
import { imageConfigDefault } from '../shared/lib/image-config'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import { warnOnce } from '../shared/lib/utils/warn-once'
import { RouterContext } from '../shared/lib/router-context'

// @ts-ignore - This is replaced by webpack alias
import defaultLoader from 'next/dist/shared/lib/image-loader'
Expand Down Expand Up @@ -303,8 +305,57 @@ const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
}
)

function ImagePreload({
isAppRouter,
imgAttributes,
}: {
isAppRouter: boolean
imgAttributes: ImgProps
}) {
const opts = {
as: 'image',
imageSrcSet: imgAttributes.srcSet,
imageSizes: imgAttributes.sizes,
crossOrigin: imgAttributes.crossOrigin,
referrerPolicy: imgAttributes.referrerPolicy,
...getDynamicProps(imgAttributes.fetchPriority),
}

if (isAppRouter) {
// See https://github.com/facebook/react/pull/26940
// @ts-expect-error TODO: upgrade to `@types/[email protected]`
preload(imgAttributes.src, opts)
return null
}

return (
<Head>
<link
key={
'__nimg-' +
imgAttributes.src +
imgAttributes.srcSet +
imgAttributes.sizes
}
rel="preload"
// Note how we omit the `href` attribute, as it would only be relevant
// for browsers that do not support `imagesrcset`, and in those cases
// it would cause the incorrect image to be preloaded.
//
// https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
href={imgAttributes.srcSet ? undefined : imgAttributes.src}
{...opts}
/>
</Head>
)
}

export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
(props, forwardedRef) => {
const pagesRouter = useContext(RouterContext)
// We're in the app directory if there is no pages router.
const isAppRouter = !pagesRouter

const configContext = useContext(ImageConfigContext)
const config = useMemo(() => {
const c = configEnv || configContext || imageConfigDefault
Expand Down Expand Up @@ -352,29 +403,10 @@ export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
/>
}
{imgMeta.priority ? (
// Note how we omit the `href` attribute, as it would only be relevant
// for browsers that do not support `imagesrcset`, and in those cases
// it would likely cause the incorrect image to be preloaded.
//
// https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
<Head>
<link
key={
'__nimg-' +
imgAttributes.src +
imgAttributes.srcSet +
imgAttributes.sizes
}
rel="preload"
as="image"
href={imgAttributes.srcSet ? undefined : imgAttributes.src}
imageSrcSet={imgAttributes.srcSet}
imageSizes={imgAttributes.sizes}
crossOrigin={imgAttributes.crossOrigin}
referrerPolicy={imgAttributes.referrerPolicy}
{...getDynamicProps(imgAttributes.fetchPriority)}
/>
</Head>
<ImagePreload
isAppRouter={isAppRouter}
imgAttributes={imgAttributes}
/>
) : null}
</>
)
Expand Down
46 changes: 29 additions & 17 deletions test/integration/next-image-new/app-dir/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ function runTests(mode) {
}
})

// TODO: need to add <link preload> to app dir
it.skip('should preload priority images', async () => {
it.only('should preload priority images', async () => {
let browser
try {
browser = await webdriver(appPort, '/priority')
Expand All @@ -125,7 +124,15 @@ function runTests(mode) {
const fetchpriority = await link.getAttribute('fetchpriority')
const imagesrcset = await link.getAttribute('imagesrcset')
const imagesizes = await link.getAttribute('imagesizes')
entries.push({ fetchpriority, imagesrcset, imagesizes })
const crossorigin = await link.getAttribute('crossorigin')
const referrerPolicy = await link.getAttribute('referrerPolicy')
entries.push({
fetchpriority,
imagesrcset,
imagesizes,
crossorigin,
referrerPolicy,
})
}

expect(
Expand All @@ -139,6 +146,8 @@ function runTests(mode) {
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.jpg&w=640&q=75 1x, /_next/image?url=%2Ftest.jpg&w=828&q=75 2x',
crossorigin: 'anonymous',
referrerPolicy: '',
})

expect(
Expand All @@ -152,6 +161,23 @@ function runTests(mode) {
imagesizes: '100vw',
imagesrcset:
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w',
crossorigin: '',
referrerPolicy: '',
})

expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x'
)
).toEqual({
fetchpriority: 'high',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x',
crossorigin: '',
referrerPolicy: 'no-referrer',
})

// When priority={true}, we should _not_ set loading="lazy"
Expand Down Expand Up @@ -197,20 +223,6 @@ function runTests(mode) {
/was detected as the Largest Contentful Paint/gm
)
expect(warnings).not.toMatch(/React does not recognize the (.+) prop/gm)

// should preload with crossorigin
expect(
await browser.elementsByCss(
'link[rel=preload][as=image][crossorigin=anonymous][imagesrcset*="test.jpg"]'
)
).toHaveLength(1)

// should preload with referrerpolicy
expect(
await browser.elementsByCss(
'link[rel=preload][as=image][referrerpolicy="no-referrer"][imagesrcset*="test.png"]'
)
).toHaveLength(1)
} finally {
if (browser) {
await browser.close()
Expand Down
48 changes: 30 additions & 18 deletions test/integration/next-image-new/default/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function runTests(mode) {
}
})

it('should preload priority images', async () => {
it.only('should preload priority images', async () => {
let browser
try {
browser = await webdriver(appPort, '/priority')
Expand All @@ -125,8 +125,17 @@ function runTests(mode) {
const fetchpriority = await link.getAttribute('fetchpriority')
const imagesrcset = await link.getAttribute('imagesrcset')
const imagesizes = await link.getAttribute('imagesizes')
entries.push({ fetchpriority, imagesrcset, imagesizes })
const crossorigin = await link.getAttribute('crossorigin')
const referrerPolicy = await link.getAttribute('referrerPolicy')
entries.push({
fetchpriority,
imagesrcset,
imagesizes,
crossorigin,
referrerPolicy,
})
}

expect(
entries.find(
(item) =>
Expand All @@ -138,6 +147,8 @@ function runTests(mode) {
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.jpg&w=640&q=75 1x, /_next/image?url=%2Ftest.jpg&w=828&q=75 2x',
crossorigin: 'anonymous',
referrerPolicy: '',
})

expect(
Expand All @@ -151,6 +162,23 @@ function runTests(mode) {
imagesizes: '100vw',
imagesrcset:
'/_next/image?url=%2Fwide.png&w=640&q=75 640w, /_next/image?url=%2Fwide.png&w=750&q=75 750w, /_next/image?url=%2Fwide.png&w=828&q=75 828w, /_next/image?url=%2Fwide.png&w=1080&q=75 1080w, /_next/image?url=%2Fwide.png&w=1200&q=75 1200w, /_next/image?url=%2Fwide.png&w=1920&q=75 1920w, /_next/image?url=%2Fwide.png&w=2048&q=75 2048w, /_next/image?url=%2Fwide.png&w=3840&q=75 3840w',
crossorigin: '',
referrerPolicy: '',
})

expect(
entries.find(
(item) =>
item.imagesrcset ===
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x'
)
).toEqual({
fetchpriority: 'high',
imagesizes: '',
imagesrcset:
'/_next/image?url=%2Ftest.png&w=640&q=75 1x, /_next/image?url=%2Ftest.png&w=828&q=75 2x',
crossorigin: '',
referrerPolicy: 'no-referrer',
})

// When priority={true}, we should _not_ set loading="lazy"
Expand Down Expand Up @@ -196,22 +224,6 @@ function runTests(mode) {
/was detected as the Largest Contentful Paint/gm
)
expect(warnings).not.toMatch(/React does not recognize the (.+) prop/gm)

// should preload with crossorigin
expect(
(
await browser.elementsByCss(
'link[rel=preload][as=image][crossorigin=anonymous][imagesrcset*="test.jpg"]'
)
).length
).toBeGreaterThanOrEqual(1)

// should preload with referrerpolicy
expect(
await browser.elementsByCss(
'link[rel=preload][as=image][referrerpolicy="no-referrer"][imagesrcset*="test.png"]'
)
).toHaveLength(1)
} finally {
if (browser) {
await browser.close()
Expand Down