เราปรับปรุงประสิทธิภาพ SmashingMag อย่างไร

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ ในบทความนี้ เราจะพิจารณาอย่างใกล้ชิดถึงการเปลี่ยนแปลงบางอย่างที่เราทำในไซต์นี้ — ทำงานบน JAMStack พร้อม React — เพื่อปรับประสิทธิภาพเว็บให้เหมาะสมและปรับปรุงตัวชี้วัด Core Web Vitals ด้วยความผิดพลาดบางอย่างที่เราทำ และการเปลี่ยนแปลงที่คาดไม่ถึงซึ่งช่วยเพิ่มเมตริกทั้งหมดทั่วกระดาน

เรื่องราวประสิทธิภาพของเว็บทุกเรื่องคล้ายกันใช่ไหม เริ่มต้นด้วยการยกเครื่องเว็บไซต์ที่รอคอยมายาวนานเสมอ วันที่โปรเจ็กต์ที่ได้รับการขัดเกลาอย่างเต็มที่และปรับให้เหมาะสมที่สุด เปิดตัว จัดอันดับให้สูงขึ้นและทะยานเหนือคะแนนประสิทธิภาพใน Lighthouse และ WebPageTest มีการเฉลิมฉลองและความรู้สึกถึงความสำเร็จอย่างสุดหัวใจในอากาศ — สะท้อนให้เห็นอย่างสวยงามในการรีทวีตและความคิดเห็นและจดหมายข่าวและกระทู้ Slack

แต่เมื่อเวลาผ่านไป ความตื่นเต้นก็ค่อยๆ หายไป และการปรับเปลี่ยนอย่างเร่งด่วน คุณลักษณะที่จำเป็นมาก และความต้องการทางธุรกิจใหม่ๆ ก็คืบคลานเข้ามา และในทันใด ก่อนที่คุณจะรู้ตัว โค้ดเบสก็มี น้ำหนักเกินและกระจัดกระจาย เล็กน้อย สคริปต์ต้องโหลดเร็วขึ้นเล็กน้อย และเนื้อหาไดนามิกใหม่ที่เป็นประกายได้เข้าสู่ DOM ผ่านแบ็คดอร์ของสคริปต์บุคคลที่สามและแขกที่ไม่ได้รับเชิญ

เราเคยไปที่นั่นที่ Smashing เช่นกัน มีคนไม่มากที่รู้เรื่องนี้ แต่เราเป็นทีมเล็กๆ ที่มีสมาชิกประมาณ 12 คน ซึ่งหลายคนทำงานนอกเวลาและส่วนใหญ่มักจะสวมหมวกหลายใบในแต่ละวัน แม้ว่าผลงานจะเป็นเป้าหมายของเรามาเกือบทศวรรษแล้วก็ตาม แต่เราไม่เคยมีทีมผลงานที่ทุ่มเทจริงๆ

หลังจากการออกแบบใหม่ครั้งล่าสุดเมื่อปลายปี 2017 Ilya Pukhalski ทำงานด้าน JavaScript (นอกเวลา), Michael Riethmueller ในด้าน CSS ของสิ่งต่างๆ (ไม่กี่ชั่วโมงต่อสัปดาห์) และของคุณคือเล่นเกมใจด้วย CSS ที่สำคัญ และพยายามเล่นกลหลายๆ อย่างมากเกินไป

ภาพหน้าจอแหล่งที่มาของประสิทธิภาพแสดงคะแนน Lighthouse ระหว่าง 40 ถึง 60
นี่คือที่ที่เราเริ่มต้น ด้วยคะแนน Lighthouse ที่อยู่ระหว่าง 40 ถึง 60 เราจึงตัดสินใจที่จะจัดการกับประสิทธิภาพ (อีกครั้ง) แบบตรงไปตรงมา (ที่มาของรูปภาพ: Lighthouse Metrics) (ตัวอย่างขนาดใหญ่)

เมื่อมันเกิดขึ้น เราสูญเสียการติดตามการทำงานในกิจวัตรประจำวันที่วุ่นวาย เรากำลังออกแบบและสร้างสิ่งต่างๆ ตั้งค่าผลิตภัณฑ์ใหม่ ปรับโครงสร้างส่วนประกอบ และเผยแพร่บทความ ดังนั้นในช่วงปลายปี 2020 สิ่งต่างๆ ก็เริ่มควบคุมไม่ได้ โดย คะแนน Lighthouse สีแดงอมเหลือง ค่อยๆ ปรากฏขึ้นทั่วกระดาน เราต้องแก้ไขสิ่งนั้น

นั่นคือที่ที่เราอยู่

บางท่านอาจรู้ว่าเรากำลังใช้งาน JAMStack โดยบทความและหน้าทั้งหมดจัดเก็บเป็นไฟล์ Markdown, ไฟล์ Sass ที่คอมไพล์เป็น CSS, JavaScript แบ่งออกเป็นส่วนๆ ด้วย Webpack และ Hugo สร้างหน้าสแตติกที่เราให้บริการโดยตรงจาก Edge CDN ย้อนกลับไปในปี 2017 เราได้สร้างเว็บไซต์ทั้งหมดด้วย Preact แต่หลังจากนั้นได้ย้ายไปที่ React ในปี 2019 — และใช้ร่วมกับ API สองสามตัวสำหรับการค้นหา แสดงความคิดเห็น การรับรองความถูกต้อง และการชำระเงิน

เว็บไซต์ทั้งหมดสร้างขึ้นโดยคำนึงถึงการเพิ่มประสิทธิภาพแบบก้าวหน้า หมายความว่าคุณผู้อ่านที่รัก สามารถอ่านบทความ Smashing ทุกบทความได้อย่างครบถ้วนโดยไม่ต้องบูตแอปพลิเคชันเลย ก็ไม่น่าแปลกใจเช่นกัน ในท้ายที่สุด บทความที่ตีพิมพ์ไม่ได้เปลี่ยนแปลงอะไรมากในช่วงหลายปีที่ผ่านมา ในขณะที่ชิ้นส่วนแบบไดนามิก เช่น การรับรองความถูกต้องของสมาชิกและการชำระเงิน ต้องการให้แอปพลิเคชันทำงาน

บิลด์ทั้งหมดสำหรับการปรับใช้บทความ 2,500 บทความสดใช้เวลาประมาณ 6 นาที ในขณะนี้ กระบวนการสร้างด้วยตัวมันเองได้กลายเป็นสัตว์ร้ายเมื่อเวลาผ่านไปเช่นกัน ด้วยการฉีด CSS ที่สำคัญ การแยกโค้ดของ Webpack การแทรกโฆษณาและแผงคุณลักษณะแบบไดนามิก การสร้าง RSS (อีกครั้ง) และการทดสอบ A/B ในท้ายที่สุดบนขอบ

ในช่วงต้นปี 2020 เราได้เริ่มด้วยการปรับโครงสร้างองค์ประกอบเค้าโครง CSS ครั้ง ใหญ่ เราไม่เคยใช้ CSS-in-JS หรือ styled-components แต่เป็นระบบที่ใช้ส่วนประกอบที่ดีของ Sass-modules ซึ่งจะคอมไพล์เป็น CSS ย้อนกลับไปในปี 2017 เลย์เอาต์ทั้งหมดถูกสร้างขึ้นด้วย Flexbox และสร้างใหม่ด้วย CSS Grid และ CSS Custom Properties ในกลางปี ​​2019 อย่างไรก็ตาม บางหน้าจำเป็นต้องได้รับการดูแลเป็นพิเศษเนื่องจากมีจุดโฆษณาใหม่และแผงผลิตภัณฑ์ใหม่ ดังนั้นในขณะที่เลย์เอาต์ทำงาน มันทำงานได้ไม่ดีนัก และดูแลรักษาค่อนข้างยาก

นอกจากนี้ ส่วนหัวที่มีการนำทางหลักยังต้องเปลี่ยนเพื่อรองรับรายการอื่นๆ ที่เราต้องการแสดงแบบไดนามิก นอกจากนี้ เราต้องการปรับโครงสร้างองค์ประกอบที่ใช้บ่อยบางส่วนที่ใช้ในไซต์ และ CSS ที่ใช้นั้นจำเป็นต้องมีการแก้ไขเช่นกัน — กล่องจดหมายข่าวเป็นผู้กระทำผิดที่โดดเด่นที่สุด เราเริ่มต้นด้วยการจัดโครงสร้างองค์ประกอบบางอย่างใหม่ด้วย CSS ที่เน้นยูทิลิตี้เป็นหลัก แต่เราไม่เคยไปถึงจุดที่มีการใช้อย่างสม่ำเสมอทั่วทั้งไซต์

ปัญหาที่ใหญ่กว่าคือ กลุ่ม JavaScript ขนาดใหญ่ ซึ่งไม่น่าแปลกใจเลยที่บล็อกเธรดหลักเป็นเวลาหลายร้อยมิลลิวินาที ชุด JavaScript ขนาดใหญ่อาจดูไม่ปกติในนิตยสารที่เพียงแค่เผยแพร่บทความ แต่จริงๆ แล้ว มีสคริปต์จำนวนมากเกิดขึ้นเบื้องหลัง

เรามีสถานะส่วนประกอบต่างๆ สำหรับลูกค้าที่ตรวจสอบสิทธิ์และลูกค้าที่ไม่ผ่านการตรวจสอบสิทธิ์ เมื่อคุณลงชื่อเข้าใช้แล้ว เราต้องการแสดงผลิตภัณฑ์ทั้งหมดในราคาสุดท้าย และเมื่อคุณเพิ่มหนังสือลงในรถเข็น เราต้องการให้รถเข็นสามารถเข้าถึงได้ด้วยการแตะเพียงปุ่มเดียว ไม่ว่าคุณจะอยู่ในหน้าใด การโฆษณาต้องเข้ามาอย่างรวดเร็วโดยไม่ทำให้เกิดการ เปลี่ยนแปลงของเล ย์เอาต์ และเช่นเดียวกันกับแผงผลิตภัณฑ์ดั้งเดิมที่เน้นผลิตภัณฑ์ของเรา บวกกับพนักงานบริการที่แคชสินทรัพย์คงที่ทั้งหมดและให้บริการสำหรับการดูซ้ำ พร้อมกับบทความเวอร์ชันแคชที่ผู้อ่านเคยเข้าชมแล้ว

ดังนั้นสคริปต์ทั้งหมดนี้จึงต้องเกิดขึ้นใน บาง จุด และทำให้ประสบการณ์การอ่านลดลง แม้ว่าสคริปต์จะเข้ามาค่อนข้างช้าก็ตาม ตรงไปตรงมา เรากำลังทำงานอย่างอุตสาหะในไซต์และส่วนประกอบใหม่โดยไม่ได้จับตาดูประสิทธิภาพการทำงานอย่างใกล้ชิด (และเรามีสิ่งอื่นๆ อีกสองสามข้อที่ต้องคำนึงถึงในปี 2020) จุดเปลี่ยนมาโดยไม่คาดคิด Harry Roberts จัดการ (ยอดเยี่ยม) Web Performance Masterclass ของเขาในฐานะเวิร์กช็อปออนไลน์กับเรา และตลอดทั้งเวิร์กชอป เขาใช้ Smashing เป็นตัวอย่างโดยเน้นปัญหาที่เรามีและแนะนำวิธีแก้ไขปัญหาเหล่านั้นควบคู่ไปกับเครื่องมือและแนวทางที่เป็นประโยชน์

ตลอดการประชุมเชิงปฏิบัติการ ฉันได้จดบันทึกและทบทวนฐานรหัสอย่างขยันขันแข็ง ในช่วงเวลาของการประชุมเชิงปฏิบัติการ คะแนน Lighthouse ของเราอยู่ ที่ 60–68 คะแนนในหน้าแรก และประมาณ 40-60 คะแนนสำหรับหน้าบทความ และแย่กว่านั้นอย่างเห็นได้ชัดในอุปกรณ์เคลื่อนที่ เลิกงานแล้วพวกเราก็ทำงานต่อ

ระบุคอขวด

เรามักจะพึ่งพาคะแนนใดคะแนนหนึ่งเพื่อทำความเข้าใจว่าเราทำงานได้ดีเพียงใด แต่บ่อยครั้งเกินไปที่คะแนนเดี่ยวไม่ได้ให้ภาพรวมที่สมบูรณ์ ตามที่ David East ระบุไว้อย่างชัดเจนในบทความของเขา ประสิทธิภาพของเว็บไม่ใช่ค่าเดียว มันคือการกระจาย แม้ว่าประสบการณ์การใช้งานเว็บจะหนักหน่วงและทั่วถึงเป็นประสิทธิภาพรอบด้านที่ได้รับการปรับแต่งอย่างเต็มที่ แต่ก็ไม่อาจทำได้เร็วเพียงเท่านี้ ผู้เข้าชมบางคนอาจจะเร็ว แต่ท้ายที่สุดแล้ว ผู้เข้าชมบางคนอาจจะช้ากว่า (หรือช้า) ด้วย

เหตุผลมีมากมาย แต่เหตุผลที่สำคัญที่สุดคือเงื่อนไขเครือข่ายและฮาร์ดแวร์ของอุปกรณ์ต่างกันมากทั่วโลก บ่อยครั้งเราไม่สามารถโน้มน้าวสิ่งเหล่านั้นได้จริงๆ ดังนั้นเราจึงต้องแน่ใจว่าประสบการณ์ของเรารองรับสิ่งเหล่านั้นแทน

โดยพื้นฐานแล้ว งานของเราคือการเพิ่มสัดส่วนของประสบการณ์ที่ฉับไว และลดสัดส่วนของประสบการณ์ที่เฉื่อยชา แต่สำหรับสิ่งนั้น เราจำเป็นต้องได้ภาพที่เหมาะสม ว่าจริงๆ แล้วการกระจายคืออะไร ตอนนี้ เครื่องมือวิเคราะห์และเครื่องมือตรวจสอบประสิทธิภาพจะให้ข้อมูลนี้เมื่อจำเป็น แต่เราตรวจสอบ CrUX ซึ่งเป็นรายงานประสบการณ์ผู้ใช้ Chrome โดยเฉพาะ CrUX สร้างภาพรวมของการกระจายประสิทธิภาพเมื่อเวลาผ่านไป โดยมีการรับส่งข้อมูลที่รวบรวมจากผู้ใช้ Chrome ข้อมูลส่วนใหญ่นี้เกี่ยวข้องกับ Core Web Vitals ซึ่ง Google ได้ประกาศย้อนกลับไปในปี 2020 และมีส่วนสนับสนุนและเปิดเผยใน Lighthouse

สถิติ Largest Contentful Paint (LCP) แสดงการลดลงอย่างมากระหว่างเดือนพฤษภาคมและกันยายนในปี 2020
การกระจายประสิทธิภาพสำหรับ Largest Contentful Paint ในปี 2020 ระหว่างเดือนพฤษภาคมถึงกันยายน ประสิทธิภาพลดลงอย่างมาก ข้อมูลจาก CrUX (ตัวอย่างขนาดใหญ่)

เราสังเกตว่าโดยรวมแล้ว ผลงานของเราถดถอยอย่างมากตลอดทั้งปี โดยลดลงโดยเฉพาะในช่วงเดือนสิงหาคมและกันยายน เมื่อเราเห็นแผนภูมิเหล่านี้แล้ว เราสามารถมองย้อนกลับไปใน PR บางส่วนที่เราได้เผยแพร่ไปแล้วเพื่อศึกษาสิ่งที่เกิดขึ้นจริง

ใช้เวลาไม่นานในการค้นหาว่าเราได้เปิดตัวแถบนำทางใหม่ในช่วงเวลาเหล่านี้ แถบการนำทางนั้น — ใช้กับทุกหน้า — อาศัย JavaScript เพื่อแสดงรายการการนำทางในเมนูเมื่อแตะหรือเมื่อคลิก แต่จริงๆ แล้ว JavaScript บิตของมันถูกรวมไว้ภายในบันเดิ ล app.js เพื่อปรับปรุง Time To Interactive เราตัดสินใจ แยกสคริปต์การนำทางออก จากบันเดิลและให้บริการแบบอินไลน์

ในช่วงเวลาเดียวกัน เราได้เปลี่ยนจากไฟล์ CSS สำคัญ ๆ ที่สร้างด้วยตนเอง (ล้าสมัย) ไปเป็นระบบอัตโนมัติที่สร้าง CSS ที่สำคัญสำหรับทุกเทมเพลต — หน้าแรก บทความ หน้าผลิตภัณฑ์ เหตุการณ์ กระดานงาน และอื่นๆ — และ CSS ที่สำคัญในบรรทัดระหว่าง เวลาสร้าง แต่เราไม่รู้จริง ๆ ว่า CSS ที่สำคัญที่สร้างโดยอัตโนมัตินั้นหนักแค่ไหน เราต้องสำรวจรายละเอียดเพิ่มเติม

และในช่วงเวลาเดียวกัน เรากำลังปรับการ โหลดแบบอักษรของเว็บ พยายามผลักดันแบบอักษรของเว็บให้รุนแรงขึ้นด้วยคำแนะนำทรัพยากร เช่น การ โหลดล่วงหน้า ดูเหมือนว่าจะเป็นการตอบโต้กับความพยายามด้านประสิทธิภาพของเรา เนื่องจากแบบอักษรของเว็บทำให้การแสดงผลเนื้อหาล่าช้า จึงมีการจัดลำดับความสำคัญมากเกินไปถัดจากไฟล์ CSS แบบเต็ม

ตอนนี้ สาเหตุทั่วไปประการหนึ่งของการถดถอยคือค่าใช้จ่ายจำนวนมากของ JavaScript ดังนั้นเราจึงตรวจสอบ Webpack Bundle Analyzer และแผนที่คำขอของ Simon Hearne เพื่อให้ได้ภาพของการพึ่งพา JavaScript ของเรา มันดูค่อนข้างมีสุขภาพดีในตอนเริ่มต้น

ภาพแผนที่ความคิดของการพึ่งพา JavaScript
ไม่มีอะไรแปลกใหม่จริงๆ: แผนที่คำขอดูเหมือนจะไม่มากเกินไปในตอนแรก (ตัวอย่างขนาดใหญ่)

มีคำขอสองสามรายการมาถึง CDN ซึ่งเป็นบริการยินยอมเกี่ยวกับคุกกี้ Cookiebot, Google Analytics รวมถึงบริการภายในของเราสำหรับการให้บริการแผงผลิตภัณฑ์และการโฆษณาที่กำหนดเอง ดูเหมือนว่าจะไม่มีคอขวดมากมาย จนกระทั่งเราพิจารณาให้ละเอียดยิ่งขึ้น

ในการทำงานด้านประสิทธิภาพ เป็นเรื่องปกติที่จะดูประสิทธิภาพของหน้าที่สำคัญบางหน้า ซึ่งน่าจะเป็นหน้าแรกและส่วนใหญ่มักจะเป็นหน้าบทความ/ผลิตภัณฑ์สองสามหน้า อย่างไรก็ตาม แม้ว่าจะมีหน้าแรกเพียงหน้าแรก แต่อาจมีหน้าผลิตภัณฑ์ต่างๆ มากมาย เราจึงต้องเลือกหน้าที่เป็นตัวแทนของผู้ชมของเรา

อันที่จริง ในขณะที่เราเผยแพร่บทความเกี่ยวกับ SmashingMag ที่เน้นการใช้โค้ดและการออกแบบค่อนข้างมาก หลายปีที่ผ่านมาเราได้สะสมบทความนับพันที่มี GIF จำนวนมาก ตัวอย่างโค้ดที่เน้นไวยากรณ์ การฝัง CodePen วิดีโอ/เสียง ฝังและเธรดที่ซ้อนกันของความคิดเห็นที่ไม่มีวันสิ้นสุด

เมื่อนำมารวมกัน หลายคนทำให้เกิดการ ระเบิดในขนาด DOM ควบคู่ไปกับ การทำงานของเธรดหลักที่มากเกินไป ทำให้ประสบการณ์ในหน้าหลายพันหน้าช้าลง ไม่ต้องพูดถึงว่าด้วยการโฆษณา องค์ประกอบ DOM บางอย่างถูกฉีดเข้าไปในช่วงท้ายของวงจรชีวิตของเพจ ทำให้เกิดการคำนวณรูปแบบใหม่และทาสีซ้ำซ้อน ซึ่งเป็นงานที่มีราคาแพงซึ่งสามารถสร้างงานที่ยาวได้

ทั้งหมดนี้ไม่ปรากฏในแผนที่ที่เราสร้างขึ้นสำหรับหน้าบทความที่ค่อนข้างไม่ซับซ้อนในแผนภูมิด้านบน ดังนั้นเราจึงเลือกหน้าที่หนักที่สุดที่เรามี — หน้าแรกที่ทรงพลัง หน้ายาวที่สุด หน้าที่มีการฝังวิดีโอจำนวนมาก และหน้าที่มีการฝัง CodePen จำนวนมาก — และตัดสินใจที่จะเพิ่มประสิทธิภาพหน้าเหล่านั้นให้มากที่สุด ท้ายที่สุด ถ้าพวกมันเร็ว หน้าที่มีการฝัง CodePen เดียวก็ควรจะเร็วขึ้นเช่นกัน

เมื่อคำนึงถึงหน้าเหล่านี้ แผนที่จึงดูแตกต่างไปเล็กน้อย สังเกตเส้นหนาขนาดใหญ่ที่มุ่งหน้าไปยังเครื่องเล่น Vimeo และ Vimeo CDN โดยมีคำขอ 78 รายการมาจากบทความ Smashing

ภาพแผนที่ความคิดที่แสดงปัญหาด้านประสิทธิภาพโดยเฉพาะในบทความที่ใช้การฝังวิดีโอและ/หรือวิดีโอจำนวนมาก
ในหน้าบทความบางหน้า กราฟจะดูแตกต่างออกไป โดยเฉพาะอย่างยิ่งกับการฝังโค้ดหรือวิดีโอจำนวนมาก ประสิทธิภาพก็ลดลงค่อนข้างมาก น่าเสียดายที่บทความของเรามีบทความมากมาย (ตัวอย่างขนาดใหญ่)

เพื่อศึกษาผลกระทบต่อเธรดหลัก เราได้เจาะลึกไปที่แผงประสิทธิภาพใน DevTools โดยเฉพาะอย่างยิ่ง เรากำลังมองหางานที่ใช้เวลานานกว่า 50 มิลลิวินาที (เน้นด้วยสี่เหลี่ยมสีแดงที่มุมบนขวา) และงานที่มีลักษณะการคำนวณใหม่ (แถบสีม่วง) อย่างแรกจะบ่งบอกถึงการเรียกใช้ JavaScript ที่มีราคาแพง ในขณะที่อันหลังจะเปิดเผยการทำให้รูปแบบเป็นโมฆะซึ่งเกิดจากการแทรกเนื้อหาแบบไดนามิกใน DOM และ CSS ที่ไม่เหมาะสม สิ่งนี้ทำให้เรามีตัวชี้ที่สามารถนำไปดำเนินการได้ว่าจะเริ่มต้นจากที่ใด ตัวอย่างเช่น เราค้นพบอย่างรวดเร็วว่าการโหลดแบบอักษรเว็บของเรามีค่าใช้จ่ายในการทาสีใหม่อย่างมาก ในขณะที่กลุ่ม JavaScript ยังคงหนักพอที่จะบล็อกเธรดหลักได้

สกรีนช็อตของแผงประสิทธิภาพใน DevTools ที่แสดง JavaScript ที่ยังคงหนักพอที่จะบล็อกเธรดหลักได้
การศึกษาแผงประสิทธิภาพใน DevTools มีงานยาวสองสามงาน ใช้เวลามากกว่า 50 มิลลิวินาทีและบล็อกเธรดหลัก (ตัวอย่างขนาดใหญ่)

โดยพื้นฐานแล้ว เราได้ตรวจสอบ Core Web Vitals อย่างใกล้ชิด โดยพยายามทำให้แน่ใจว่าเราทำคะแนนได้ดีในทุกด้าน เราเลือกที่จะเน้นไปที่อุปกรณ์เคลื่อนที่ที่ช้าโดยเฉพาะ — ด้วย 3G ที่ช้า, 400ms RTT และความเร็วในการถ่ายโอน 400kbps เพื่อมองในแง่ร้าย จึงไม่น่าแปลกใจที่ Lighthouse จะไม่พึงพอใจกับไซต์ของเราเช่นกัน โดยให้คะแนนสีแดงที่แน่นหนาสำหรับบทความที่หนักที่สุด และบ่นอย่างไม่รู้จักเหน็ดเหนื่อยเกี่ยวกับ JavaScript, CSS, รูปภาพนอกจอและขนาดที่ไม่ได้ใช้

ภาพหน้าจอของข้อมูล Lighthouse ที่แสดงโอกาสและการประหยัดโดยประมาณ
Lighthouse ไม่ค่อยพอใจกับประสิทธิภาพของบางหน้าเช่นกัน นั่นคืออันที่มีการฝังวิดีโอมากมาย (ตัวอย่างขนาดใหญ่)

เมื่อเรามีข้อมูลอยู่ตรงหน้าแล้ว เราสามารถมุ่งเน้นไปที่การเพิ่มประสิทธิภาพหน้าบทความที่หนักที่สุดสามหน้า โดยเน้นที่ CSS ที่สำคัญ (และไม่สำคัญ) ชุด JavaScript งานที่ใช้เวลานาน การโหลดแบบอักษรของเว็บ การเปลี่ยนเลย์เอาต์ และบุคคลที่สาม -ฝัง ภายหลัง เราจะแก้ไขฐานรหัสเพื่อลบรหัสเดิมและใช้คุณลักษณะใหม่ของเบราว์เซอร์ที่ทันสมัย ดูเหมือนว่างานข้างหน้าจะเยอะ และแน่นอนว่าเราค่อนข้างยุ่งสำหรับเดือนต่อๆ ไป

การปรับปรุงลำดับของสินทรัพย์ใน <head>

กระแทกแดกดัน สิ่งแรกที่เราพิจารณาไม่ได้เกี่ยวข้องอย่างใกล้ชิดกับงานทั้งหมดที่เราระบุไว้ข้างต้น ในเวิร์กชอปด้านประสิทธิภาพ แฮร์รี่ใช้เวลาพอสมควรในการอธิบายลำดับของเนื้อหาใน <head> ของแต่ละหน้า การชี้ว่าการแสดงเนื้อหาที่สำคัญอย่างรวดเร็วหมายถึงการมีกลยุทธ์และใส่ใจอย่างมากเกี่ยวกับการจัดลำดับเนื้อหาในซอร์สโค้ด .

ตอนนี้ ไม่ควรมาเป็นการเปิดเผยครั้งใหญ่ว่า CSS ที่สำคัญมีประโยชน์ต่อประสิทธิภาพของเว็บ อย่างไรก็ตาม เป็นเรื่องแปลกใจเล็กน้อยที่ ความแตกต่าง ของลำดับของเนื้อหาอื่นๆ ทั้งหมด — คำแนะนำทรัพยากร การโหลดแบบอักษรเว็บล่วงหน้า สคริปต์ซิงโครนัสและอะซิงโครนัส CSS และข้อมูลเมตาแบบเต็ม

เราได้พลิกกลับ <head> ทั้งหมด โดยวาง CSS ที่สำคัญไว้ข้าง หน้า สคริปต์อะซิงโครนัสทั้งหมดและเนื้อหาที่โหลดไว้ล่วงหน้าทั้งหมด เช่น แบบอักษร รูปภาพ ฯลฯ เราได้แยกย่อยเนื้อหาที่เราจะเชื่อมต่อล่วงหน้าหรือโหลดล่วงหน้าตามเทมเพลตและ ประเภทไฟล์ เพื่อให้รูปภาพที่สำคัญ การเน้นไวยากรณ์ และการฝังวิดีโอจะถูกร้องขอในช่วงต้นของบทความและหน้าเว็บบางประเภทเท่านั้น

โดยทั่วไป เราได้จัดลำดับอย่างระมัดระวังใน <head> ลดจำนวนเนื้อหาที่โหลดล่วงหน้าซึ่งแข่งขันกันเพื่อแบนด์วิดท์ และมุ่งเน้นที่การรับสิทธิ์ CSS ที่สำคัญ หากคุณต้องการเจาะลึกลงไปในข้อควรพิจารณาที่สำคัญบางประการด้วยคำสั่ง <head> แฮร์รี่จะเน้นย้ำในบทความเรื่อง CSS และประสิทธิภาพของเครือข่าย การเปลี่ยนแปลงนี้เพียงอย่างเดียวทำให้เราได้คะแนน Lighthouse ประมาณ 3-4 คะแนนทั่วกระดาน

การย้ายจาก CSS Critical อัตโนมัติกลับไปเป็น CSS Critical แบบแมนนวล

การย้ายแท็ก <head> เป็นส่วนที่เรียบง่ายของเรื่องราว สิ่งที่ยากกว่าคือการสร้างและจัดการไฟล์ CSS ที่สำคัญ ย้อนกลับไปในปี 2017 เราสร้างสรรค์ CSS ที่สำคัญด้วยตนเองสำหรับทุกเทมเพลต โดยรวบรวมสไตล์ทั้งหมดที่จำเป็นในการแสดง ความสูง 1,000 พิกเซลแรกใน ทุกความกว้างของหน้าจอ แน่นอนว่านี่เป็นงานที่ยุ่งยากและไม่น่าสนใจเล็กน้อย ไม่ต้องพูดถึงปัญหาการบำรุงรักษาสำหรับการเชื่องไฟล์ CSS ที่สำคัญทั้งครอบครัวและไฟล์ CSS แบบเต็ม

ดังนั้นเราจึงพิจารณาตัวเลือกใน การทำให้กระบวนการนี้เป็นอัตโนมัติโดย เป็นส่วนหนึ่งของรูทีนบิลด์ ไม่มีเครื่องมือที่ขาดแคลนจริงๆ ดังนั้นเราจึงได้ทำการทดสอบบางส่วนและตัดสินใจทำการทดสอบสองสามอย่าง เราสามารถตั้งค่าและดำเนินการได้ค่อนข้างเร็ว ดูเหมือนว่าเอาต์พุตจะดีพอสำหรับกระบวนการอัตโนมัติ ดังนั้นหลังจากปรับแต่งการกำหนดค่าเล็กน้อย เราจึงเสียบปลั๊กและนำไปใช้กับการผลิต ซึ่งเกิดขึ้นราวๆ กรกฎาคม-สิงหาคมปีที่แล้ว ซึ่งแสดงให้เห็นอย่างชัดเจนจากการพุ่งขึ้นอย่างรวดเร็วและประสิทธิภาพที่ลดลงในข้อมูล CrUX ด้านบน เรากลับไปกลับมาด้วยการกำหนดค่า ซึ่งมักจะมีปัญหากับสิ่งง่ายๆ เช่น การเพิ่มรูปแบบเฉพาะหรือการนำรูปแบบอื่นๆ ออก เช่น รูปแบบข้อความขอความยินยอมในการใช้คุกกี้ซึ่งไม่ได้รวมอยู่ในหน้าเว็บจริงๆ เว้นแต่สคริปต์คุกกี้จะเริ่มต้น

ในเดือนตุลาคม เราได้แนะนำการเปลี่ยนแปลงเลย์เอาต์หลักๆ บางอย่างในไซต์ และเมื่อพิจารณา CSS ที่สำคัญ เราก็พบปัญหาเดิมอีกครั้ง — ผลลัพธ์ที่สร้างขึ้นนั้นค่อนข้างละเอียด และไม่ใช่สิ่งที่เราต้องการ . จากการทดลองในช่วงปลายเดือนตุลาคม เราทุกคนได้รวมจุดแข็งของเราไว้เพื่อทบทวนแนวทาง CSS ที่สำคัญของเราอีกครั้ง และศึกษาว่า CSS ที่สำคัญที่ ออกแบบด้วยมือจะมีขนาดเล็กลงเพียงใด เราสูดหายใจเข้าลึกๆ และใช้เวลาหลายวันกับเครื่องมือครอบคลุมโค้ดบนหน้าหลัก เราจัดกลุ่มกฎ CSS ด้วยตนเอง และนำรหัสที่ซ้ำกันและรหัสเดิมออกในทั้งสองที่ — CSS ที่สำคัญและ CSS หลัก เป็นการล้างข้อมูลที่จำเป็นอย่างยิ่ง เนื่องจากรูปแบบต่างๆ ที่เขียนขึ้นในปี 2560-2561 ล้าสมัยในช่วงหลายปีที่ผ่านมา

ด้วยเหตุนี้ เราจึงลงเอยด้วยไฟล์ CSS ที่สำคัญที่จัดทำขึ้นด้วยมือสามไฟล์ และอีกสามไฟล์ที่อยู่ระหว่างดำเนินการ:

  • critic-homepage-manual.css (8.2 KB, Brotlified)
  • critic-article-manual.css (8 KB, Brotlified)
  • critic-articles-manual.css (6 KB, Brotlified)
  • critic-books-manual.css ( งานที่ต้องทำ )
  • critic-events-manual.css ( งานที่ต้องทำ )
  • Critical-job-board-manual.css ( งานที่ต้องทำ )

ไฟล์ต่างๆ จะอยู่ในส่วนหัวของแต่ละเทมเพลต และในขณะนี้ไฟล์เหล่านั้นจะถูกทำซ้ำในกลุ่ม CSS แบบเสาหินที่มีทุกอย่างที่เคยใช้ (หรือไม่ได้ใช้แล้ว) บนไซต์ ในขณะนี้ เรากำลังพิจารณาที่จะแยกกลุ่ม CSS ทั้งหมดออกเป็นแพ็คเกจ CSS สองสามตัว ดังนั้นผู้อ่านนิตยสารจะไม่ดาวน์โหลดสไตล์จากกระดานงานหรือหน้าหนังสือ แต่เมื่อไปถึงหน้าเหล่านั้นจะได้รับการแสดงผลอย่างรวดเร็ว ด้วย CSS ที่สำคัญและรับ CSS ที่เหลือสำหรับหน้านั้นแบบอะซิงโครนัส — เฉพาะในหน้านั้นเท่านั้น

เป็นที่ยอมรับว่าไฟล์ CSS สำคัญที่จัดทำขึ้นด้วยมือนั้นไม่ได้เล็กลงมากนัก: เราได้ ลดขนาดไฟล์ CSS ที่สำคัญลงประมาณ 14% อย่างไรก็ตาม พวกเขารวมทุกอย่างที่เราต้องการในลำดับที่ถูกต้องตั้งแต่ต้นจนจบโดยไม่มีรูปแบบซ้ำซ้อนและเอาชนะสไตล์ ดูเหมือนจะเป็นก้าวไปในทิศทางที่ถูกต้อง และทำให้ Lighthouse เพิ่มขึ้นอีก 3-4 แต้ม เรากำลังก้าวหน้า

การเปลี่ยนการโหลดแบบอักษรของเว็บ

ด้วย font-display ที่ปลายนิ้วของเรา การโหลดแบบอักษรดูเหมือนจะเป็นปัญหาในอดีต ขออภัย มันไม่ถูกต้องนักในกรณีของเรา คุณผู้อ่านที่รัก ดูเหมือนจะเยี่ยมชมบทความมากมายใน Smashing Magazine คุณยังกลับมาที่ไซต์เพื่ออ่านบทความอื่นอยู่บ่อยครั้ง บางทีอาจสองสามชั่วโมงหรือวันต่อมา หรือบางทีอาจถึงหนึ่งสัปดาห์ต่อมา ปัญหาหนึ่งที่เรามีกับ font-display ใช้ทั่วทั้งไซต์คือสำหรับผู้อ่านที่ย้ายไปมาระหว่างบทความเป็นจำนวนมาก เราสังเกตเห็นการกะพริบจำนวนมากระหว่างแบบอักษรทางเลือกและแบบอักษรของเว็บ (ซึ่งโดยปกติไม่ควรเกิดขึ้นเนื่องจากแบบอักษรจะเป็น แคชอย่างถูกต้อง)

ซึ่งไม่รู้สึกเหมือนเป็นประสบการณ์ที่ดีของผู้ใช้ ดังนั้นเราจึงพิจารณาตัวเลือกต่างๆ ใน Smashing เราใช้ แบบอักษรหลักสองแบบ — Mija สำหรับหัวเรื่องและ Elena สำหรับตัวคัดลอก Mija มาในสองตุ้มน้ำหนัก (ปกติและตัวหนา) ในขณะที่เอเลน่ามาในสามตุ้มน้ำหนัก (ปกติ ตัวเอียง ตัวหนา) เราทิ้ง Bold Italic ของ Elena เมื่อหลายปีก่อนในระหว่างการออกแบบใหม่ เพียงเพราะเราใช้มันในไม่กี่หน้า เราย่อยฟอนต์อื่นๆ โดยการลบอักขระที่ไม่ได้ใช้และช่วง Unicode

บทความของเราส่วนใหญ่กำหนดเป็นข้อความ ดังนั้นเราจึงพบว่าโดยส่วนใหญ่บนไซต์ Largest Contentful Paint อาจเป็นย่อหน้าแรกของข้อความในบทความหรือรูปถ่ายของผู้แต่ง ซึ่งหมายความว่าเราต้องระมัดระวังเป็นพิเศษเพื่อให้แน่ใจว่าย่อหน้าแรกจะปรากฏอย่างรวดเร็วในแบบอักษรทางเลือก ในขณะที่เปลี่ยนแบบอักษรของเว็บอย่างงดงามด้วยการรีโฟลว์เพียงเล็กน้อย

ดูประสบการณ์การโหลดเริ่มต้นของหน้าแรกอย่างใกล้ชิด (ช้าลงสามครั้ง):

เรามีเป้าหมายหลักสี่ประการในการค้นหาวิธีแก้ปัญหา:

  1. ในการเข้าชมครั้งแรก ให้แสดงข้อความทันทีด้วยแบบอักษรสำรอง
  2. จับคู่เมตริกฟอนต์ของฟอนต์สำรองและฟอนต์เว็บเพื่อลดการเปลี่ยนเลย์เอาต์
  3. โหลดแบบอักษรเว็บทั้งหมดแบบอะซิงโครนัสและปรับใช้ทั้งหมดพร้อมกัน (สูงสุด 1 รีโฟลว์)
  4. ในการเข้าชมครั้งต่อๆ ไป ให้แสดงข้อความทั้งหมดโดยตรงในแบบอักษรของเว็บ (โดยไม่กะพริบหรือรีโฟลว์)

ตอนแรกเราพยายามใช้ font-display: swap on font-face ดูเหมือนจะเป็นตัวเลือกที่ง่ายที่สุด อย่างไรก็ตาม ตามที่กล่าวไว้ข้างต้น ผู้อ่านบางคนจะเข้าชมหลายหน้า ดังนั้นเราจึงลงเอยด้วยการกะพริบถี่ๆ กับแบบอักษรหกแบบที่เราแสดงผลทั่วทั้งไซต์ ด้วย การแสดงแบบอักษร เพียงอย่างเดียว เราไม่สามารถจัดกลุ่มคำขอหรือทาสีใหม่ได้

อีกแนวคิดหนึ่งคือการแสดงทุกอย่างเป็น แบบอักษรสำรองในการเข้าชมครั้งแรก จากนั้นจึงขอและแคชแบบอักษรทั้งหมดแบบอะซิงโครนัส และเฉพาะในการเข้าชมครั้งต่อๆ ไปเท่านั้นที่จะส่งแบบอักษรของเว็บจากแคชโดยตรง ปัญหาของแนวทางนี้คือมีผู้อ่านจำนวนหนึ่งมาจากเสิร์ชเอ็นจิ้น และอย่างน้อยบางคนก็จะเห็นหน้านั้นเพียงหน้าเดียว และเราไม่ต้องการแสดงบทความในแบบอักษรของระบบเพียงอย่างเดียว

แล้วอะไรล่ะ?

ตั้งแต่ปี 2017 เราได้ใช้วิธีการแสดงผลสองขั้นตอนสำหรับการโหลดแบบอักษรของเว็บ ซึ่งโดยทั่วไปจะอธิบายการเรนเดอร์สองขั้นตอน: ขั้นตอนแรกมีชุดย่อยของแบบอักษรเว็บน้อยที่สุด และอีกชุดมีน้ำหนักแบบอักษรที่สมบูรณ์ ย้อนกลับไปในสมัยก่อน เราได้สร้างชุดย่อยของ Mija Bold และ Elena Regular ซึ่งเป็นตุ้มน้ำหนักที่ใช้บ่อยที่สุดในไซต์ ชุดย่อยทั้งสองประกอบด้วยอักขระละติน เครื่องหมายวรรคตอน ตัวเลข และอักขระพิเศษสองสามตัวเท่านั้น แบบอักษรเหล่านี้ ( ElenaInitial.woff2 และ MijaInitial.woff2 ) มีขนาดเล็กมาก โดยมักมีขนาดเพียง 10–15 KBs เราให้บริการพวกเขาในขั้นตอนแรกของการแสดงแบบอักษร โดยแสดงทั้งหน้าในแบบอักษรทั้งสองนี้

CLS ที่เกิดจากแบบอักษรเว็บกะพริบ
CLS เกิดจากการกะพริบของแบบอักษรของเว็บ (เงาภายใต้รูปภาพของผู้เขียนกำลังเคลื่อนที่เนื่องจากการเปลี่ยนแบบอักษร) สร้างด้วยตัวสร้าง GIF ของ Layout Shift (ตัวอย่างขนาดใหญ่)

เราใช้ Font Loading API ซึ่งให้ข้อมูลว่าฟอนต์ใดโหลดสำเร็จและฟอนต์ใดยังไม่โหลด เบื้องหลังเกิดขึ้นโดยการเพิ่มคลาส .wf-loaded-stage1 ให้กับ body ด้วยสไตล์ที่แสดงเนื้อหาในฟอนต์เหล่านั้น:

 .wf-loaded-stage1 article, .wf-loaded-stage1 promo-box, .wf-loaded-stage1 comments { font-family: ElenaInitial,sans-serif; } .wf-loaded-stage1 h1, .wf-loaded-stage1 h2, .wf-loaded-stage1 .btn { font-family: MijaInitial,sans-serif; }

เนื่องจากไฟล์ฟอนต์มีขนาดเล็ก หวังว่าจะผ่านเครือข่ายได้อย่างรวดเร็ว จากนั้นในขณะที่ผู้อ่านสามารถเริ่มอ่านบทความได้จริง เราโหลดฟอนต์แบบเต็มน้ำหนักแบบอะซิงโครนัส และเพิ่ม .wf-loaded-stage2 ลงใน เนื้อหา :

 .wf-loaded-stage2 article, .wf-loaded-stage2 promo-box, .wf-loaded-stage2 comments { font-family: Elena,sans-serif; } .wf-loaded-stage2 h1, .wf-loaded-stage2 h2, .wf-loaded-stage2 .btn { font-family: Mija,sans-serif; }

ดังนั้นเมื่อโหลดหน้า ผู้อ่านจะได้รับฟอนต์เว็บชุดย่อยอย่างรวดเร็วก่อน จากนั้นเราจะสลับไปยังตระกูลฟอนต์แบบเต็ม ตามค่าเริ่มต้นแล้ว การสลับเหล่านี้ระหว่างแบบอักษรสำรองและแบบอักษรของเว็บจะเกิดขึ้นแบบสุ่ม โดยพิจารณาจากสิ่งใดก็ตามที่มาก่อนผ่านเครือข่าย ที่อาจรู้สึกค่อนข้างก่อกวนเมื่อคุณเริ่มอ่านบทความ ดังนั้น แทนที่จะปล่อยให้เบราว์เซอร์ตัดสินใจว่าจะเปลี่ยนฟอนต์เมื่อใด เราจัดกลุ่ม repaints เพื่อลดผลกระทบของการรีโฟลว์ให้เหลือน้อยที่สุด

 /* Loading web fonts with Font Loading API to avoid multiple repaints. With help by Irina Lipovaya. */ /* Credit to initial work by Zach Leatherman: https://noti.st/zachleat/KNaZEg/the-five-whys-of-web-font-loading-performance#sWkN4u4 */ // If the Font Loading API is supported... // (If not, we stick to fallback fonts) if ("fonts" in document) { // Create new FontFace objects, one for each font let ElenaRegular = new FontFace( "Elena", "url(/fonts/ElenaWebRegular/ElenaWebRegular.woff2) format('woff2')" ); let ElenaBold = new FontFace( "Elena", "url(/fonts/ElenaWebBold/ElenaWebBold.woff2) format('woff2')", { weight: "700" } ); let ElenaItalic = new FontFace( "Elena", "url(/fonts/ElenaWebRegularItalic/ElenaWebRegularItalic.woff2) format('woff2')", { style: "italic" } ); let MijaBold = new FontFace( "Mija", "url(/fonts/MijaBold/Mija_Bold-webfont.woff2) format('woff2')", { weight: "700" } ); // Load all the fonts but render them at once // if they have successfully loaded let loadedFonts = Promise.all([ ElenaRegular.load(), ElenaBold.load(), ElenaItalic.load(), MijaBold.load() ]).then(result => { result.forEach(font => document.fonts.add(font)); document.documentElement.classList.add('wf-loaded-stage2'); // Used for repeat views sessionStorage.foutFontsStage2Loaded = true; }).catch(error => { throw new Error(`Error caught: ${error}`); }); }

อย่างไรก็ตาม จะเกิดอะไรขึ้นหากฟอนต์ย่อยชุดแรกไม่ผ่านเครือข่ายอย่างรวดเร็ว เราสังเกตเห็นว่าสิ่งนี้ดูเหมือนจะเกิดขึ้นบ่อยกว่าที่เราต้องการ ในกรณีนั้น หลังจากหมดเวลา 3 วินาที เบราว์เซอร์สมัยใหม่จะถอยกลับไปเป็นแบบอักษรของระบบ (ในกองแบบอักษรของเรา มันจะเป็น Arial) จากนั้นสลับไปที่ ElenaInitial หรือ MijaInitial เพื่อเปลี่ยนเป็น Elena หรือ Mija แบบเต็มในภายหลัง . นั่นทำให้เกิดการกระพริบมากเกินไปในการชิมของเรา เรากำลังคิดที่จะลบการเรนเดอร์สเตจแรกสำหรับเครือข่ายที่ช้าในตอนแรกเท่านั้น (ผ่าน Network Information API) แต่เราตัดสินใจลบมันทั้งหมด

ดังนั้นในเดือนตุลาคม เราจึงนำส่วนย่อยออกไปพร้อมกับระยะกลาง เมื่อใดก็ตามที่น้ำหนัก ทั้งหมด ของทั้งแบบอักษร Elena และ Mija ถูกดาวน์โหลดโดยไคลเอนต์และพร้อมที่จะนำไปใช้ เราจะเริ่มขั้นตอนที่ 2 และทาสีใหม่ทั้งหมดพร้อมกัน และเพื่อให้การรีโฟลว์สังเกตเห็นได้น้อยลง เราจึงใช้เวลาเล็กน้อยใน การจับคู่แบบอักษรสำรองและแบบอักษรของเว็บ ซึ่งส่วนใหญ่หมายถึงการใช้ขนาดแบบอักษรและความสูงของบรรทัดที่แตกต่างกันเล็กน้อยสำหรับองค์ประกอบที่ทาสีในส่วนแรกที่มองเห็นได้ของหน้า

เพื่อที่เราใช้ font-style-matcher และ (อะแฮ่ม อะแฮ่ม) ตัวเลขเวทย์มนตร์สองสามตัว นั่นเป็นเหตุผลว่าทำไมเราเริ่มใช้ -apple-system และ Arial เป็นฟอนต์สำรองทั่วโลก ซานฟรานซิสโก (แสดงผ่าน -apple-system ) ดูเหมือนจะดีกว่า Arial เล็กน้อย แต่ถ้ามันไม่พร้อมใช้งาน เราเลือกใช้ Arial เพียงเพราะมันมีการแพร่กระจายอย่างกว้างขวางในระบบปฏิบัติการส่วนใหญ่

ใน CSS จะมีลักษณะดังนี้:

 .article__summary { font-family: -apple-system,Arial,BlinkMacSystemFont,Roboto Slab,Droid Serif,Segoe UI,Ubuntu,Cantarell,Georgia,sans-serif; font-style: italic; /* Warning: magic numbers ahead! */ /* San Francisco Italic and Arial Italic have larger x-height, compared to Elena */ font-size: 0.9213em; line-height: 1.487em; } .wf-loaded-stage2 .article__summary { font-family: Elena,sans-serif; font-size: 1em; /* Original font-size for Elena Italic */ line-height: 1.55em; /* Original line-height for Elena Italic */ }

สิ่งนี้ใช้ได้ผลค่อนข้างดี เราแสดงข้อความทันที และแบบอักษรของเว็บจะเข้ามาบนหน้าจอที่จัดกลุ่ม ทำให้เกิดการรีโฟลว์เพียงครั้งเดียวในมุมมองแรก และไม่มีการรีโฟลว์ทั้งหมดในมุมมองที่ตามมา

เมื่อดาวน์โหลดแบบอักษรแล้ว เราจะเก็บไว้ใน แคชของพนักงานบริการ ในการเข้าชมครั้งต่อๆ ไป เราจะตรวจสอบก่อนว่าแบบอักษรนั้นอยู่ในแคชหรือไม่ หากเป็นเช่นนั้น เราจะดึงข้อมูลจากแคชของพนักงานบริการและนำไปใช้ทันที และถ้าไม่ใช่ เราก็เริ่มต้นใหม่ทั้งหมดด้วย fallback-web-font-switcheroo

โซลูชันนี้ลดจำนวนการรีโฟลว์ให้เหลือน้อยที่สุด (หนึ่ง) ในการเชื่อมต่อที่ค่อนข้างเร็ว ในขณะที่ยังคงรักษาแบบอักษรไว้อย่างต่อเนื่องและเชื่อถือได้ในแคช ในอนาคต เราหวังเป็นอย่างยิ่งว่าจะแทนที่ตัวเลขเวทย์มนตร์ด้วย f-mods บางทีแซค เลเธอร์แมนคงจะภูมิใจ

การระบุและการทำลาย JS . แบบเสาหิน

เมื่อเราศึกษาเธรดหลักในแผงประสิทธิภาพของ DevTools เรารู้ดีว่าเราจำเป็นต้องทำอะไร มีงานยาวแปดงานที่ใช้ระหว่าง 70ms ถึง 580ms บล็อกอินเทอร์เฟซและทำให้ไม่ตอบสนอง โดยทั่วไป สคริปต์เหล่านี้มีค่าใช้จ่ายมากที่สุด:

  • uc.js สคริปต์แจ้งเตือนคุกกี้ (70ms)
  • การคำนวณรูปแบบใหม่ที่เกิดจากไฟล์ full.css ขาเข้า (176ms) (CSS ที่สำคัญไม่มีสไตล์ที่ต่ำกว่าความสูง 1000px ในวิวพอร์ตทั้งหมด)
  • สคริปต์โฆษณาที่ทำงานบนเหตุการณ์โหลดเพื่อจัดการแผง ตะกร้าสินค้า ฯลฯ + การคำนวณรูปแบบใหม่ (276ms)
  • การเปลี่ยนแบบอักษรของเว็บ การคำนวณรูปแบบใหม่ (290ms)
  • การประเมิน app.js (580ms)

เรามุ่งเน้นไปที่สิ่งที่อันตรายที่สุดก่อน — อย่างที่ว่ากันยาวที่สุด Long Tasks

ภาพหน้าจอที่นำมาจาก DevTools แสดงการตรวจสอบความถูกต้องของสไตล์สำหรับหน้าแรกของนิตยสารยอดเยี่ยม
ที่ด้านล่าง Devtools จะแสดงรูปแบบที่ไม่ถูกต้อง — สวิตช์ฟอนต์ได้รับผลกระทบ 549 องค์ประกอบที่ต้องทาสีใหม่ ไม่ต้องพูดถึงการเปลี่ยนแปลงเลย์เอาต์ที่เกิดขึ้น (ตัวอย่างขนาดใหญ่)

อันแรกเกิดขึ้นเนื่องจากการคำนวณเลย์เอาต์ใหม่ที่มีราคาแพงซึ่งเกิดจากการเปลี่ยนฟอนต์ (จากฟอนต์สำรองเป็นฟอนต์ของเว็บ) ทำให้เกิดการทำงานพิเศษมากกว่า 290 มิลลิวินาที (บนแล็ปท็อปที่เร็วและการเชื่อมต่อที่รวดเร็ว) การลบสเตจที่หนึ่งออกจากการโหลดฟอนต์เพียงอย่างเดียว ทำให้เราได้กลับมาประมาณ 80 มิลลิวินาที มันยังไม่ดีพอเพราะมันเกินงบประมาณ 50ms ไปแล้ว ดังนั้นเราจึงเริ่มขุดลึกลงไป

สาเหตุหลักที่ทำให้การคำนวณใหม่เกิดขึ้นเพียงเพราะความแตกต่างอย่างมากระหว่างแบบอักษรสำรองและแบบอักษรของเว็บ ด้วย การจับคู่ความสูงของบรรทัดและขนาดสำหรับแบบอักษรสำรองและแบบอักษรของเว็บ เราสามารถหลีกเลี่ยงสถานการณ์ต่างๆ ได้มากมายเมื่อบรรทัดข้อความจะตัดในบรรทัดใหม่ในแบบอักษรสำรอง แต่จากนั้นจะเล็กลงเล็กน้อยและพอดีกับบรรทัดก่อนหน้า ทำให้เกิดการเปลี่ยนแปลงครั้งใหญ่ในเรขาคณิตของทั้งหน้า และทำให้เลย์เอาต์เปลี่ยนแปลงไปอย่างมาก เราได้เล่นกับ letter-spacing word-spacing ด้วย แต่ก็ไม่ได้ผลลัพธ์ที่ดี

ด้วยการเปลี่ยนแปลงเหล่านี้ เราสามารถลดได้อีก 50-80 มิลลิวินาที แต่เราไม่สามารถย่อให้ต่ำกว่า 120 มิลลิวินาทีโดยไม่แสดงเนื้อหาเป็นฟอนต์สำรองและแสดงเนื้อหาในฟอนต์ของเว็บในภายหลัง เห็นได้ชัดว่าควรส่งผลกระทบอย่างมากต่อผู้เข้าชมครั้งแรกเท่านั้น เนื่องจากการดูหน้าเว็บที่ตามมาจะแสดงผลด้วยแบบอักษรที่ดึงมาจากแคชของพนักงานบริการโดยตรง โดยไม่ต้องมีการปรับขั้นตอนใหม่ซึ่งมีค่าใช้จ่ายสูงเนื่องจากการสลับแบบอักษร

อย่างไรก็ตาม มันค่อนข้างสำคัญที่จะต้องสังเกตว่าในกรณีของเรา เราสังเกตว่า Long Tasks ส่วนใหญ่ไม่ได้เกิดจาก JavaScript จำนวนมาก แต่เกิดจาก Layout Recalculations และการแยกวิเคราะห์ CSS ซึ่งหมายความว่าเราจำเป็นต้องทำ CSS เล็กน้อย cleaning, especially watching out for situations when styles are overwritten. In some way, it was good news because we didn't have to deal with complex JavaScript issues that much. However, it turned out not to be straightforward as we are still cleaning up the CSS this very day. We were able to remove two Long Tasks for good, but we still have a few outstanding ones and quite a way to go. Fortunately, most of the time we aren't way above the magical 50ms threshold.

The much bigger issue was the JavaScript bundle we were serving, occupying the main thread for a whopping 580ms. Most of this time was spent in booting up app.js which contains React, Redux, Lodash, and a Webpack module loader. The only way to improve performance with this massive beast was to break it down into smaller pieces. So we looked into doing just that.

With Webpack, we've split up the monolithic bundle into smaller chunks with code-splitting , about 30Kb per chunk. We did some package.json cleansing and version upgrade for all production dependencies, adjusted the browserlistrc setup to address the two latest browser versions, upgraded to Webpack and Babel to the latest versions, moved to Terser for minification, and used ES2017 (+ browserlistrc) as a target for script compilation.

We also used BabelEsmPlugin to generate modern versions of existing dependencies. Finally, we've added prefetch links to the header for all necessary script chunks and refactored the service worker, migrating to Workbox with Webpack (workbox-webpack-plugin).

A screenshot showing JavaScript chunks affecting performance with each running no longer than 40ms on the main thread
JavaScript chunks in action, with each running no longer than 40ms on the main thread. (ตัวอย่างขนาดใหญ่)

Remember when we switched to the new navigation back in mid-2020, just to see a huge performance penalty as a result? The reason for it was quite simple. While in the past the navigation was just static plain HTML and a bit of CSS, with the new navigation, we needed a bit of JavaScript to act on opening and closing of the menu on mobile and on desktop. That was causing rage clicks when you would click on the navigation menu and nothing would happen, and of course, had a penalty cost in Time-To-Interactive scores in Lighthouse.

We removed the script from the bundle and extracted it as a separate script . Additionally, we did the same thing for other standalone scripts that were used rarely — for syntax highlighting, tables, video embeds and code embeds — and removed them from the main bundle; instead, we granularly load them only when needed.

Performance stats for the smashing magazine front page showing the function call for nav.js that happened right after a monolithic app.js bundle had been executed
Notice that the function call for nav.js is happening after a monolithic app.js bundle is executed. นั่นไม่ถูกต้องนัก (ตัวอย่างขนาดใหญ่)

However, what we didn't notice for months was that although we removed the navigation script from the bundle, it was loading after the entire app.js bundle was evaluated, which wasn't really helping Time-To-Interactive (see image above). We fixed it by preloading nav.js and deferring it to execute in the order of appearance in the DOM, and managed to save another 100ms with that operation alone. By the end, with everything in place we were able to bring the task to around 220ms.

A screenshot of the the Long task reduced by almost 200ms
By prioritizing the nav.js script, we were able to reduce the Long task by almost 200ms. (ตัวอย่างขนาดใหญ่)

We managed to get some improvement in place, but still have quite a way to go, with further React and Webpack optimizations on our to-do list. At the moment we still have three major Long Tasks — font switch (120ms), app.js execution (220ms) and style recalculations due to the size of full CSS (140ms). For us, it means cleaning up and breaking up the monolithic CSS next.

It's worth mentioning that these results are really the best-scenario- results. On a given article page we might have a large number of code embeds and video embeds, along with other third-party scripts and customer's browser extensions that would require a separate conversation.

Dealing With 3rd-Parties

Fortunately, our third-party scripts footprint (and the impact of their friends' fourth-party-scripts) wasn't huge from the start. But when these third-party scripts accumulated, they would drive performance down significantly. This goes especially for video embedding scripts , but also syntax highlighting, advertising scripts, promo panels scripts and any external iframe embeds.

Obviously, we defer all of these scripts to start loading after the DOMContentLoaded event, but once they finally come on stage, they cause quite a bit of work on the main thread. This shows up especially on article pages, which are obviously the vast majority of content on the site.

The first thing we did was allocating proper space to all assets that are being injected into the DOM after the initial page render. It meant width and height for all advertising images and the styling of code snippets. We found out that because all the scripts were deferred, new styles were invalidating existing styles, causing massive layout shifts for every code snippet that was displayed. We fixed that by adding the necessary styles to the critical CSS on the article pages.

We've re-established a strategy for optimizing images (preferably AVIF or WebP — still work in progress though). All images below the 1000px height threshold are natively lazy-loaded (with <img loading=lazy> ), while the ones on the top are prioritized ( <img loading=eager> ). The same goes for all third-party embeds.

We replaced some dynamic parts with their static counterparts — eg while a note about an article saved for offline reading was appearing dynamically after the article was added to the service worker's cache, now it appears statically as we are, well, a bit optimistic and expect it to be happening in all modern browsers.

As of the moment of writing, we're preparing facades for code embeds and video embeds as well. Plus, all images that are offscreen will get decoding=async attribute, so the browser has a free reign over when and how it loads images offscreen, asynchronously and in parallel.

A screenshot of the main front page of smashing magazine being highlighted by the Diagnostics CSS tool for each image that does not have a width/height attribute
Diagnostics CSS in use: highlighting images that don't have width/height attributes, or are served in legacy formats. (ตัวอย่างขนาดใหญ่)

To ensure that our images always include width and height attributes, we've also modified Harry Roberts' snippet and Tim Kadlec's diagnostics CSS to highlight whenever an image isn't served properly. It's used in development and editing but obviously not in production.

One technique that we used frequently to track what exactly is happening as the page is being loaded, was slow-motion loading .

First, we've added a simple line of code to the diagnostics CSS, which provides a noticeable outline for all elements on the page.

* { outline: 3px solid red }
* { outline: 3px solid red } 
A screenshot of an article published on smashing magazine with red lines on the layout to help check the stability and rendering on the page
A quick trick to check the stability of the layout, by adding * { outline: 3px red } and observing the boxes as the browser is rendering the page. (ตัวอย่างขนาดใหญ่)

Then we record a video of the page loaded on a slow and fast connection. Then we rewatch the video by slowing down the playback and moving back and forward to identify where massive layout shifts happen.

Here's the recording of a page being loaded on a fast connection:

Recording for the loading of the page with an outline applied, to observe layout shifts.

And here's the recording of a recording being played to study what happens with the layout:

Auditing the layout shifts by rewatching a recording of the site loading in slow motion, watching out for height and width of content blocks, and layout shifts.

By auditing the layout shifts this way, we were able to quickly notice what's not quite right on the page, and where massive recalculation costs are happening. As you probably have noticed, adjusting the line-height and font-size on headings might go a long way to avoid large shifts.

With these simple changes alone, we were able to boost performance score by a whopping 25 Lighthouse points for the video-heaviest article, and gain a few points for code embeds.

Enhancing The Experience

We've tried to be quite strategic in pretty much everything from loading web fonts to serving critical CSS. However, we've done our best to use some of the new technologies that have become available last year.

We are planning on using AVIF by default to serve images on SmashingMag, but we aren't quite there yet, as many of our images are served from Cloudinary (which already has beta support for AVIF), but many are directly from our CDN yet we don't really have a logic in place just yet to generate AVIFs on the fly. That would need to be a manual process for now.

We're lazy rendering some of the offset components of the page with content-visibility: auto . For example, the footer, the comments section, as well as the panels way below the first 1000px height threshold, are all rendered later after the visible portion of each page has been rendered.

เราเคยลองใช้ link rel="prefetch" มาบ้างแล้ว หรือแม้แต่ link rel="prerender" (NoPush prefetch) บางส่วนของหน้าที่มีแนวโน้มว่าจะใช้สำหรับการนำทางต่อไปมาก เช่น การดึงข้อมูลเนื้อหาล่วงหน้าในครั้งแรก บทความในหน้าแรก (ยังอยู่ในการสนทนา).

นอกจากนี้ เรายัง โหลดรูปภาพของผู้เขียนไว้ล่วงหน้า เพื่อลดขนาดเนื้อหาที่ใหญ่ที่สุด และเนื้อหาหลักบางส่วนที่ใช้ในแต่ละหน้า เช่น รูปภาพแมวเต้น (สำหรับการนำทาง) และเงาที่ใช้สำหรับรูปภาพของผู้เขียนทั้งหมด อย่างไรก็ตาม ทั้งหมดจะถูกโหลดไว้ล่วงหน้าก็ต่อเมื่อผู้อ่านอยู่ในหน้าจอที่ใหญ่ขึ้น (>800px) แม้ว่าเราจะกำลังมองหาการใช้ Network Information API แทนเพื่อให้มีความแม่นยำมากขึ้น

เรายัง ลดขนาดของ CSS แบบเต็ม และไฟล์ CSS ที่สำคัญทั้งหมดด้วยการลบโค้ดเดิม การจัดองค์ประกอบใหม่จำนวนหนึ่ง และการนำเคล็ดลับ text-shadow ที่เราเคยใช้เพื่อให้ได้ขีดเส้นใต้ที่สมบูรณ์แบบด้วยการรวม text-decoration-skip -ink และ text-decoration-thickness (สุดท้าย!)

งานที่ต้องทำ

เราได้ใช้เวลาค่อนข้างมากในการแก้ไขการเปลี่ยนแปลงเล็กน้อยและที่สำคัญทั้งหมดบนไซต์ เราสังเกตเห็นการปรับปรุงที่ค่อนข้างสำคัญบนเดสก์ท็อปและการเพิ่มขึ้นอย่างเห็นได้ชัดในอุปกรณ์เคลื่อนที่ ในขณะที่เขียน บทความของเราให้คะแนนโดยเฉลี่ยระหว่าง 90 ถึง 100 คะแนน Lighthouse บนเดสก์ท็อป และประมาณ 65-80 คะแนนบนมือถือ

คะแนนประภาคารบนเดสก์ท็อปแสดงระหว่าง 90 ถึง 100
คะแนนประสิทธิภาพบนเดสก์ท็อป หน้าแรกได้รับการปรับให้เหมาะสมอย่างมากแล้ว (ตัวอย่างขนาดใหญ่)
คะแนนประภาคารบนมือถือแสดงระหว่าง 65 ถึง 80
บนมือถือ เราแทบจะไม่ได้คะแนน Lighthouse ที่สูงกว่า 85 เลย ปัญหาหลักยังคงเป็น Time to Interactive และ Total Blocking Time (ตัวอย่างขนาดใหญ่)

เหตุผลที่คะแนนต่ำบนมือถือนั้นเห็นได้ชัดว่า Time to Interactive ต่ำ และเวลาในการบล็อกโดยรวมที่ไม่ดีเนื่องจากการบูทของแอปและขนาดของไฟล์ CSS แบบเต็ม ยังคงมีงานต้องทำที่นั่น

สำหรับขั้นตอนต่อไป เรากำลังพิจารณา ที่จะลดขนาด CSS ลง และแยกย่อยออกเป็นโมดูลโดยเฉพาะ คล้ายกับ JavaScript การโหลด CSS บางส่วน (เช่น การชำระเงิน หรือ กระดานงาน หรือ หนังสือ/eBooks) เฉพาะเมื่อ จำเป็น

เรายังสำรวจตัวเลือกของการ ทดลองรวมกลุ่ม เพิ่มเติมบนอุปกรณ์เคลื่อนที่เพื่อลดผลกระทบด้านประสิทธิภาพของ app.js แม้ว่าจะดูเหมือนไม่ใช่เรื่องเล็กน้อยในขณะนี้ สุดท้าย เราจะมองหาทางเลือกอื่นแทนโซลูชันพร้อมท์คุกกี้ของเรา สร้างคอนเทนเนอร์ใหม่ด้วย CSS clamp() แทนที่เทคนิค padding-bottom Ratio ด้วยอัตราส่วน aspect-ratio และดูการแสดงรูปภาพให้ได้มากที่สุดใน AVIF

แค่นั้นแหละ!

หวังว่ากรณีศึกษาเล็กๆ น้อยๆ นี้จะเป็นประโยชน์กับคุณ และอาจมีหนึ่งหรือสองเทคนิคที่คุณอาจนำไปใช้กับโครงการของคุณได้ทันที ในท้ายที่สุด ประสิทธิภาพคือทั้งหมดที่เกี่ยวกับผลรวมของรายละเอียดเล็กๆ น้อยๆ ทั้งหมด ซึ่งเมื่อรวมกันแล้ว สร้างหรือทำลายประสบการณ์ของลูกค้าของคุณ

แม้ว่าเรามุ่งมั่นที่จะพัฒนาประสิทธิภาพให้ดียิ่งขึ้น แต่เรายังพยายามปรับปรุงการช่วยสำหรับการ เข้าถึง และเนื้อหาของเว็บไซต์ด้วย ดังนั้น หากคุณพบเห็นสิ่งที่ไม่ถูกต้องหรือสิ่งใดที่เราสามารถทำได้เพื่อปรับปรุง Smashing Magazine ให้ดียิ่งขึ้น โปรดแจ้งให้เราทราบในความคิดเห็นของบทความนี้

สุดท้ายนี้ หากคุณต้องการติดตามข่าวสาร ล่าสุด เกี่ยวกับบทความในลักษณะนี้ โปรดสมัครรับจดหมายข่าวทางอีเมลของเราสำหรับคำแนะนำเกี่ยวกับเว็บที่เป็นมิตร สารพัด เครื่องมือและบทความ และรายการ Smashing cats ที่คัดสรรมาตามฤดูกาล