3
3
<div class =" flex flex-col justify-start mt-4 sm:mt-8 md:my-12 p-4 gap-8 md:gap-12 flex-grow max-w-full" >
4
4
<h1 class =" flex flex-row gap-4 text-white text-3xl md:text-5xl" >
5
5
<span class =" text-green-400" >» ; </span >
6
- <button v-if =" domain && !editing" class =" bg-transparent" @click =" enableEditing" >{{ domain }}</button >
7
- <form v-else class =" flex flex-col gap-4 overflow-hidden" @submit.prevent =" navigateToNewDomain" >
8
- <input ref =" input" v-model =" newDomain" name =" domain" type =" text"
9
- class =" md:-mt-1 rounded-none py-0 bg-transparent outline-none border-b-2 border-b-solid border-transparent focus:border-green-500 underline-dashed"
10
- autofocus inputmode =" url" autocapitalize =" none" placeholder =" Enter a domain" required />
11
- <button type =" submit"
12
- class =" bg-green-400 text-black hover: hover:bg-white focus:bg-white active:bg-white text-xl md:text-2xl py-2 px-6 md:self-start" >See
13
- results</button >
14
- </form >
6
+ <TheDomainForm v-model:domain =" domain" v-model:editing =" editing" />
15
7
</h1 >
16
8
<template v-if =" ! editing && domain " >
17
- <div v-if =" status === 'pending' || results"
18
- class =" flex flex-row flex-wrap gap-4 lg:flex-row justify-around w-full" >
19
- <ProgressRing size =" normal" :value =" results && status !== 'pending' ? results.performance : undefined"
20
- caption =" performance" />
21
- <ProgressRing size =" normal" :value =" results && status !== 'pending' ? results.accessibility : undefined"
22
- caption =" accessibility" />
23
- <ProgressRing size =" normal" :value =" results && status !== 'pending' ? results.bestPractices : undefined"
24
- caption =" best practices" />
25
- <ProgressRing size =" normal" :value =" results && status !== 'pending' ? results.seo : undefined" caption =" SEO" />
26
- </div >
9
+ <template v-if =" status === ' pending' || results " >
10
+ <div class =" flex flex-row flex-wrap gap-4 lg:flex-row justify-around w-full" >
11
+ <CoreWebVitals v-if =" status === 'pending' || results?.crux" :pass =" results?.crux?.cwv"
12
+ :lcp =" results?.crux?.lcp" :cls =" results?.crux?.cls" :inp =" results?.crux?.inp"
13
+ :loading =" status === 'pending'" size =" normal" show-p75 />
14
+ <template v-else-if =" results " >
15
+ <ProgressRing size =" normal" :value =" results.lighthouse.performance" caption =" performance" />
16
+ <ProgressRing size =" normal" :value =" results.lighthouse.accessibility" caption =" accessibility" />
17
+ <ProgressRing size =" normal" :value =" results.lighthouse.bestPractices" caption =" best practices" />
18
+ <ProgressRing size =" normal" :value =" results.lighthouse.seo" caption =" SEO" />
19
+ </template >
20
+ </div >
21
+ <LighthouseTable v-if =" status === 'pending' || (results && results.crux)" :loading =" status === 'pending'"
22
+ v-bind =" results?.lighthouse || {}" />
23
+ </template >
27
24
<div v-else-if =" domain" >
28
25
No results could be fetched. Is it a valid domain?
29
26
</div >
30
- <div class =" flex flex-col gap-2 mt-auto md:mt-8" >
31
- <NuxtLink type =" submit"
32
- class =" bg-green-400 text-black hover: hover:bg-white focus:bg-white active:bg-white text-xl md:text-2xl py-2 px-6 md:self-start mb-8"
33
- :href =" shareLink" @click.prevent =" nativeShare" >
34
- Share results
35
- </NuxtLink >
36
- <a :href =" `https://pagespeed.web.dev/analysis?url=https://${domain}`"
37
- class =" self-start underline text-gray-400 hover:text-green-400 focus:text-green-400 active:text-green-400" >
38
- See full results on PageSpeed Insights » ;
39
- </a >
40
- <span v-if =" results?.timestamp" class =" text-gray-400" >
41
- Last updated at
42
- <NuxtTime :datetime =" results.timestamp" dateStyle =" full" timeStyle =" medium" />.
43
- </span >
44
- </div >
27
+ <TheShareLink v-if =" results" :domain =" domain" :type =" results.crux ? 'crux' : 'pagespeed-insights'"
28
+ :timestamp =" results.crux?.timestamp || results.lighthouse.timestamp" />
45
29
</template >
46
30
</div >
47
31
<footer class =" mt-auto p-3 text-gray-400" >
58
42
59
43
<script lang="ts" setup>
60
44
import ' @unocss/reset/tailwind-compat.css'
61
- import { joinURL , withoutLeadingSlash , parseURL } from ' ufo'
45
+ import { joinURL , withoutLeadingSlash } from ' ufo'
62
46
63
47
const route = useRoute ()
64
48
const domain = computed (() => withoutLeadingSlash (route .path ).toLowerCase ().replace (/ (\/ | \? ). * $ / , ' ' ).trim ())
@@ -69,75 +53,26 @@ if (domain.value && !/^[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/g.test(domain.value)) {
69
53
}
70
54
71
55
const editing = ref (! domain .value )
72
- const newDomain = ref (' ' )
73
- const input = ref <HTMLInputElement >()
74
-
75
- function enableEditing () {
76
- newDomain .value = domain .value
77
- editing .value = true
78
- watch (input , (input ) => {
79
- if (input ) {
80
- input .focus ()
81
- input .setSelectionRange (0 , newDomain .value .length )
82
- }
83
- }, { once: true })
84
- }
85
56
86
- const { data : results, status, refresh } = await useFetch (() => ` /api/run/${domain .value } ` , {
57
+ const { data : results, status, refresh } = await useAsyncData (async () => {
58
+ const [lighthouse, crux] = await Promise .all ([
59
+ $fetch (` /api/run/${domain .value } ` ),
60
+ $fetch (` /api/crux/${domain .value } ` ).catch (() => null )
61
+ ])
62
+ return { lighthouse , crux }
63
+ }, {
64
+ watch: [domain ],
87
65
immediate: !! domain .value ,
88
66
})
89
67
90
68
if (! domain .value ) {
91
69
watch (domain , () => refresh (), { once: true })
92
70
}
93
71
94
- const favicon = computed (() => {
95
- const radius = 80
96
- const stroke = 14
97
- const normalizedRadius = radius - stroke * 2
98
- const circumference = normalizedRadius * 2 * Math .PI
99
-
100
- const value = status .value === ' pending' || (domain .value && ! results .value )
101
- ? undefined
102
- : (results .value ? results .value .performance : 100 )
103
- const color = ! value ? ' #6b7280' : value >= 90 ? ' #23c55e' : value >= 50 ? ' #fbbf24' : ' #ef4444'
104
- const svg = ` <svg xmlns="http://www.w3.org/2000/svg" height="${radius * 2 }" width="${radius * 2 }">
105
- <style>@keyframes spin {
106
- from {transform: rotate(0deg)}
107
- to {transform: rotate(360deg)}
108
- }</style>
109
- <circle
110
- stroke="${color }"
111
- fill="transparent"
112
- stroke-linecap="round"
113
- stroke-dasharray="${circumference + ' ' + circumference }"
114
- style="transform-origin:center;stroke-dashoffset:${circumference - (Math .floor ((value || 85 ) / 4 ) * 4 ) / 100 * circumference };transform:rotate(270deg)${! value ? ' ;animation:spin 1s linear infinite' : ' ' }"
115
- stroke-width="${stroke }"
116
- r="${normalizedRadius }"
117
- cx="${radius }"
118
- cy="${radius }"
119
- />
120
- <circle
121
- fill="${color }"
122
- stroke-width="${stroke }"
123
- r="${normalizedRadius - 35 }"
124
- cx="${radius }"
125
- cy="${radius }"
126
- />
127
- </svg> `
128
- return ` data:image/svg+xml;base64,${btoa (svg )} `
129
- })
72
+ useFavicon (() => status .value !== ' pending' && !! domain .value && (results .value ? results .value .lighthouse .performance : 100 ))
130
73
131
74
useHead ({
132
75
title : () => domain .value ? domain .value : ' page-speed.dev' ,
133
- link: [
134
- () => ({
135
- key: ' favicon' ,
136
- rel: ' icon' ,
137
- type: ' image/svg' ,
138
- href: favicon .value
139
- })
140
- ]
141
76
})
142
77
143
78
useServerHead ({
@@ -186,49 +121,29 @@ useServerSeoMeta({
186
121
if (! domain .value ) {
187
122
defineOgImageComponent (' Home' )
188
123
useServerSeoMeta ({
189
- description: ' See and share PageSpeed Insights results simply and easily.' ,
124
+ description: ' See and share Core Web Vitals and Page Speed Insights results simply and easily.' ,
190
125
})
191
126
} else if (results .value ) {
192
127
useServerSeoMeta ({
193
128
description:
194
- ` Performance: ${results .value ?.performance } | ` +
195
- ` Accessibility: ${results .value ?.accessibility } | ` +
196
- ` Best Practices: ${results .value ?.bestPractices } | ` +
197
- ` SEO: ${results .value ?.seo } `
129
+ results .value ?.crux
130
+ ?
131
+ ` Core Web Vitals: ${results .value ?.crux .cwv ? ' pass' : ' fail' } | ` +
132
+ ` LCP: ${results .value ?.crux .lcp .caption } | ` +
133
+ ` CLS: ${results .value ?.crux .cls .caption } | ` +
134
+ ` INP: ${results .value ?.crux .inp .caption } `
135
+ :
136
+ ` Performance: ${results .value ?.lighthouse .performance } | ` +
137
+ ` Accessibility: ${results .value ?.lighthouse .accessibility } | ` +
138
+ ` Best Practices: ${results .value ?.lighthouse .bestPractices } | ` +
139
+ ` SEO: ${results .value ?.lighthouse .seo } `
198
140
})
199
141
200
142
defineOgImageComponent (' Lighthouse' , {
201
- performance: results .value ?.performance ,
202
- accessibility: results .value ?.accessibility ,
203
- bestPractices: results .value ?.bestPractices ,
204
- seo: results .value ?.seo ,
143
+ lighthouse: results .value .lighthouse ,
144
+ crux: results .value ?.crux ,
205
145
domain: domain .value ,
206
146
})
207
147
}
208
148
209
- function navigateToNewDomain () {
210
- if (! newDomain .value ) { return }
211
-
212
- const host = parseURL (newDomain .value ).host || newDomain .value
213
- editing .value = false
214
- return navigateTo (' /' + withoutLeadingSlash (host ).toLowerCase ().replace (/ (\/ | \? ). * $ / , ' ' ).trim ())
215
- }
216
-
217
- const shareLink = computed (() => domain .value ? ` https://twitter.com/intent/tweet?text=${encodeURIComponent (` Check out the Page Speed results for ${domain .value .replace (/ \. / g , ' .' )} ` + ` \n\n https://page-speed.dev/${domain .value }` )} ` : ' See and share PageSpeed Insights results simply and easily.' )
218
-
219
- async function nativeShare () {
220
- try {
221
- if (navigator .share ) {
222
- return await navigator .share ({
223
- title: ' page-speed.dev' ,
224
- text: ` See page speed results for ${domain .value .replace (/ \. / g , ' .' )} ` ,
225
- url: canonicalURL .value ,
226
- })
227
- }
228
- } catch {
229
- // ignore errors sharing to native share and fall back directly to Twitter
230
- }
231
- return await navigateTo (shareLink .value , { external: true , open: { target: ' _blank' } })
232
- }
233
-
234
149
</script >
0 commit comments