feat(templates): website optimisations for image sizes and loading (#9447)

Adds defaultPopulate on pages and posts
Further optimises images and fetching staticParams
This commit is contained in:
Paul
2024-11-22 12:21:56 -06:00
committed by GitHub
parent b9d02aa3a8
commit be8cd7f4d9
15 changed files with 84 additions and 60 deletions

View File

@@ -6,6 +6,7 @@ import type { Footer } from '@/payload-types'
import { ThemeSelector } from '@/providers/Theme/ThemeSelector'
import { CMSLink } from '@/components/Link'
import { Logo } from '@/components/Logo/Logo'
export async function Footer() {
const footer: Footer = await getCachedGlobal('footer', 1)()
@@ -16,15 +17,7 @@ export async function Footer() {
<footer className="border-t border-border bg-black dark:bg-card text-white">
<div className="container py-8 gap-8 flex flex-col md:flex-row md:justify-between">
<Link className="flex items-center" href="/">
<picture>
<img
alt="Payload Logo"
width={193}
height={43}
className="max-w-[6rem] invert-0 w-full"
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
/>
</picture>
<Logo />
</Link>
<div className="flex flex-col-reverse items-start md:flex-row gap-4 md:items-center">

View File

@@ -33,7 +33,7 @@ export const HeaderClient: React.FC<HeaderClientProps> = ({ header }) => {
<header className="container relative z-20 " {...(theme ? { 'data-theme': theme } : {})}>
<div className="py-8 border-b border-border flex justify-between">
<Link href="/">
<Logo />
<Logo loading="eager" priority="high" />
</Link>
<HeaderNav header={header} />
</div>

View File

@@ -21,6 +21,9 @@ export async function generateStaticParams() {
draft: false,
limit: 1000,
overrideAccess: false,
select: {
slug: true,
},
})
const params = pages.docs

View File

@@ -21,6 +21,9 @@ export async function generateStaticParams() {
draft: false,
limit: 1000,
overrideAccess: false,
select: {
slug: true,
},
})
const params = posts.docs.map(({ slug }) => {

View File

@@ -30,6 +30,12 @@ export const Pages: CollectionConfig = {
read: authenticatedOrPublished,
update: authenticated,
},
// This config controls what's populated by default when a page is referenced
// https://payloadcms.com/docs/queries/select#defaultpopulate-collection-config-property
defaultPopulate: {
title: true,
slug: true,
},
admin: {
defaultColumns: ['title', 'slug', 'updatedAt'],
livePreview: {

View File

@@ -36,6 +36,17 @@ export const Posts: CollectionConfig = {
read: authenticatedOrPublished,
update: authenticated,
},
// This config controls what's populated by default when a post is referenced
// https://payloadcms.com/docs/queries/select#defaultpopulate-collection-config-property
defaultPopulate: {
title: true,
slug: true,
categories: true,
meta: {
image: true,
description: true,
},
},
admin: {
defaultColumns: ['title', 'slug', 'updatedAt'],
livePreview: {

View File

@@ -37,7 +37,7 @@ export const Card: React.FC<{
>
<div className="relative w-full ">
{!metaImage && <div className="">No image</div>}
{metaImage && typeof metaImage !== 'string' && <Media resource={metaImage} size="360px" />}
{metaImage && typeof metaImage !== 'string' && <Media resource={metaImage} size="33vw" />}
</div>
<div className="p-4">
{showCategories && hasCategories && (

View File

@@ -1,12 +1,26 @@
import React from 'react'
export const Logo = () => {
interface Props {
className?: string
loading?: 'lazy' | 'eager'
priority?: 'auto' | 'high' | 'low'
}
export const Logo = (props: Props) => {
const { loading: loadingFromProps, priority: priorityFromProps } = props
const loading = loadingFromProps || 'lazy'
const priority = priorityFromProps || 'low'
return (
/* eslint-disable @next/next/no-img-element */
<img
alt="Payload Logo"
width={193}
height={43}
loading={loading}
fetchPriority={priority}
decoding="async"
className="max-w-[9.375rem] invert dark:invert-0 w-full"
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
/>

View File

@@ -8,30 +8,27 @@ import React from 'react'
import type { Props as MediaProps } from '../types'
import cssVariables from '@/cssVariables'
import { cssVariables } from '@/cssVariables'
import { getClientSideURL } from '@/utilities/getURL'
const { breakpoints } = cssVariables
// A base64 encoded image to use as a placeholder while the image is loading
const placeholderBlur =
'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'
'data:image/png;base64,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'
export const ImageMedia: React.FC<MediaProps> = (props) => {
const {
alt: altFromProps,
fill,
imgClassName,
onClick,
onLoad: onLoadFromProps,
priority,
resource,
size: sizeFromProps,
src: srcFromProps,
loading: loadingFromProps,
} = props
const [isLoading, setIsLoading] = React.useState(true)
let width: number | undefined
let height: number | undefined
let alt = altFromProps
@@ -53,33 +50,31 @@ export const ImageMedia: React.FC<MediaProps> = (props) => {
src = `${getClientSideURL()}${url}`
}
const loading = loadingFromProps || 'lazy'
// NOTE: this is used by the browser to determine which image to download at different screen sizes
const sizes = sizeFromProps
? sizeFromProps
: Object.entries(breakpoints)
.map(([, value]) => `(max-width: ${value}px) ${value}px`)
.map(([, value]) => `(max-width: ${value}px) ${value * 2}w`)
.join(', ')
return (
<NextImage
alt={alt || ''}
className={cn(imgClassName)}
fill={fill}
height={!fill ? height : undefined}
onClick={onClick}
onLoad={() => {
setIsLoading(false)
if (typeof onLoadFromProps === 'function') {
onLoadFromProps()
}
}}
placeholder="blur"
blurDataURL={placeholderBlur}
priority={priority}
quality={90}
sizes={sizes}
src={src}
width={!fill ? width : undefined}
/>
<picture>
<NextImage
alt={alt || ''}
className={cn(imgClassName)}
fill={fill}
height={!fill ? height : undefined}
placeholder="blur"
blurDataURL={placeholderBlur}
priority={priority}
quality={100}
loading={loading}
sizes={sizes}
src={src}
width={!fill ? width : undefined}
/>
</picture>
)
}

View File

@@ -11,6 +11,7 @@ export interface Props {
imgClassName?: string
onClick?: () => void
onLoad?: () => void
loading?: 'lazy' | 'eager' // for NextImage only
priority?: boolean // for NextImage only
ref?: Ref<HTMLImageElement | HTMLVideoElement | null>
resource?: MediaType | string | number // for Payload media

View File

@@ -1,19 +1,12 @@
// Keep these in sync with the CSS variables in the `_css` directory
// Keep these in sync with the CSS variables in your tailwind configuration
const cssVariables = {
export const cssVariables = {
breakpoints: {
l: 1440,
m: 1024,
s: 768,
},
colors: {
base0: 'rgb(255, 255, 255)',
base100: 'rgb(235, 235, 235)',
base500: 'rgb(128, 128, 128)',
base850: 'rgb(34, 34, 34)',
base1000: 'rgb(0, 0, 0)',
error500: 'rgb(255, 111, 118)',
'3xl': 1920,
'2xl': 1536,
xl: 1280,
lg: 1024,
md: 768,
sm: 640,
},
}
export default cssVariables

View File

@@ -92,7 +92,7 @@ export const home: RequiredDataFromCollectionSlug<'pages'> = {
format: 0,
mode: 'normal',
style: '',
text: 'here',
text: 'on our Github',
version: 1,
},
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 753 KiB

View File

@@ -38,10 +38,13 @@ export const HighImpactHero: React.FC<Page['hero']> = ({ links, media, richText
</div>
<div className="min-h-[80vh] select-none">
{media && typeof media === 'object' && (
<React.Fragment>
<Media fill imgClassName="-z-10 object-cover" resource={media} />
{/* <div className="absolute pointer-events-none left-0 bottom-0 w-full h-1/2 bg-gradient-to-t from-black to-transparent" /> */}
</React.Fragment>
<Media
fill
imgClassName="-z-10 object-cover"
priority={false}
loading="lazy"
resource={media}
/>
)}
</div>
</div>

View File

@@ -5,7 +5,9 @@ import type { Page, Post } from '../payload-types'
import { mergeOpenGraph } from './mergeOpenGraph'
import { getServerSideURL } from './getURL'
export const generateMeta = async (args: { doc: Page | Post }): Promise<Metadata> => {
export const generateMeta = async (args: {
doc: Partial<Page> | Partial<Post>
}): Promise<Metadata> => {
const { doc } = args || {}
const ogImage =