Skip to content

Commit df78d80

Browse files
committed
Enhance Markdown components to support eager loading for the first image
1 parent 62588bf commit df78d80

4 files changed

Lines changed: 44 additions & 6 deletions

File tree

src/components/markdown/Markdown.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,41 @@ import {
1313

1414
type MarkdownRenderOptions = {
1515
preserveTabPanels?: boolean
16+
eagerFirstImage?: boolean
1617
}
1718

1819
export type MarkdownProps = {
1920
content?: string
2021
document?: MarkdownDocument
2122
preserveTabPanels?: boolean
23+
/** Render the first image in the document as high-priority/eager (e.g. blog post hero images) */
24+
eagerFirstImage?: boolean
2225
}
2326

2427
export function Markdown({
2528
content,
2629
document,
2730
preserveTabPanels,
31+
eagerFirstImage,
2832
}: MarkdownProps) {
2933
const parsed = React.useMemo(
3034
() => document ?? parseSiteMarkdown(content ?? ''),
3135
[content, document],
3236
)
37+
const heroConsumedRef = React.useRef(false)
38+
heroConsumedRef.current = false
3339

3440
return React.useMemo(() => {
3541
return renderMarkdownReact(parsed, {
3642
allowHtml: true,
3743
components: createMarkdownComponents({
3844
preserveTabPanels,
45+
eagerFirstImage,
46+
heroConsumedRef,
3947
}),
4048
headingAnchors,
4149
})
42-
}, [parsed, preserveTabPanels])
50+
}, [parsed, preserveTabPanels, eagerFirstImage])
4351
}
4452

4553
const headingAnchors = {
@@ -160,7 +168,11 @@ function TableElement({
160168
)
161169
}
162170

163-
function createMarkdownComponents(options: MarkdownRenderOptions = {}) {
171+
function createMarkdownComponents(
172+
options: MarkdownRenderOptions & {
173+
heroConsumedRef?: React.RefObject<boolean>
174+
} = {},
175+
) {
164176
function MdCommentComponentWithOptions(
165177
props: React.ComponentProps<typeof MdCommentComponent>,
166178
) {
@@ -172,6 +184,18 @@ function createMarkdownComponents(options: MarkdownRenderOptions = {}) {
172184
)
173185
}
174186

187+
function ImgElement(props: React.ComponentProps<typeof MarkdownImg>) {
188+
const heroConsumedRef = options.heroConsumedRef
189+
const isFirstImage =
190+
options.eagerFirstImage && heroConsumedRef && !heroConsumedRef.current
191+
192+
if (isFirstImage) {
193+
heroConsumedRef.current = true
194+
}
195+
196+
return <MarkdownImg {...props} priority={isFirstImage} />
197+
}
198+
175199
return {
176200
a: LinkElement,
177201
code: CodeElement,
@@ -184,7 +208,7 @@ function createMarkdownComponents(options: MarkdownRenderOptions = {}) {
184208
h5: createHeadingComponent('h5'),
185209
h6: createHeadingComponent('h6'),
186210
iframe: MarkdownIframe,
187-
img: MarkdownImg,
211+
img: ImgElement,
188212
'md-comment-component': MdCommentComponentWithOptions,
189213
'md-framework-panel': MdFrameworkPanel,
190214
'md-tab-panel': MdTabPanel,

src/components/markdown/MarkdownContent.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type MarkdownContentProps = {
3333
pagePath?: string
3434
/** Current framework for filtering markdown content */
3535
currentFramework?: string
36+
/** Render the first image in the document as high-priority/eager (e.g. blog post hero images) */
37+
eagerFirstImage?: boolean
3638
}
3739

3840
function CopyPageDropdownFallback() {
@@ -76,6 +78,7 @@ export function MarkdownContent({
7678
libraryVersion,
7779
pagePath,
7880
currentFramework,
81+
eagerFirstImage,
7982
}: MarkdownContentProps) {
8083
const [canLoadCopyControls, setCanLoadCopyControls] = React.useState(false)
8184

@@ -84,7 +87,11 @@ export function MarkdownContent({
8487
}, [])
8588

8689
const renderedMarkdown = (
87-
<Markdown document={markdown} preserveTabPanels={preserveTabPanels} />
90+
<Markdown
91+
document={markdown}
92+
preserveTabPanels={preserveTabPanels}
93+
eagerFirstImage={eagerFirstImage}
94+
/>
8895
)
8996

9097
const contentNode =

src/routes/blog.$.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ function BlogPost() {
182182
branch={branch}
183183
filePath={filePath}
184184
containerRef={markdownContainerRef}
185+
eagerFirstImage
185186
/>
186187
</div>
187188
{isTocVisible && (

src/ui/MarkdownImg.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import { getPublicImageDimensions } from '~/utils/publicImageDimensions'
55

66
const DEFAULT_TRANSFORM_WIDTH = 1200
77

8+
export type MarkdownImgProps = HTMLProps<HTMLImageElement> & {
9+
priority?: boolean
10+
}
11+
812
export const MarkdownImg = React.memo(function MarkdownImg({
913
alt,
1014
src,
1115
className,
1216
width,
1317
height,
1418
style,
19+
priority,
1520
children: _,
1621
...props
17-
}: HTMLProps<HTMLImageElement>) {
22+
}: MarkdownImgProps) {
1823
const sourceDimensions = src ? getPublicImageDimensions(src) : undefined
1924
const providedWidth = parseDimension(width)
2025
const providedHeight = parseDimension(height)
@@ -64,7 +69,8 @@ export const MarkdownImg = React.memo(function MarkdownImg({
6469
...style,
6570
}}
6671
className={`block max-w-full h-auto rounded-lg shadow-md ${className ?? ''}`}
67-
loading="lazy"
72+
loading={priority ? 'eager' : 'lazy'}
73+
fetchPriority={priority ? 'high' : undefined}
6874
decoding="async"
6975
/>
7076
)

0 commit comments

Comments
 (0)