เมื่อผม ต้องเปลี่ยน เว็บ Wordpress แสนช้า ให้กลายเป็น Nuxt.js และทำให้มันโหลดเร็วกว่า 1 วินาที

ก่อนอื่นต้องขอโทษด้วยที่ผมไม่สามารถเอ่ยชื่อเว็บและให้เห็น screenshot ได้ บอกได้แต่ว่า เว็บนี้เป็นเว็บข่าวที่ใหญ่พอสมควร และมี traffic เยอะมากๆเว็บนึงของไทยเลยก็ว่าได้

นี่คือ stat จาก lighthouse ล่าสุด

บังเอิญได้ทำ

คือเรื่องมันมีอยู่ว่า เพื่อนผมที่ดูแลเว็บนี้อยู่มันบ่นว่า เว็บที่เป็น Wordpress เดิม มันช้าอยากจะเขียนใหม่ มันเลยให้ผมสอนใช้งาน Nuxt.js เพราะอยากจะย้ายไปใช้ Nuxt.js ซึ่งผมก็ได้สอนจนจะลืมไปแล้วว่าสอนมัน จนวันนึงมันมาบอกว่าเออ ย้ายมาเป็น Nuxt.js แล้วนะ ใช้วิธีเชื่อมไปยัง wp-json เอา แล้วก็อะไรที่ API ไม่มีก็ hook API เดิม แต่มันก็ยังติดปัญหาเรื่องการใช้าน Vue.js อยู่พอสมควร ก็เลยมาให้ผมช่วยดูหน่อยว่าพอจะแก้อะไรได้มั้ย พอผมเข้าไปดูก็แก้จนมันรันได้ตามปกติ แต่พอลองเล่นดูก็ยังรู้สึกว่ามันยังไม่สะใจมันช้า ก็เลยบอกมันว่า เอามั้ย จะอัดให้เร็วที่สุดที่จะเร็วได้เลย เอาแบบ เป็น “เว็บข่าวที่เร็วสุดในไทยไปเลย”

ย้ายมา Nuxt.js ไม่ได้ง่ายเลย

เท่าที่ฟังไอเดีย มันดูไม่น่ายากเลยเพราะก็แค่สร้าง UI ใหม่ขึ้นมาแล้วก็เชื่อกับ API ของ Wordpress เข้าไป ซึ่งจริงๆแล้วก็ไม่น่าจะยากเลย

  • มาเล่าเรื่อง Nuxt.js กันก่อน คือ ต้องเข้าใจก่อนว่า Nuxt.js เนี่ย เป็น server side randering ของ Vue.js ซึ่งมันมี พฤติกรรมนึงที่ต้องเข้าใจก่อนว่า หน้าเว็บที่เราเห็นเนี่ย จะ render มาจาก server แค่ ครั้งแรกเท่านั้น แต่พอเรา navigate ไปเรื่อยๆบนเว็บ มันจะ render บน client ซึ่ง บาง script เนี่ย มันจะไม่ถูก re-initialize และมันจะใช้งานไม่ได้โดยเฉพาะพวก Ads
  • พวก Script ads เนี่ย เพื่อนผม copy มาจากของเก่าเลย ซึ่งจาก พฤติกรรมข้างบน ทำให้ ads มัน error บ่อยมากเนื่องจาก มันไม่ได้ re-initialize และทำให้มันเห็นว่าเรา define ads ตัวนี้ไปแล้ว define ซ้ำไม่ได้ทำให้ผมต้องเขียน middleware มา destroy ads ด้วย method window.googletag.destroySlots() ก่อน
  • Theme ถอดมาจากของเก่าหมดเลย เพื่อนผมถึกมากๆ แทนที่จะ design ใหม่ มันเลือกที่จะ copy โครงเก่ามาแล้ว implement ให้เข้ากับ Nuxt.js เลย ซึ่งวิธีนี้นรกมากๆเพราะว่า โคตรงสร้างทั้งหมดถูกทำไว้แล้วการถอดทุกส่วนมาเป็น component คืองานช้างโคตรๆ นั้นถ้าจะทำ design ใหม่สบายใจกว่า
  • Router ของ nuxt มันไม่ง่ายเหมือน Wordpress เลย เพราะว่า Wordpress มันทำ router มาดีมากๆ รวมถึง nuxt เองก็ต้องวางไฟล์ตาม path ซึ่งเพื่อนผมก็ต้องไป extend router ออกมาเยอะมากๆ เพื่อให้มัน support การทำ route แปลกๆ ผมก็ไม่เข้าใจเลยว่าทำไมถึงชอบกันจังไอ้ pattern ที่ให้วาง path ตาม folder เนี่ย
  • Google analytics ติดธรรมดาไม่ได้ ต้องไป copy ตัวอย่างจาก Nuxt.js มาใช้งานซึ่งโชคดีที่ไม่วุ่นวายมากเพราะว่า nuxt เตรียมไว้ให้อยู่แล้ว https://nuxtjs.org/faq/google-analytics/
  • Wordpress API ส่งของออกมาไม่ครบเท่าที่เราอยากใช้ คือ ถ้าให้เคยใช้ Wordpress API จะเจอเลยว่ามันปล่อยข้อมูลออกมาน้อยมากๆ ทำให้เพื่อนผมต้อง Hook เข้าไป เปลี่ยน format API แทบทั้งหมดเลย และที่ทำสัญอย่าใช้ _embedded เป็นอันขาด
  • ลืม plugin wordpress ไปเลย เพราะเนื่องจากเคสนี้เราใช้ Wordpress เป็น Headless CMS Plugin ที่เคยใช้ทั้งหมดเลยใช้ไม่ได้หมดเลย ต้องเขียนเองหมด และด้วยความที่ไม่มี jQuery ก็ทิ้งพวก lib เก่าๆไปได้เลยไม่ต้องแคร์ ที่ใช้จริงๆเป็นพวก content type และ custom fields ด้วยวิธีนี้ API จะทำงานเร็วกว่าเดิมขึ้นไปอีก
  • Development flow รวมถึง Tech stack เปลี่ยนทั้งหมด เพราะว่า Nuxt.js เปลี่ยน Node.js ทำให้ server ที่จะเอาไปใช้ต้องรองรับ Node.js รวมถึงทุกครั้งที่แก้ไฟล์ก็ต้องรอ build ใหม่ เสมอ และพอ application ใหญ่ขึ้นเรื่อยๆ ก็จะ build ช้าลงเรื่อยๆเช่นกัน เพราะฉะนั้นใครจะเปลี่ยนคิดเยอะๆคิดดีๆก่อน
  • vue-meta บน server กับ client ทำงานไม่เหมือนกัน ทำให้ต้องมานั่งแก้บั๊กการ display ของมันอยู่นานพอสมควรเพราะว่า behavior ของ server และ client ทำงานไม่เหมือนกัน ถ้าสมมติว่าจะใช้ ลองสังเกตุดูดีๆว่ามัน แสดงผลอย่างที่อยาก
  • ถ้าคิดว่าย้ายมาแล้วเร็วหยุดคิดก่อน คือ การย้ายมา nuxt.js แค่ “เพิ่มโอกาสที่จะทำให้มันเร็ว” มากขึ้นเท่านั้น ไม่ได้ทำให้เร็วขึ้นเพราะฉะนั้นต้องอาศัยความเข้าใจเยอะมากๆเพื่อให้มันเร็วขึ้น
  • ใช้ Nginx ช่วยย้ายมาเฉพาะบาง path เนื่องจากเว็บเก่ามีหลายหน้ามากๆและบางหน้ายังทำไม่เสร็จทำให้เพื่อนต้องตัดสินใจทำแค่บางหน้าและ rewrite เฉพาะหน้าที่เสร็จแล้วมาที่ nuxt.js ส่วนของเก่าก็ให้วิ่งไปที่ของเก่า
  • คนบ้าเท่านั้นที่ใช้ plugin:vue/recommended คือถ้าคนเขียน vue จะรู้ดีว่า vue มี style giulde อยู่หลายระดับ recommended คือ โหดสุด เพื่อนผมซึ่งไม่เคยเขียน vue.js มาก่อนกลัวว่า code ออกมาจะไม่สวย เลยเปิด eslint ระดับนี้เอาไว้ซึ่งกว่าจะเขียน code ผ่านไปทีละส่วนต้องมาใช้เวลาเกือบครึ่งไปกับการแก้ style ของ code แต่สุดท้ายเราก็ตัดสินใจยัด code เข้า deepscan ไปด้วยเพื่อให้มันใจว่า code ที่เขียนมีคุณภาพจริงๆ

มาเริ่ม optimize กัน

  • โจทย์มีหลักๆคือ แก้มากๆไม่ได้เพราะทำเสร็จไปเกือบหมดแล้ว รื้อบางอย่างทิ้งไม่ได้ เพราะฉะนั้นพวก component ต่างๆต้องปล่อยไว้
  • เรียกว่าโชคดีก็ได้เพราะว่า Nuxt.js ใช้ Webpack ซึ่งหลายๆอย่าง Webpack จัดการไปให้แล้ว อย่างพวก preload หรือ code optimization
  • สิ่งแรกที่ต้องมาทำคือ จับยัด https://developers.google.com/speed/pagespeed/insights/ ก่อนเลยว่า มีอะไรบ้างที่ช้าซึ่งตอนแรกคะแนนออกมา 4 เต็ม 100 (บน mobile) ผมนี่เข่าแทบทรุดไปเลย
  • เว็บนี้ใช้ webfont เยอะมากๆ ซึ่งจะทำให้เร็วเนี่ยได้ยากมากๆ ผมก็เลยแก้ได้เพียงแค่ไปใส่ font-display:swap เข้าไปตอนโหลด font และด้วยกลุ่มลูกค้าของเว็บนี้ไม่ได้เน้น IE11 ผมเลยเปลี่ยนจากการโหลด font ทุกนามสกุลมาเป็น woff2แทน
ไม่แคร์ IE ก็ใช้ แค่ WOFF2 ก็ได้
  • Off screen component เยอะมากๆ คือ ถ้าให้พูดง่ายๆให้เห็นภาพคือ เว็บจ้องโหลดสิ่งที่ user ยังมองไม่เห็นไปก่อนเยอะมากๆ ลองจินตาการหน้าจอมือถือดูครับ user เห็นเว็บแค่หน้าจอมือถือ สูง 730px แต่เว็บทั้งเว็บจริงๆแล้ว ยาว 9600px ซึ่งส่วนที่เว็บโหลด ไป ยาวไปมากกว่านั้น 10 กว่าเท่าได้ สุดท้ายผมก็เลย สร้าง lazyload component ขึ้นมา ให้มันซ่อน component ที่ยังไม่ถูกเห็นไว้ก่อน ถ้า user เลื่อนลงมาค่อย display ออกมา และที่ไม่ทำเฉพาะรูปส่วนนึงก็เพราะว่า รูปเป็นแค่ส่วนเดียวที่ทำให้ช้าโดยให้มันโหลดติ่งข้างล่างไว้ เผื่อ 200px user จะได้ไม่รู้สึกว่ามันกระตุก และเพื่อให้มันสมบูรณ์ผมก็เลยลง polyfill ของ intersection-observer เข้าไปด้วยไม่งั้น safari จะใช้งานไม่ได้
  • Bootstrap คือ ตัวน่ารำคาญ ด้วยความที่เพื่อนต้องการจะถอดของเก่ามาให้เยอะที่สุดก็เลยไปเอา theme เก่าที่ทำบน Bootstrap มาเลย ซึ่งปัญหาก็เกิดเลยครับ เพราะว่า bootstrap เล่นกินขนาด bundle ไป เกินครึ่ง และสุดท้ายก็ถอดไม่ได้เพราะว่า หลายๆ component ใช้ bootstrap ผมก็เลยทำได้แต่ ถอด wrapper ที่เป็น vue component ออก เพื่อลด exec time
  • รูปนี่เรื่องใหญ่ รูปไม่ได้สร้างปัญหาให้เราแค่โหลดช้า แต่ด้วยการใช้ภาพไซต์ต่างกันเยอะมากๆ ทำให้เราต้องสั่งให้ wordpress convert รูปออกมาหลายๆไซต์เพื่อใช้ในแต่ละจุดที่ต่างกัน เพราะตอนที่เข้าไปทำตอนแรก ผมไปเจอหลายๆจุที่ไม่ได้แก้อย่างเช่นใช้รูปจริงขนาด 950x200 แต่ displayจริง แค่ 170x100 กว่าๆ ซึ่งตรงนี้ก็มาไล่แก้ไปทั้งเว็บเลยทีเดียว
  • Preconnect resource เนื่องจาก เว็บนี้เรียก Ads กับ พวก tracking เยอะมากๆทำให้ผมต้อง preconnect ไว้ก่อนเพื่อให้มันโหลดเร็วขึ้น และกว่าจะไล่ได้ก็หมดไปหลายชั่วโมง
ยังมีอีกเยอะ นี่แค่นิดเดียว
  • ต้องเปิด modern mode ต้องเรียกว่าโชคดีที่ Nuxt.js มีสิ่งที่เรียกว่า modern mode ซึ่งมันคือ การสร้างไฟล์ build สอง version แยกกัน สำหรับ browser เก่าๆ และ ใหม่ๆ ทำให้ browser ใหม่ๆที่ support feature มากกว่า เร็วกว่า อ่านเรื่อง modern mode ได้ที่นี่ครับ https://nuxtjs.org/api/configuration-modern/
  • เกลียด carousel อันนี้เป็นปัญหาใหญ่มากๆของเว็บนี้เนื่องจากเราเจอว่า carousel ไม่ได้ทำ lazy load เอาไว้และด้วยวิธีการเขียน ทำให้ใช้ Lazyload ไม่ได้ สุดท้ายก็ต้องมานั่งทำเองเพื่อไม่ให้ carousel โหลดรูปที่ยังไม่ต้องแสดง
  • Balance server side และ Client side เนื่องจากมีการcall API เยอะมากๆให้หน้าเว็บนี้สิ่งที่ต้องนั่งคิดเยอะมากๆคือจะเอา API ไหนไป call ฝั่ง server และฝั่ง client บ้าง เพราะถ้า call ฝั่ง server หมด response time จะสูงมากๆ แต่ถ้า call ฝั่ง client เยอะ CPU ฝั่ง user ก็จะเต็ม

จริงๆนอกจากที่เขียนมาเว็บไซต์นี้ยังใช้เทคนิคพื้นฐานอย่าง CDN , HTTP/2 เพื่อช่วยลด โหลดของฝั่ง server ด้วย หากใครจะลองใช้ Nuxt.js ก็ลองอย่าลืมดูนะครับว่าต้องทำอะไรบ้างเพื่อทำให้เว็บของคุณเร็วขึ้น เพราะไม่ฉะนั้นแล้ว ใช้ Nuxt.js ก็ไม่ได้ช่วยอะไรเลย หรือถ้าใครสนใจเรื่องการ optimize เว็บไซต์ก็ทักแชทมาคุยกันได้นะครับ