ทุกการปรับแต่งเบื้องหลังคะแนน Lighthouse ที่สมบูรณ์แบบ
ผมรัน Lighthouse บนไซต์ของผมอยู่ตลอดเวลา คะแนนของผมอยู่ที่ 100 ในการรันแบบ local ทุกครั้ง และ 100 ใน Vercel's Real Experience Score ด้วย
คะแนนเหล่านี้ไม่ได้มาจากรายการตรวจสอบทั่วไป แต่ผมทำได้โดยการย้ายภาระงานจากเบราว์เซอร์ไปไว้ที่ขั้นตอนการ Build
และนี่คือวิธีที่ผมทำอย่างละเอียด
ย้ายงานไปไว้ที่ขั้นตอนการ Build
คู่มือส่วนใหญ่มักจะบอกให้คุณทำ lazy-load ทุกอย่าง แต่ผมชอบการทำ prerender มากกว่า ผมใช้ TanStack Start พร้อมกับเปิดใช้งานการ prerendering
สิ่งนี้จะเปลี่ยนไซต์ทั้งไซต์ให้กลายเป็น HTML แบบสแตติกในระหว่างการ build ทำให้เบราว์เซอร์ไม่ต้องประมวลผล JavaScript หนักๆ เพียงเพื่อจะแสดงหน้าแรก HTML จะพร้อมใช้งานทันทีเมื่อผู้ใช้กด Enter
คำนวณลอจิกที่ซับซ้อนไว้ล่วงหน้า
หน้าแรกของผมมีแผนที่โลกที่มีจุดถึง 5,000 จุด โดยปกติแล้ว ไลบรารีจะทำการ parse GeoJSON และรันการคำนวณบน main thread ซึ่งจะทำให้หน้าเว็บค้างไปประมาณ 1,000ms
ผมแก้ปัญหานี้โดยการย้ายการคำนวณไปไว้ใน build script:
- ผมสร้างสตริง SVG path เพียงชุดเดียวสำหรับจุดทั้ง 5,000 จุด
- การใช้ path เดียวทำให้เบราว์เซอร์เรนเดอร์ได้เร็วกว่าการเรนเดอร์วงกลมแยกกัน 5,000 วงมาก
- ผมคำนวณตารางค้นหาพิกัด (coordinate lookup tables) ไว้ล่วงหน้า เพื่อให้เบราว์เซอร์ไม่ต้องคำนวณอะไรเลยในขณะ runtime
ความล่าช้า 1,000ms จะกลายเป็นการวาดภาพ (paint) ครั้งเดียวที่เกิดขึ้นทันที
ปรับแต่งการโหลดฟอนต์
ผมใช้ rel="preload" สำหรับฟอนต์หลักของผม
ข้อผิดพลาดที่พบบ่อยคือการลืมใส่ attribute crossOrigin หากคุณละเลยมัน เบราว์เซอร์จะดึงฟอนต์มาสองครั้ง ซึ่งจะทำลายค่า Largest Contentful Paint (LCP) ของคุณ ผมเลือก preload เฉพาะฟอนต์สามตัวที่ใช้ในส่วนบนของหน้าจอ (above the fold) เท่านั้น
ใช้เครื่องมือที่เหมาะสมสำหรับการทำแอนิเมชัน
ผมใช้ SMIL สำหรับแอนิเมชันการเต้น (pulse) แบบง่ายๆ บนเครื่องหมายในแผนที่ ซึ่งประหยัดทรัพยากรกว่าการใช้ React state เพื่อขับเคลื่อนลูปแอนิเมชัน เพราะมันช่วยให้เบราว์เซอร์จัดการงานบน compositor thread ได้
สำหรับเส้นทาง (paths) ที่ซับซ้อน ผมใช้ motion โดยเน้นความเรียบง่าย ผมทำแอนิเมชันเพียงครั้งเดียวเมื่อ mount และหลีกเลี่ยงการดักฟังตำแหน่งการ scroll
ใช้เวกเตอร์และ WebP เป็นหลัก
หากเป็นโลโก้หรือรูปทรง ให้ใช้ SVG หากเป็นรูปถ่าย ให้ใช้ WebP วิธีนี้จะช่วยให้ขนาดไฟล์ต่ำและป้องกันการเกิด layout shifts
หลีกเลี่ยงการทำ Over-engineering
ผมไม่ได้ใช้ image CDN และไม่ได้ใช้การทำ code-splitting ที่ซับซ้อน ไซต์ของผมมีขนาดเล็ก ดังนั้นการทำ splitting ในระดับ route จึงเพียงพอแล้ว
คะแนนที่สมบูรณ์แบบอาจเป็นเพียงตัวชี้วัดที่เน้นความสวยงาม (vanity metric) แต่เป้าหมายที่แท้จริงคือการวัดประสิทธิภาพของคุณ และย้ายภาระงานให้มากที่สุดเท่าที่จะเป็นไปได้ออกจากอุปกรณ์ของผู้ใช้
My Portfolio: brodin.dev
Source code: github.com/NathanBrodin/Portfolio
TanStack Start prerendering: tanstack.com/start
Paper Shaders: shaders.paper.design
Full post: https://dev.to/nathan-brodin/every-optimization-behind-a-perfect-lighthouse-score-283n
