เมื่อ Android Developer คนหนึ่งอยากเปลี่ยน portfolio website ใหม่
เล่าประสบการณ์การทำ website portfolio ใหม่ของเรากัน ว่าของเดิมก็มีอยู่แล้ว ทำไมต้องเปลี่ยนด้วยล่ะ?
บล็อกนี้เราเล่าประสบการณ์ในการทำเล็ก ๆ น้อย ๆ ว่าทำไมต้องเปลี่ยน แล้วเปลี่ยนมาเป็นแบบไหน เผื่อเป็นไอเดียในการเอาไปทำเนอะ หลายสิ่งใช้ Firebase ทำน้า
แล้วก็นำความรู้ที่ได้จากคอร์ส JavaScript for Web Development มาใช้ด้วย ถึงแม้บางครั้งที่ติดปัญหา หรืออยากได้อะไรตามในหัว จะ search หา Google ก็ตาม ซึ่งตามตรงเลย ทางนี้เป็น Android Developer ที่นาน ๆ มาจับอะไรพวกนี้เล่นที เอาจริง ๆ มันก็สนุกดีนะ (ซึ่งแน่นอน สนุกกว่างานไปอี๊กกกก)
ทำไมต้องเปลี่ยน portfolio website?
ของเดิมเราทำไว้นานมาก ๆ แล้ว ประมาณ 6 - 7 ปีมั้ง ตอนนั้นก็หานานมากว่าจะทำยังไงดี เลยใช้ Bootstrap ที่ง่าย และเร็ว และเอาเว็บนี้ขึ้น Firebase Hosting ซึ่งมันก็เป็น static website แหละ
พอเวลานานไปในการอัพเดตอะไรบาง เราก็เพิ่มบรรทัด เปลี่ยนรายละเอียด เพิ่มรูป deploy ขึ้น push code บางทีเราก็ไม่ค่อยมีเวลาอัพเดตมันเท่าไหร่ โดยเฉพาะตัว activity ต่าง ๆ ที่เราทำ นาน ๆ อัพที Github Action ก็พังอีก ต้องมานั่ง deploy มือ นาน ๆ เข้าก็ไม่ค่อยมีเวลาอัพก็เลยเป็น version ที่ข้อมูลยังไม่ได้อัพเดตล่าสุด
อีกทั้งก็มีหลาย ๆ framework ต่าง ๆ ที่น่าสนใจ ก็เลยลองหาวิธีว่าทำยังไงได้บ้าง และจบที่ HTML + CSS + JavaScript ตาม default เลยไม่ได้ใช้ framework ใด ๆ (ตอนแรกจะใช้ Svelte จริง ๆ น้า 🥺 แต่ลืมแล้วว่าต้องใช้ยังไง แล้ว deploy ไป Firebase Hosting ยังไงอีก)
ตัวข้อมูลพวก site project และ activity จริง ๆ มีทำบางส่วนใน Firebase Cloud Firestore แล้ว เลยอัพเดตของนิด ๆ หน่อย ๆ แล้วเอามาใช้ต่อในเว็บ
Concept และ Design
ในหัวคือมันต้องเป็น one-page portfolio website ลดภาระในการทำหน้าเว็บเพิ่ม เนื่องจากมีบางโปรเจกต์ใน portfolio เรา ที่มันเป็น project ที่ผ่านมานานแล้ว และใส่รายละเอียดไว้อีกหน้าเว็บนึง ที่ต้อง copy หน้าหลักมาทำใหม่ เลยลดอะไรตรงนี้ลง จนมาเป็นกดปุ่มแล้วเปิดหน้า dialog แทน
และมีความเป็น 8-bit pixel art เพราะอยากทำอะไรที่เป็นตัวของเราเองที่สุด และบวกกับความสาย geek ด้วย
reference ประมาณนี้ คือ เจอ cover เต็มหน้าจอ แล้วค่อยเลื่อนลงมาเจอรายละเอียดของเราเรื่อย ๆ
เลื่อนลงมาเจอรายละเอียด about me ของเรา ซึ่งของเดิมเป็นเว็บแยก แล้วก็ 5 blogs ล่าสุด แล้วก็แอพที่ทำที่เป็น side project ต่าง ๆ พวก activity ต่าง ๆ ที่เราทำ จบด้วยประสบการณ์การทำงาน แฮร่
และเราลองวาดคร่าว ๆ ใน Notability ว่าหน้าตาประมาณไหน มีข้อมูลอะไรที่ต้องเอามาแสดงบ้าง
- ตัว cover แสดงเต็มหน้าจอ พื้นหลังเป็นดวงดาว เพื่อความเป็นอวกาศ บอกชื่อเราคร่าว ๆ มีปุ่มไปยังเว็บไซต์นี้ ไปยัง bio link แล้วก็ site project แนว content อย่าง mikkicoding และ cryptominseo ตอนเลื่อนจะมี animation ที่จรวดจากโลกไปดวงจันทร์ ซึ่งทำจริงกากกว่าที่คิดไว้ 555
- ลงมาเป็น about me พร้อม link social ต่าง ๆ
- blog ล่าสุด 5 อัน
- แอพที่ publish บน Play Store ซึ่งบางอันตอนนี้ยังไม่ได้เอากลับขึ้นไป กับบางอันปล่อยไปแล้ว
- โปรเจกต์ Android ที่เคยทำ
- โปรเจกต์ฝั่ง website รวมถึงพวก chatbot
- พวก activity ต่าง ๆ ที่เราทำ
- พวกงานกับการศึกษาไว้ล่างสุดดีกว่า
- จบด้วย footer
ส่วนใหญ่ก็เป็น item แบบต่าง ๆ คล้ายกับเว็บเดิม ปรับเปลี่ยนบางอย่าง แต่ logo ใหม่ ยังไม่ได้ทำ
เริ่มทำเว็บกัน
เริ่มจากที่เราสร้างโปรเจกต์ที่ Glitch โดยสร้างเป็นโปรเจกต์เว็บ และลบออกจนเหลือแค่ส่วน html และเพิ่มพวก CSS และ JavaScript เพื่อให้มันเป็น static website เนอะ เริ่มจากบนไปล่างตามที่ design ไว้แหละ เพื่อลอง POC ดู ข้อดีและข้อเสีย คือหน้าเว็บเปลี่ยนทุกครั้งที่มีการอัพเดต และถ้าเรียก API จริง ก็เรียกเยอะแหละ
มีส่วนนึงที่เพิ่มมาทีหลัง คือ สามารถเปิดเพลงได้ด้วย พอดีเห็นเว็บของ Zentry ทำแล้วอยากทำบ้าง เพลงเลยไป generate จาก Suno มาใส่ ตอนแรกจะใส่เป็น Lottie โทนคล้าย ๆ กัน ไป ๆ มา ๆ ไม่ใช่ Lottie เพราะว่ามันไม่สามารถทำแบบ dynamic website ที่ต้อง import library ข้างนอกเข้ามาแล้วทำงานได้ เลยเปลี่ยนเป็นตัว Icon ที่มีความ pixel แทน
สุดท้าย responsive ที่ใช้ ChatGPT ช่วยให้มันออกมาสวยงามไม่ล้นขอบ ซึ่งปรับกันที่ CSS เป็นหลักแหละ
Element of Design
เราหา element หรือ resource ต่าง ๆ จากการทำเว็บนี้จากไหน ยังไง มีหลายส่วนมาก ๆ เลย
CSS
ใช้อันนี้ Nostalgic CSS พอใช้แล้วเราได้ cursor แนว Windows สมัยก่อนมา พร้อม container อื่น ๆ
เกี่ยวกับการใช้งานอื่น ๆ ดูได้ที่บล็อกนี้เลย
Font
แน่นอนเมื่อใช้ Nostalgic CSS ก็ต้องใช้ Press Start 2P
และเราหา font ที่มีความ pixel อื่น ๆ ก็เลยหยิบ Handjet มาใช้
เมื่อเราค้นหา Google Fonts ที่เป็น font ภาษาไทยที่มีความ Pixel แต่หาไม่เจอ ก็เลยใช้ Tlwg Typewriter ที่ดูจะคล้ายที่สุดแล้ว พอดีเจอเว็บ Thai Web Font โดยบังเอิญ
ซึ่งเว็บนี้เขามีฟอนต์ไทยมากกว่า Google Fonts ด้วยนะ เขามีฟอนต์ไทยทั้งหมด 50 ฟอนต์ด้วยกัน (บน Google Fonts ถ้า filter ภาษาไทยมีทั้งหมด 32 ฟอนต์)
ก่อนอื่นเลือกฟอนต์กันก่อน การใช้งานก็ง่าย ๆ แปะตัว <link>
ในส่วน <head>
บน HTML แล้วก็เอาไปใช้ต่อบน CSS ได้เลย หรือจะ import url เข้ามาใน CSS หรือ ใช้ @import
มาก็ได้ แล้วก่อนเอามาใช้อ่านสัญญาอนุญาตก็ดีนะ
Image
หาอันที่ใช้ฟรีเชิงพาณิชย์ตามเว็บต่าง ๆ แล้วยังไม่โดนใจ ทำเองก็ไม่ไหว ไป ๆ มา ๆ เจอใน Canva เลยเอามาใช้ประกอบตัวเว็บของเรา
Icon
ตรง activity เราก็จะแปะคลิปที่เราพูด สไลด์ที่ปิ้งให้ทุกคนอ่าน หรืออะไรใด ๆ เลยเอาเป็นภาพอะไรก็ได้ที่เป็น icon ที่คนเข้าใจว่าอันนี้เป็นลิ้ง Github นะ เลยมาเจออันนี้ ใช้ฟรีแบบสีเดียว ชอบอันไหน copy มาใช้ จะได้ svg tag ใส่ใน HTML ของเราได้
ทำ API จาก Firebase Cloud Firestore ด้วย Firebase Cloud Function ยังไง?
ตัวข้อมูลที่เป็นพวก site project และ activity ก็จะใช้ข้อมูลจาก Firebase Cloud Firestore ตรง ๆ แต่ติดความ static website ก็เลยลองทำเป็น API เพื่อเรียกใช้ในหน้าเว็บแทนแล้วกัน แบบไม่ต้องใช้ API Key ซึ่งจริง ๆ ก็เคยเขียนเกี่ยวกับเรื่องนี้แล้วแหละ
แต่เพิ่มเติม node version ที่เหนือกว่า โดยการไปทำที่ Github Codespace ก่อนอื่นเลยไป install Firebase CLI และ login Firebase ให้เรียบร้อย จากนั้นก็ใช้งานเหมือนเครื่องปกติเลย สาเหตุที่ใช้ Github Codespace เพราะติดเรื่อง update node นี่แหละ เลยไปใช้ในนั้นน่าจะสะดวกกว่า
ก่อนหน้านี้เราทำ Firebase Cloud Firestore ไว้ประมาณนี้ ซึ่งทำ API ของ 4 collection นี้เพิ่มเติมจากของเดิมที่เป็น version 1 เป็น version 2
เริ่มจากเรียก library มาใช้งานกันก่อน จากนั้นก็เรียกใช้งานตัว Firebase Admin
const {logger} = require("firebase-functions");
const {onRequest} = require("firebase-functions/v2/https");
// The Firebase Admin SDK to access Firestore.
const {initializeApp} = require("firebase-admin/app");
const {getFirestore} = require("firebase-admin/firestore");
initializeApp();
ตามมาด้วย set regions ไปที่ใกล้ที่สุดเท่าที่เป็นไปได้
// set regions
const { setGlobalOptions } = require("firebase-functions/v2");
setGlobalOptions({ region: 'asia-east2' });
แล้วก็เริ่มทำ API จาก Firebase Cloud Firestore กัน โดยประกาศตัวแปรเหล่านี้กันก่อน เอ้ออเราใช้ Express.js ในการทำ API เหล่านี้
// Get firestore to create API
const firestore = getFirestore();
const express = require('express');
const appPortfolio = express();
const cors = require("cors");
appPortfolio.use(cors());
ภายใต้ตัว appPortfolio
จะมีอยู่ด้วยกัน 4 เส้นย่อย คือ activity
, android-apps
, android-projects
, และ web-apps
ซึ่งโค้ดก็ลอกกันมาเล้ยยยยย
ในที่นี้หยิบเส้น activity
ประกอบการอธิบาย
ก่อนอื่นเราสร้างเส้น API ที่เราเรียกข้อมูลขึ้นมา เป็น GET เนอะ และดึงข้อมูลจาก Firebase Cloud Firestore ที่เป็น collection ที่ชื่อว่า activity
โดยเรา sort จาก time ให้เรียงจากล่าสุดลงมา จากนั้นหยิบตัว snapshot
ที่ได้มา check ถ้าไม่มีให้ return 404 กลับไป ถ้ามีก็ push ตัว data ของแต่ละ document เข้าไปที่ response แล้วบอกว่า contentType
เราเป็น json นะ
appPortfolio.get('/activites', function (request, response) {
var data = {};
const result = [];
firestore.collection('activity').orderBy("time", "desc").get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return response.status(404).send();
}
snapshot.forEach(doc => {
console.log(doc.data());
result.push(doc.data());
});
data.items = result;
response.contentType('application/json');
response.send(data);
return response;
})
.catch((err) => {
console.log('Error getting documents', err);
})
})
ตอนจบ export ตัว appPortfolio
ออกมาเป็น module ที่ชื่อว่า portfolio
แล้วเอามาใช้ต่อ เป็นชื่อ function
exports.portfolio = onRequest(appPortfolio);
ต่อมาสั่ง deploy ซึ่งเราจะ deploy หมดทุกเส้นเลยก็ได้ หรือแค่เฉพาะ function ก็ได้
ก่อนอื่น เข้าไปที่ folder functions ก่อน
cd functions
ถ้า deploy หมด ใช้คำสั่ง
npm run deploy
ถ้าแค่เฉพาะ function ในที่นี้คือ portfolio ใช้คำสั่ง
firebase deploy --only functions:portfolio
ผลที่ได้
เรื่อง cors เรา allow หมดไปก่อน เดี๋ยวเปลี่ยนแค่ allow บาง url ล่ะ
Custom Domain Name ผ่าน Firebase Hosting ยังไง
เนื่องจากเราอยากให้ตัว portfolio website อยู่ภายใต้ domain เดียวกันกับ website นี้ เลยต้องทำ subdomain เป็น portfolio จากเดิมที่เป็น .web.app
ไปที่ Firebase console กันก่อนเลย แล้วไปที่ Hosting แล้วจิ้ม Add custom domain ได้เลย ต่อมากรอก custom domain name ที่ต้องการ จากนั้นกด Continue แล้วไปเพิ่ม record ตามนี้ที่ DNS
ของเราใช้ CloudFlare ไปที่ domain website เรา แล้วไปที่ DNS -> Records จากนั้นกดปุ่ม Add record แล้วกรอกตามใน Firebase ได้เลย
จากนั้นรอไปประมาณ 1 คืน เพื่อให้มันเชื่อมกันหวาน ๆ (เพราะเปิดเลยมันจะยังเปิดไม่ได้อยู่) จากนั้นเราใช้ custom domain ได้ล่ะนะ
เก็บ key ที่ใช้สำหรับ Firebase Hosting ได้ไหม ยังไงดี
ในส่วนที่แสดง 5 blogs ล่าสุดนั้น เราจะต้อง generate API key มาใหม่จาก Ghost CMS แล้วเอามาใช้แบบตรง ๆ แบบไม่ปลอดภัยดูก่อน คือ set value ในโค้ดตรง ๆ เลย
ถ้าเก็บ key ใน Glitch ที่เก็บ key ที่ไฟล์ .env
ส่วน Github Codespaces ที่ไปเก็บ key ในโปรเจกต์ โดยไปที่ repo ของเราที่เปิดใน Codespaces ตามมาด้วย Settings -> Secrets and variables -> Codespaces จากนั้นกดปุ่ม New repository secret ต่อมากรอกชื่อ key พร้อม value ให้เรียบร้อย แล้วกดปุ่ม Add secret ก็จะได้ key ที่เราสร้างมาแล้ว สุดท้ายกลับไปที่ Codespaces มันจะมี popup เล็ก ๆ ด้านล่าง ให้กด Reload to apply เป็นอันจบ
ถ้าอยาก check ไปที่ terminal แล้วพิมพ์ command ประมาณนี้ เราก็จะได้ value ของ key นั้นออกมาล่ะ
echo $KEY_NAME
ตอนนี้ยังไม่สามารถใช้ท่า process.env
ได้ จึง hide element นี้ไปก่อน คิดว่าน่าจะใช้ได้เมื่อมันสามารถ install node package แล้วมีไฟล์ package.json ออกมานั่นแหละ
ถ้าหาวิธีได้จะมาอัพเดตก็แล้วกัน สำหรับ static website
ทดสอบก่อน deploy จริงที่ Codespaces
แน่นอนว่าเราต้องย้ายโค้ดที่ทำใน Glitch ที่เราลอง POC มาที่ Github Codespaces เพื่อทดลอง deploy เพื่อลองใช้งาน และตรวจสอบ performance ใด ๆ ที่ PageSpeed Insights
การ deploy เพื่อทดสอบ ก่อนอื่นไปที่ folder public แล้วบิ้ว channel ของตัว Firebase Hosting ออกมา
cd public
// preview
firebase hosting:channel:deploy CHANNEL_ID
ผลที่ได้ เราเอาตัว channel url มาเปิดเทส แล้วก็ใช้เทสใน PageSpeed Insights ได้เลย อายุการใช้งาน default คือ 1 อาทิตย์โดยประมาณ
แล้วเราสามารถ set วันหมดอายุพวกนี้ไวขึ้นหรือช้าลงได้ไหม? ไปที่หน้า Firebase console ของ Hosting สิ เราจะเห็น preview channels กดตรง Until แล้วก็เปลี่ยน expiration ได้เลย
ซึ่งจริง ๆ สามารถ set expires duration ได้นะ ตามนี้เลย
firebase hosting:channel:deploy new-awesome-feature --expires 7d
มีคนสงสัย มันกด preview ดูไม่ได้หรอ ใน Github Codespaces อ่ะ ทางนี้ใช้ plug-in แล้วแต่เปิดไม่ได้ หน้ามันเพี้ยน เลยก็แบบนี้แหละ
ส่วนผลการตรวจสอบกับ PageSpeed Insights ติด SEO ยังแก้ไม่ได้เลยแหะ งงมาก หรือ deploy จริงถึงจะหาย หรือยางง้าย
ถ้าลอง deploy จริง SEO จะเต็ม 100 หรือไม่ มาลอง deploy กันเถอะ
cd public
// public deploy
firebase deploy --only hosting
พอ deploy ปุ๊ปยังเป็นหน้าเดิมอยู่ อาจจะลองใช้คำสั่งนี้เพื่อ clone ตัว preview มาอัน live คืออันที่ทุกคนเข้าถึงได้
firebase hosting:clone PROJECT_ID:CHANNEL_ID PROJECT_ID:live
แล้วก็ลองเปิด Incognito เพื่อ recheck อีกที ถ้าทำแนว PWA อย่าลืม clear cache ออกก่อน ไปที่ dev tool มา tab Application แล้วกด Clear site data โล้ดดด (แต่จริง ๆ อยากให้ลองอันนี้ก่อน แล้วค่อยลอง deploy ใหม่อีกทีนุง)
เท่านี้ก็ได้แล้ว
ไปดู PageSpeed Insights พบว่า SEO เต็ม 100 ล่ะ ตรงกับที่คิดไว้ เพราะ preview คนที่เห็นคือคนที่เรากด share link ไปให้ ฟีลเหมือน share Google Drive อ่ะ
แล้วได้ความรู้อะไรใหม่ ๆ เพิ่มเติมบ้างไหมนะ?
- การ set element จะมีแบบ id ที่ชื่อมันจะไม่ซํ้า กับ class ที่สามารถ appiled design นี้ไปที่อื่น ๆ ได้ โดย CSS ใส่ # หน้าชื่อ id และใส่ . หน้าชื่อ class ประมาณนี้
#id
.class
- ใช้
position: absolute
ใน CSS กับ div หรือ elements ที่ต้องการให้วางซ้อนทับกัน
- มีไม่ผ่านเรื่อง Accessibility ที่ tag a ตรง link name ใส่
aria-label
ไปด้วย
- การติด GA4 จริง ๆ มันก็โค้ดเดิมป่ะ แค่ ID เปลี่ยน
- ของเรามีติดเรื่อง SEO ไม่เต็ม 100% แล้วก็งง ๆ ว่าทำยังไงดี ก็เลยใส่ meta tag ประมาณนี้ เพื่อให้มันทำ SEO ได้
<meta name="robots" content="index,follow"/>
มีอะไรต้องทำหรือปรับแก้เพิ่มบ้าง
- ปรับการ scroll แล้วตัวจรวดไปแบบวงกลม และปรับความเร็วของจอใหญ่
- carousel สำหรับรูปอื่น ๆ ใน activity ที่เราทำ บางงานมีบางงานไม่มี
- ถ้าพวก detail พวก wording หรือรูป อันนี้ไปแก้ที่ Cloud Firestore เลย ไม่ต้องไปแก้แล้ว deploy website ใหม่
- ปรับส่วน PWA แล้วก็พวก meta tag
สุดท้ายนี้ ไม่รู้จะพูดอะไรต่อล่ะ 555 เอาเป็นว่า แปะ Github กับตัว Portfolio Website ไว้ก็แล้วกันเนอะเผื่ออยากไปดูกัน
ถ้ามีคำแนะนำเพิ่มเติม หรือมีอะไรแนะนำก็มาบอกกันได้น้า
ติดตามข่าวสารตามช่องทางต่าง ๆ และทุกช่องทางโดเนทกันไว้ที่นี่เลย แนะนำให้ใช้ tipme เน้อ ผ่าน promptpay ได้เต็มไม่หักจ้า
ติดตามข่าวสารแบบไว ๆ มาที่ Twitter เลย บางอย่างไม่มีในบล็อก และหน้าเพจนะ