Skip to content

Boost: Surface Error details for LCP Optimization #43647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5903f2f
Refactor LCP component: Introduce error handling and tooltip for opti…
LiamSarsfield May 27, 2025
36a9e13
Changelog
LiamSarsfield May 27, 2025
d0c0af9
Refactor LCP error handling: Introduce ErrorDetails component
LiamSarsfield May 27, 2025
7521e68
Enhance LCP background image optimization: Add conditional optimizati…
LiamSarsfield May 27, 2025
7822175
Refactor LCP error display: Update ErrorDetails component and styles
LiamSarsfield May 28, 2025
4c28ed3
Use property instead of
haqadn May 28, 2025
6c69217
Rename lcp image URL method and make it reusable
haqadn May 28, 2025
c7a8b10
changelog
haqadn May 28, 2025
5531637
Refactor LCP error handling: Update ErrorDetails component and styles
LiamSarsfield May 29, 2025
01a5ca3
Merge branch 'trunk' into add/boost/lcp/error-messaging
LiamSarsfield May 29, 2025
eb980e6
Merge branch 'add/boost/lcp/error-messaging' of github.com:Automattic…
LiamSarsfield May 29, 2025
aae21b4
Merge branch 'update/lcp-bg-url-property' into add/boost/lcp/error-me…
LiamSarsfield May 29, 2025
1c3e4ee
Use `selector` instead of `element` property
haqadn May 29, 2025
24993ff
Merge remote-tracking branch 'origin/update/lcp-bg-url-property' into…
LiamSarsfield May 29, 2025
f8964a6
Merge branch 'trunk' into update/lcp-bg-url-property
LiamSarsfield May 29, 2025
508eb38
Merge branch 'update/lcp-bg-url-property' into add/boost/lcp/error-me…
LiamSarsfield May 29, 2025
1952e14
Merge branch 'trunk' into add/boost/lcp/error-messaging
LiamSarsfield May 30, 2025
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import useMeasure from 'react-use-measure';
import ChevronDown from '$svg/chevron-down';
import ChevronUp from '$svg/chevron-up';
import { Button } from '@automattic/jetpack-components';
import { animated, useSpring } from '@react-spring/web';
import clsx from 'clsx';
import { useState } from 'react';
import ChevronDown from '$svg/chevron-down';
import ChevronUp from '$svg/chevron-up';
import useMeasure from 'react-use-measure';
import styles from './folding-element.module.scss';
import { Button } from '@automattic/jetpack-components';

type PropTypes = {
labelExpandedText: string;
Expand Down
66 changes: 9 additions & 57 deletions projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,13 @@
import { __ } from '@wordpress/i18n';
import styles from './lcp.module.scss';
import { Button } from '@automattic/jetpack-components';
import RefreshIcon from '$svg/refresh';
import Module from '$features/module/module';
import { useLcpState, useOptimizeLcpAction } from './lib/stores/lcp-state';
import TimeAgo from '$features/critical-css/time-ago/time-ago';
import { recordBoostEvent } from '$lib/utils/analytics';
import Pill from '$features/ui/pill/pill';

const Status = () => {
const [ query ] = useLcpState();
const lcpState = query?.data;

if ( lcpState?.status === 'error' ) {
return (
<div className={ styles?.failures }>
{ __(
"An error occurred while optimizing your Cornerstone Page's LCP. Please try again.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status === 'not_analyzed' ) {
// This should never happen, but just in case.
return (
<div>
{ __(
"Click the optimize button to start optimizing your Cornerstone Page's LCP.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status === 'pending' ) {
return (
<div className={ styles?.generating }>
{ __(
"Jetpack Boost is optimizing your Cornerstone Page's LCP for you.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status !== 'analyzed' || ! lcpState?.updated ) {
return null;
}

return (
<div className={ styles?.successes }>
{ __( 'Last optimized', 'jetpack-boost' ) }{ ' ' }
<TimeAgo time={ new Date( lcpState.updated * 1000 ) } />.
</div>
);
};
import { recordBoostEvent } from '$lib/utils/analytics';
import RefreshIcon from '$svg/refresh';
import { Button } from '@automattic/jetpack-components';
import { __ } from '@wordpress/i18n';
import styles from './status/status.module.scss';
import { useLcpState, useOptimizeLcpAction } from './lib/stores/lcp-state';
import Status from './status/status';
import { ErrorDetails } from './status/error-details';

const Lcp = () => {
const [ query ] = useLcpState();
Expand Down Expand Up @@ -109,6 +60,7 @@ const Lcp = () => {
{ __( 'Optimize', 'jetpack-boost' ) }
</Button>
</div>
{ lcpState?.status === 'analyzed' && <ErrorDetails /> }
</Module>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { JSONSchema } from '$lib/utils/json-types';
import z from 'zod';

// @TODO: We don't send this back from the API, but it's here for if we do.
// TODO: Reflect this in Boost Cloud after Beta release, each one should be an Error type.
export const LcpErrorType = z.enum( [
'UrlError',
'HttpError',
Expand All @@ -11,10 +10,7 @@ export const LcpErrorType = z.enum( [
] );

export const LcpErrorDetailsSchema = z.object( {
url: z.coerce.string(),
message: z.coerce.string(),
meta: z.record( JSONSchema ).catch( {} ),
type: LcpErrorType,
} );

export const PageSchema = z.object( {
Expand All @@ -25,7 +21,6 @@ export const PageSchema = z.object( {
// Status
status: z.enum( [ 'success', 'pending', 'error' ] ).catch( 'pending' ),
// Error details
// @TODO: We don't send this back from the API, but it's here for if we do.
errors: z.array( LcpErrorDetailsSchema ).optional(),
} );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import '$css/main/mixins';

.summary {
margin-top: 16px;
margin-bottom: 16px;

@include breakpoint( xs ) {
margin-bottom: 1em;
}

&__list {
display: flex;
flex-direction: column;
gap: 8px;
}

&__row {
list-style: disc;
margin-bottom: 0;
margin-left: 20px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import FoldingElement from '$features/critical-css/folding-element/folding-element';
import { recordBoostEvent } from '$lib/utils/analytics';
import { Notice } from '@automattic/jetpack-components';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useLcpState } from '../lib/stores/lcp-state';
import styles from './error-details.module.scss';

export const ErrorDetails = () => {
const [ query ] = useLcpState();
const lcpState = query?.data;

if ( lcpState?.status !== 'analyzed' ) {
return null;
}

const pages = lcpState?.pages;
if ( ! pages || pages.length === 0 ) {
return null;
}

const errors = pages.filter( page => ( page?.errors?.length || 0 ) > 0 );
if ( errors.length === 0 ) {
return null;
}

const errorMessages = errors.flatMap( p => ( p.errors || [] ).map( e => e.message ) );

return (
<Notice
level="warning"
hideCloseButton={ true }
title={ __( 'LCP Optimization issues', 'jetpack-boost' ) }
>
<div className={ styles.summary }>
{ sprintf(
// translators: %d is a number of pages which failed to be optimized
_n(
'%d page could not be optimized.',
'%d pages could not be optimized.',
errorMessages.length,
'jetpack-boost'
),
errorMessages.length
) }
</div>
<FoldingElement
labelExpandedText={ __( 'View details', 'jetpack-boost' ) }
labelCollapsedText={ __( 'Hide details', 'jetpack-boost' ) }
onExpand={ ( isExpanded: boolean ) => {
if ( isExpanded ) {
recordBoostEvent( 'lcp_error_details_expanded', {} );
}
} }
>
<ul className={ styles.summary__list }>
{ errorMessages.map( ( error, index ) => (
<li className={ styles.summary__row } key={ index }>
{ error }
</li>
) ) }
</ul>
</FoldingElement>
</Notice>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
@import "$css/main/mixins";
@import "$css/main/variables";
@import '$css/main/mixins';
@import '$css/main/variables';

// TODO: This is a copy of the styles from the critical-css/status component.
// We should move this to a shared location.

.status {
margin-bottom: 32px;
margin-bottom: 16px;
font-size: 14px;
line-height: 22px;
display: flex;
Expand All @@ -16,7 +16,7 @@
margin-bottom: 0;
}

@include breakpoint(xs) {
@include breakpoint( xs ) {
display: block;
}

Expand All @@ -25,47 +25,28 @@
flex-grow: 1;
position: relative;

.successes, .generating {
.successes,
.generating {
color: $gray_40;

@include breakpoint(md) {
@include breakpoint( md ) {
margin-right: 115px;
}
}

.failures {
margin-top: 1em;
color: $gray_100;

@include breakpoint(xs) {
margin-bottom: 1em;
}

svg {
position: absolute;
width: 1.4rem;
height: 1.4rem;
left: -60px;
}
}
}

button {

&:global(.components-button.has-icon) {
&:global( .components-button.has-icon ) {
min-width: auto;
}

svg {
fill: $jetpack-green;
}
}

.optimize-button:global(.components-button) {
.optimize-button:global( .components-button ) {

&:disabled {
background: transparent;
opacity: .5;
opacity: 0.5;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import TimeAgo from '$features/critical-css/time-ago/time-ago';
import { __ } from '@wordpress/i18n';
import { useLcpState } from '../lib/stores/lcp-state';
import styles from './status.module.scss';

const Status: React.FC = () => {
const [ query ] = useLcpState();
const lcpState = query?.data;

if ( lcpState?.status === 'error' ) {
return (
<div className={ styles?.failures }>
{ __(
"An error occurred while optimizing your Cornerstone Page's LCP. Please try again.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status === 'not_analyzed' ) {
// This should never happen, but just in case.
return (
<div>
{ __(
"Click the optimize button to start optimizing your Cornerstone Page's LCP.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status === 'pending' ) {
return (
<div className={ styles?.generating }>
{ __(
"Jetpack Boost is optimizing your Cornerstone Page's LCP for you.",
'jetpack-boost'
) }
</div>
);
}

if ( lcpState?.status !== 'analyzed' || ! lcpState?.updated ) {
return null;
}

return (
<>
<div className={ styles?.successes }>
{ __( 'Last optimized', 'jetpack-boost' ) }{ ' ' }
<TimeAgo time={ new Date( lcpState.updated * 1000 ) } />.
</div>
</>
);
};

export default Status;
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public function preload_background_images() {
$selectors = array();

foreach ( $this->lcp_data as $lcp_data ) {
$lcp_optimizer = new LCP_Optimization_Util( $lcp_data );
if ( ! $lcp_optimizer->can_optimize() ) {
continue;
}

if ( in_array( $lcp_data['selector'], $selectors, true ) ) {
// If we already printed the styling for this element, skip it.
continue;
Expand Down Expand Up @@ -74,14 +79,18 @@ public function add_bg_style_override() {
$selectors = array();

foreach ( $this->lcp_data as $lcp_data ) {
$lcp_optimizer = new LCP_Optimization_Util( $lcp_data );
if ( ! $lcp_optimizer->can_optimize() ) {
continue;
}

if ( in_array( $lcp_data['selector'], $selectors, true ) ) {
// If we already printed the styling for this element, skip it.
continue;
}
$selectors[] = $lcp_data['selector'];

$lcp_optimizer = new LCP_Optimization_Util( $lcp_data );
$image_url = $lcp_optimizer->get_lcp_image_url();
$image_url = $lcp_optimizer->get_lcp_image_url();
if ( empty( $image_url ) ) {
continue;
}
Expand Down
Loading
Loading