Skip to content

Conversation

@jonkafton
Copy link
Contributor

@jonkafton jonkafton commented Oct 28, 2024

What are the relevant tickets?

Closes https://github.com/mitodl/hq/issues/5857

Upgrades to Next.js v15

Description (What does it do?)

  • Replaces <img> tags with <Image> components where it makes sense to.

    • There are places where it doesn't make sense to, such as where we have containers that would prevent layout shift and where we are already able to provide the image as a static import (Next.js set the src string to its image optimization endpoint), e.g. the PersonalizeSection image.

    • Static imports for images in repo (import src is a path to the Next.js image optimization endpoint). This is used for both <img> tags and CSS background-image.

    • Provides a utility that produces a CSS image-set() declaration for a statically imported image. These are links for the image from the optimization endpoint at resolutions for 1x and 2x screen pixel densities, see https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set. Applied to the homepage personalize section background. These can also be used on <img> tags (srcset attribute). We should consider applying elsewhere, particularly for large images, though also should use with caution as it may not be desirable to render high resolution images for example for backgrounds that contain little detail.

    • The resource drawer images are now optimized by Next.js with noticeable improvement to quality:

      • Before:
        image

      • After:
        image

  • Resource drawer YouTube videos are displayed using a simple iframe (YouTube's embed html is similar). Non-YouTube videos fall back to Embedly, though all of our videos are YouTube at present. This also removes some padding added by Embedly and sizes the videos to match our aspect ratios and applies the border radius.

    • Before:
      image

    • After:
      image

    • Updates ol-components/Logo to support unit logos and distinguish between units and platforms so it can be used on the unit channel pages. We can't import from the ./public/images folder to use image optimization as we have the rule that ol-components are leaf imports, though there is no value for SVGs.

    • Clean up repetitions of the Embedly key / move into EmbedlyCard.

How can this be tested?

Check that images and logos display correctly at mobile and desktop. Particular attention to:

  • Resource drawer images and video.
  • Homepage personalize section background.
  • Nav drawer icons.
  • About page main image.
  • Topics listing page banner background.
  • Channel pages banner background.
  • Units listing page card logos.

@jonkafton jonkafton changed the title Image optimizations Image and video optimizations Oct 29, 2024
Comment on lines 139 to 150
<DesktopOnly>
<PlatformLogoInverted
unitCode={name as OfferedByEnum}
height={50}
/>
</DesktopOnly>
<MobileOnly>
<PlatformLogoInverted
unitCode={name as OfferedByEnum}
height={40}
/>
</MobileOnly>
Copy link
Contributor Author

@jonkafton jonkafton Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we produce for each side of the width breakpoint and use the css media query to hide one. This is wordy, though needed for varying the height with only CSS.

Where the images are in the repo we can generally pull them in as static imports, so don't need to specify the dimensions, though in this case the ol-components PlatformLogo is not allowed to import from outside its workspace.

Further, the width and height on <Image> components do not size the image itself - it is just to block space to prevent layout shift, which can be done with CSS. Here though the logos are of different sizes so we need to hardcode the dimensions (here providing fixed height with width calculated in PlatformLogo).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where the images are in the repo we can generally pull them in as static imports,

That's why Logo.tsx specifies the dimensions, but we shouldn't have to here, right? I think you can achieve the same effect just with

const PlatformLogoInverted = styled(PlatformLogo)(({ theme }) => ({
  filter: "saturate(0%) invert(100%)",
  height: 50,
  maxWidth: "100%",
  [theme.breakpoints.down("md")]: {
    height: 40,
    width: "auto",
  },
}))

Probably a separate Issue: Doesn't seem great to be specifying aspect ratios / widths in Logo.tsx. Maybe we should just move the enum'd labels to ol-components. I guess we probably need to static string paths for email templates or something. (Taking a quick look.... maybe we don't use them anyway in the backend.)

current="Departments"
/>
}
backgroundUrl={backgroundSteps.src}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <Banner> component has a default image path (background_steps.jpg), but as this is just a path string, the image is loaded directly as opposed to from the image optimization endpoint. Static imports get the image optimzation endpoint, but as a rule the <Banner> component cannot import from outside ol-components.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we publish ol-components as a separate package, and I guess this could wait until then, we should do one of the following:

  1. Not include Banner in ol-components, move it to main/src/components
  2. Remove the string-based default background image from Banner Doesn't make sense unless I guess it's an absolute / fully qualified URL to some server somewhere.
  3. Move the default background image to ol-components.

I would probably lean toward (1). Our Banner component has lots of props enabling it to be used for (A) simple cases like /departments, /topics, etc, and (B) complex cases like the unit channel pages. I'd be happy to include (A) in the published design system, but (B) seems too Learn-specific. I think the two cases are different components in Figma, too.

@ChristopherChudzicki ChristopherChudzicki self-assigned this Oct 30, 2024
Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is working great. I left a few comments, some of which we should probably make sense to do now (remove comments, maybe use backgroundSrcSetCSS more, add fallbak to backgroundSrcSetCSS?).

<SubHeaderImageContainer>
<SubHeaderImage
src={domeImage}
alt="A view of the entablature of MIT's Great Dome"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

today i learn...

Comment on lines 139 to 150
<DesktopOnly>
<PlatformLogoInverted
unitCode={name as OfferedByEnum}
height={50}
/>
</DesktopOnly>
<MobileOnly>
<PlatformLogoInverted
unitCode={name as OfferedByEnum}
height={40}
/>
</MobileOnly>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where the images are in the repo we can generally pull them in as static imports,

That's why Logo.tsx specifies the dimensions, but we shouldn't have to here, right? I think you can achieve the same effect just with

const PlatformLogoInverted = styled(PlatformLogo)(({ theme }) => ({
  filter: "saturate(0%) invert(100%)",
  height: 50,
  maxWidth: "100%",
  [theme.breakpoints.down("md")]: {
    height: 40,
    width: "auto",
  },
}))

Probably a separate Issue: Doesn't seem great to be specifying aspect ratios / widths in Logo.tsx. Maybe we should just move the enum'd labels to ol-components. I guess we probably need to static string paths for email templates or something. (Taking a quick look.... maybe we don't use them anyway in the backend.)

current="Departments"
/>
}
backgroundUrl={backgroundSteps.src}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we publish ol-components as a separate package, and I guess this could wait until then, we should do one of the following:

  1. Not include Banner in ol-components, move it to main/src/components
  2. Remove the string-based default background image from Banner Doesn't make sense unless I guess it's an absolute / fully qualified URL to some server somewhere.
  3. Move the default background image to ol-components.

I would probably lean toward (1). Our Banner component has lots of props enabling it to be used for (A) simple cases like /departments, /topics, etc, and (B) complex cases like the unit channel pages. I'd be happy to include (A) in the published design system, but (B) seems too Learn-specific. I think the two cases are different components in Figma, too.

}
title="Browse by Topic"
header="Select a topic below to explore relevant learning resources across all Academic and Professional units."
backgroundUrl={backgroundSteps.src}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use backgroundSrcSetCSS here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one the getImageProps() only produces the single 1x image, so using srcset has no impact.

Also, the backgroundUrl prop is used in the <Banner> css like linear-gradient(rgba(0 0 0 / ${backgroundDim}%), rgba(0 0 0 / ${backgroundDim}%)), url('${backgroundUrl}'), so we needed some extra gymnastics:

    const backgroundUrlFn = backgroundUrl.startsWith("image-set(")
      ? backgroundUrl
      : `url('${backgroundUrl}')`

Works fine though.

"iso-639-1": "^3.1.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"next": "^14.2.15",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really matter, but I'd make this a peer-dep along with react.

})
.join(", ")

return imageSet ? `image-set(${imageSet})` : ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand when props.srcSet, and hence imageSet would be undefined. But I guess it can be?

Should this fall back to url(`${props.src}`)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props.srcSet is typed string | undefined, though I'm not sure how it could be. Either way the backround image would be broken, so have removed the ternary.


const DEFAULT_WIDTH = 200

export const PlatformLogo: React.FC<{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have separate PlatformLogo and UnitLogo components?

Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@ChristopherChudzicki ChristopherChudzicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jonkafton jonkafton merged commit 5951883 into main Nov 1, 2024
11 checks passed
@jonkafton jonkafton deleted the jk/5857-image-optimizations branch November 1, 2024 19:20
This was referenced Nov 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants