এই পর্বে যা শিখবেন
- একটি পাতা লোডের পুরো পথ — HTML থেকে CSS, ফন্ট, ছবি আর JavaScript — কোনটি কাকে আটকায়
- LCP, INP আর CLS কী মাপে, কেন খারাপ হয়, আর HTML-CSS দিয়ে কীভাবে ঠিক করবেন
- ল্যাবের মাপ আর আসল ব্যবহারকারীর মাপের পার্থক্য; Lighthouse, PageSpeed Insights আর Performance প্যানেল পড়া
- CSS, ফন্ট আর ছবি লোডের পেশাদার কৌশল — critical CSS, preload, subset, compression, caching
- ব্রাউজারের আঁকার খরচ কমানো —
contain,content-visibility, দামি property, layout thrashing আর third-party script-এর হিসাব
১ · গতি কেন জরুরি — ধীর পাতা মানে হারানো পাঠক
ধরুন রাজশাহীর এক কলেজছাত্রী বাসে বসে একটি অনলাইন দোকানের লিংকে চাপ দিলেন। ফোনটি পুরোনো, কম দামের; সিগন্যাল কখনো 4G, কখনো নেমে যায়। পর্দা অনেকক্ষণ সাদা থাকে। তারপর লেখা আসে, কিন্তু অক্ষর একবার বদলে যায়। ছবি এলে লেখা নিচে সরে যায়। “অর্ডার করুন” বোতামে চাপ দিলেন, কিছুই হলো না — আবার চাপলেন। এই অভিজ্ঞতার পর তিনি আর সেই দোকানে ফেরেন না।
ডেভেলপার নিজের দ্রুত ল্যাপটপে, অফিসের Wi-Fi-তে এর কিছুই দেখেন না — এটাই ওয়েব পারফরম্যান্সের মূল ফাঁদ। বাংলাদেশে ওয়েবের বড় অংশ দেখা হয় ফোনে। অনেকের ফোনে প্রসেসর দুর্বল, মেমরি কম। নেটওয়ার্কের গতি এলাকা আর সময়ভেদে ওঠানামা করে। আর মোবাইল ডেটা প্যাকেজ কিনে চলেন বলে প্রতিটি অপ্রয়োজনীয় মেগাবাইট আসলে পাঠকের টাকা।
🧑 মানুষ
দ্রুত পাতা মানে কম অপেক্ষা, কম ডেটা আর ব্যাটারি খরচ। ধীর পাতা থেকে মানুষ লোড শেষের আগেই চলে যান।
ধীর নেটওয়ার্ক, দুর্বল ফোন🔎 SEO
Google তার সার্চ র্যাঙ্কিংয়ে “page experience”-এর অংশ হিসেবে Core Web Vitals দেখে। লেখার মান ও প্রাসঙ্গিকতাই প্রধান, তবে সমান মানের দুটি পাতার মধ্যে দ্রুতটির সুবিধা আছে।
পর্ব ১৩-এর SEO-র সাথে মিলিয়ে🛒 Conversion — বিক্রি ও সাইনআপ
পাতা ধীর হলে কম মানুষ কেনেন, কম মানুষ সাইনআপ করেন, কম পাতা পড়া হয়। গতি তাই সরাসরি ব্যবসার বিষয়।
অর্ডার, ফর্ম, পাতা দেখাপারফরম্যান্স বোঝার সবচেয়ে সহজ উপায় একটি রেস্তোরাঁ। অতিথি বসার পর কত দ্রুত পানি আর মেনু আসে (প্রথম পর্দা), কত দ্রুত আসল খাবার আসে (প্রধান অংশ — LCP), ওয়েটারকে ডাকলে কত দ্রুত সাড়া দেন (INP), আর টেবিলের থালা-বাটি হঠাৎ সরে যায় কি না (CLS).
২ · একটি পাতা কীভাবে লোড হয় — critical rendering path
পর্ব ০১-এ দেখেছেন, ব্রাউজার HTML থেকে DOM আর CSS থেকে CSSOM বানায়, দুটো মিলিয়ে render tree, তারপর layout আর paint. প্রথম ছবিটি পর্দায় আসার আগে যে ধাপগুলো পার হতেই হয়, তাদের একসাথে বলে critical rendering path. এই পথে কোন ফাইল কাকে আটকায়, সেটি বোঝাই পারফরম্যান্সের অর্ধেক কাজ। ধাপে ধাপে দেখুন:
- HTML
TTFBপ্রথম byte - Parser
preload scannerঠিকানা খোঁজা - CSS
CSSOMrender আটকায় - ফন্ট
woff2লেখার চেহারা - ছবি
LCPপ্রধান অংশ - JS
deferসাড়া দেওয়া
GET /shop/index.html … DNS, TCP, TLS, সার্ভারের হিসাব … 200 OK Content-Encoding: br <!doctype html><html lang="bn"><head> … ← টুকরো টুকরো আসে
<link rel="stylesheet" href="main.css"> <link rel="preload" href="bn.woff2" as="font" …> <script src="app.js" defer></script> … body-র ভেতরে … <img src="hero.avif" …> ← scanner এটিও আগেভাগে দেখে
body { font-family: "Anek Bangla", sans-serif; }
.hero { … }
@import url("extra.css"); ← এখন আরেকটি ফাইল, আরেক দফা অপেক্ষা
@font-face {
font-family: "Anek Bangla";
src: url(bn.woff2) format("woff2");
font-display: swap; ← আগে সিস্টেম ফন্টে লেখা দেখাও
}
<img src="hero.avif" alt="ঈদের নতুন কালেকশন"
width="1200" height="600"
fetchpriority="high"> ← lazy নয়!
<script src="app.js" defer></script> ← HTML পড়া শেষে, ক্রম মেনে <script src="ads.js" async></script> ← যখনই আসে, ক্রম ছাড়া <script src="old.js"></script> ← ✗ parser থামিয়ে দেয়
- HTML আসে। অনুরোধ পাঠানো থেকে সার্ভারের উত্তরের প্রথম byte আসা পর্যন্ত সময়কে বলে TTFB. এর ভেতরে আছে DNS, সংযোগ তৈরি, HTTPS আর সার্ভারের হিসাব। HTML টুকরো টুকরো আসে, আর ব্রাউজার পুরোটা আসার অপেক্ষা না করেই পড়া শুরু করে।
- Parser আর preload scanner. HTML parser ওপর থেকে নিচে DOM বানায়। পাশাপাশি preload scanner নামে একটি হালকা পাঠক সামনে উঁকি দিয়ে
<link>,<script>,<img>-এর ঠিকানা খুঁজে আগেভাগে নামানো শুরু করে। কিন্তু খেয়াল করুন: CSS-এর ভেতরে লুকানোbackground-imageবা JavaScript দিয়ে বসানো ছবি এই scanner দেখতে পায় না। - CSS — render আটকায়। head-এর stylesheet পুরো না আসা আর CSSOM তৈরি না হওয়া পর্যন্ত ব্রাউজার কিছুই আঁকে না। এটি ইচ্ছাকৃত, যাতে সাজসজ্জাহীন পাতার ঝলক না দেখা যায় (পর্ব ১৬)। CSS-এর ভেতরে
@importথাকলে সেই ফাইল খোঁজা শুরু হয় প্রথমটি আসার পরে — এক দফার জায়গায় দুই দফা অপেক্ষা। এরপর প্রথম লেখা বা ছবি পর্দায় আসে: FCP. - ফন্ট। ব্রাউজার ফন্ট নামায় তখনই, যখন CSS বলে কোন ফন্ট লাগবে আর DOM-এ সেই ফন্টের লেখা আছে — অর্থাৎ বেশ দেরিতে। ফন্ট আসার আগে লেখা লুকিয়ে রাখা হবে, নাকি সিস্টেম ফন্টে দেখানো হবে, তা ঠিক করে font-display. খুব জরুরি একটি ফন্ট
preloadকরে আগেভাগে আনা যায়। - ছবি — প্রায়ই LCP. প্রথম পর্দার সবচেয়ে বড় ছবি বা লেখার অংশটি দেখা যাওয়ার মুহূর্ত হলো LCP. ছবিটি HTML-এ সরাসরি
<img>হলে scanner শুরুতেই খুঁজে পায়;fetchpriority="high"দিলে ব্রাউজার একে অন্য ছবির আগে নামায়।widthআরheightদিলে ছবি আসার আগেই জায়গা রাখা থাকে, তাই লেখা লাফায় না। - JavaScript. সাধারণ
<script src>parser-কে থামিয়ে দেয় — ফাইল নামানো আর চালানো শেষ না হলে HTML পড়া এগোয় না।deferদিলে নামানো চলে পাশাপাশি, আর চালানো হয় HTML পড়া শেষে, লেখার ক্রমে।asyncক্রম মানে না, নামামাত্র চলে — বিজ্ঞাপন বা analytics-এর মতো স্বাধীন script-এর জন্য। JavaScript-ই পরে ক্লিকের সাড়া দেয়, তাই এর ভার INP-তে ধরা পড়ে।
পারফরম্যান্সের প্রায় প্রতিটি কৌশল তিনটি প্রশ্নের একটির উত্তর: (১) কম পাঠানো যায় কী? (ছোট ফাইল, কম ফাইল) (২) আগে পাঠানো যায় কী? (প্রথম পর্দার জিনিস আগে খুঁজে পাওয়া, অগ্রাধিকার) (৩) পরে পাঠানো যায় কী? (নিচের ছবি, অদরকারি script, print-এর CSS). নতুন কোনো কৌশল দেখলে নিজেকে জিজ্ঞেস করুন — এটি কোন প্রশ্নের উত্তর?
৩ · Core Web Vitals — তিনটি সংখ্যায় ব্যবহারকারীর অভিজ্ঞতা
“পাতাটা দ্রুত” — কথাটি অস্পষ্ট। কোন মুহূর্তে দ্রুত? কার ফোনে? এই অস্পষ্টতা দূর করতে Google ২০২০ সালে Core Web Vitals নামে তিনটি মাপ ঘোষণা করে। প্রতিটি মাপ ব্যবহারকারীর অভিজ্ঞতার একটি দিক ধরে: লোড হওয়া, সাড়া দেওয়া আর স্থির থাকা।
| মাপ | প্রশ্ন | ভালো | উন্নতি দরকার | খারাপ |
|---|---|---|---|---|
| LCP — Largest Contentful Paint | প্রধান অংশ কত দ্রুত দেখা গেল? | ≤ 2.5s | 2.5–4s | > 4s |
| INP — Interaction to Next Paint | চাপ দিলে পর্দা কত দ্রুত বদলায়? | ≤ 200ms | 200–500ms | > 500ms |
| CLS — Cumulative Layout Shift | পাতা কতটা অপ্রত্যাশিতভাবে লাফায়? | ≤ 0.1 | 0.1–0.25 | > 0.25 |
একটি গুরুত্বপূর্ণ খুঁটিনাটি: সীমাগুলো গড়ের ওপর নয়, ৭৫তম পারসেন্টাইলের ওপর বিচার হয়। অর্থাৎ আপনার সাইটে আসা ১০০টি ভিজিটের মধ্যে অন্তত ৭৫টিতে LCP ২.৫ সেকেন্ড বা তার কম হলে তবেই “ভালো”। দ্রুত ফোনের পাঠকেরা গড়কে সুন্দর দেখাতে পারেন, কিন্তু ৭৫তম পারসেন্টাইল ধীর ফোনের পাঠকদের ভুলতে দেয় না।
LCP — প্রধান অংশটি কত দ্রুত দেখা যায়
LCP-এর “element” সাধারণত প্রথম পর্দার বড় ছবি, ভিডিওর poster, বড় background ছবি বা লেখার বড় অনুচ্ছেদ। LCP-কে চারটি টুকরোয় ভাঙা যায়: HTML আসার সময় (TTFB), ছবি খোঁজা শুরুর আগের দেরি, ছবি নামানোর সময়, আর নামানোর পরে আঁকার দেরি। একটি কাল্পনিক দোকানের হোমপেজে, ধীর 4G-তে, প্রতিটি সংশোধন কোন টুকরো ছোট করে দেখুন:
1<link rel="stylesheet" href="main.css">2/* main.css: @import "hero.css"; */3/* hero.css: .hero { background: url(hero.jpg) } */4<img src="hero.avif" fetchpriority="high"5 srcset="hero-800.avif 800w, hero-1600.avif 1600w"6 width="1600" height="800" alt="…">7<link rel="preload" href="bn.woff2" as="font" …>
| TTFB | |
| খোঁজার দেরি | |
| নামানো | |
| আঁকার দেরি |
- সমস্যার পাতা। প্রধান ছবিটি CSS-এর
background, তাও আবার@importকরা দ্বিতীয় CSS ফাইলের ভেতরে। ব্রাউজার ছবির খোঁজ পায় দুটি CSS ফাইল নামানো আর পড়া শেষ হলে — তাই শুধু “খোঁজার দেরি” ১.৬ সেকেন্ড। মোট ৪.১ সেকেন্ড: খারাপ। @importসরানো।hero.css-এর নিয়মmain.css-এ মিশিয়ে দেওয়া হলো (বা আলাদা<link>করা হলো)। এখন এক দফা অপেক্ষা কমল, খোঁজার দেরি নামল ০.৯ সেকেন্ডে। তবু ছবিটি CSS-এর ভেতরে, তাই preload scanner তাকে দেখতে পায় না।- background-এর বদলে
<img>. ছবিটি এখন HTML-এ, আর তাতেfetchpriority="high". scanner শুরুতেই খুঁজে পায় এবং অন্য ছবির আগে নামায় — খোঁজার দেরি প্রায় শূন্য। সাথেwidth/height, যাতে CLS-ও না বাড়ে। - ছবি নিজেই হালকা। JPEG-এর বদলে AVIF (বা WebP), আর srcset দিয়ে ফোনে ছোট ছবি (পর্ব ৩২)। নামানো ১.২ থেকে ০.৫ সেকেন্ড।
- আঁকার দেরি। ছবি এসে গেলেও render আটকে থাকতে পারে — বড় CSS বা parser-আটকানো script এখনো চলছে। LCP যদি লেখা হয়, ফন্টের অপেক্ষাও এখানে পড়ে। CSS ছোট করা, script-এ
defer, আর মূল ফন্ট preload করায় দেরি কমল। শেষে LCP ১.৭ সেকেন্ড — একই নকশা।
সবচেয়ে প্রচলিত LCP-ভুল: প্রধান ছবিতে loading="lazy". Lazy loading বলে “পর্দার কাছে না এলে নামিও না”, আর ব্রাউজার layout না জেনে বুঝতে পারে না ছবিটি পর্দায় আছে কি না — তাই প্রথম পর্দার ছবিও দেরিতে আসে। নিয়ম: প্রথম পর্দার ছবি কখনো lazy নয়; নিচের ছবিগুলো lazy.
CLS — পাতা কতটা লাফায়
পড়তে পড়তে লেখা হঠাৎ নিচে নেমে গেল, অথবা বোতামে চাপ দিতে যাচ্ছিলেন আর ঠিক তখন ওপরে একটি ব্যানার ঢুকে অন্য কিছুতে চাপ পড়ে গেল — এটাই layout shift. CLS এই লাফগুলোর মাপ: কতটা জায়গা জুড়ে কতদূর সরল। ব্যবহারকারী নিজে কিছু চাপার ঠিক পরের (প্রায় আধা সেকেন্ডের মধ্যে) বদল গোনা হয় না, কারণ সেটি প্রত্যাশিত। প্রধান কারণগুলো চেনা:
- মাপ ছাড়া ছবি, ভিডিও, iframe — নামার আগে উচ্চতা শূন্য।
- পরে ঢোকা জিনিস — বিজ্ঞাপন, কুকি-ব্যানার, “অ্যাপ নামান” পট্টি, জায়গা না রেখে লেখার ওপরে।
- ওয়েব ফন্ট বদল — সিস্টেম ফন্ট আর ওয়েব ফন্টের মাপ আলাদা হলে swap-এর মুহূর্তে লাইন ভাঙা বদলায়, লেখা লাফায়।
top/marginদিয়ে অ্যানিমেশন — অন্যদের ঠেলে সরায়;transformসরালে layout shift গোনা হয় না (পর্ব ৩৮)।
নিচের প্রিভিউতে একটি খবরের পাতা। ৭০০ মিলিসেকেন্ড পরে একটি “বিজ্ঞাপন” এসে ঢোকে — ধীর নেটওয়ার্কে যেমন হয়। ব্রাউজারের নিজের PerformanceObserver দিয়ে layout shift মাপা হচ্ছে:
<style>
body { font-family: sans-serif; max-width: 420px; min-height: 300px; }
.slot { background: #f1f5f9; }
.slot.reserve { min-height: 120px; } /* আগে থেকে জায়গা রাখা */
.banner { height: 120px; display: grid; place-items: center;
background: #fde68a; font-weight: bold; }
</style>
<h3>আজকের খবর</h3>
<div class="slot" id="slot"></div>
<p>মেট্রো রেলে আজ রেকর্ড যাত্রী। এই লেখাটি পড়তে পড়তেই ওপরে হঠাৎ কিছু ঢুকলে লেখা নিচে সরে যায়।</p>
<button>পুরো খবর পড়ুন</button>
<script>
let cls = 0;
if (PerformanceObserver.supportedEntryTypes.includes('layout-shift')) {
new PerformanceObserver(list => {
for (const e of list.getEntries()) if (!e.hadRecentInput) cls += e.value;
}).observe({ type: 'layout-shift', buffered: true });
} else console.log('এই ব্রাউজার layout-shift মাপে না (এখন শুধু Chromium-এ আছে)');
setTimeout(() => {
document.getElementById('slot').innerHTML = '<div class="banner">বিজ্ঞাপন</div>';
}, 700);
setTimeout(() => console.log('CLS = ' + cls.toFixed(3)), 1500);
</script>শুরুতে শিরোনামের ঠিক নিচে লেখা আর বোতাম। এক পলক পরে হলুদ “বিজ্ঞাপন” ব্যানারটি ঢোকে আর লেখা-বোতাম ১২০px নিচে নেমে যায়। প্রিভিউর নিচে Chromium-ভিত্তিক ব্রাউজারে (Chrome, Edge) একটি শূন্যের বেশি CLS = … দেখাবে (প্রিভিউ চওড়া বলে সংখ্যাটি ছোট; ফোনের ছোট পর্দায় একই লাফ পর্দার অনেক বড় অংশ জুড়ে থাকে, তাই মানও বেশি); Firefox বা Safari-তে এই মাপের ব্যবস্থা এখনো নেই, তাই সেখানে শুধু একটি বার্তা আসবে। (প্রিভিউটি পর্দার বাইরে থাকলে ব্রাউজার আঁকা স্থগিত রাখতে পারে — তখন প্রিভিউটি দেখতে দেখতে “আগের কোড” চেপে আবার চালান।) চেষ্টা করুন: (১) <div class="slot"-কে class="slot reserve" করুন — এখন শুরু থেকেই ১২০px-এর ধূসর জায়গা থাকে, ব্যানার সেখানে বসে, কিছুই সরে না, আর CLS আসে 0.000. (২) min-height-কে 60px করুন — লাফ ছোট হয় (১২০px-এর বদলে ৬০px), কিন্তু শূন্য নয়।
৪ · INP — চাপ দিলে পাতা কত দ্রুত সাড়া দেয়
পাতা দ্রুত দেখা গেলেই কাজ শেষ নয়। bKash অ্যাপে “Send Money” চাপার পর এক সেকেন্ড কিছু না ঘটলে মনে হয় অ্যাপ আটকে গেছে, আর মানুষ আবার চাপেন। ওয়েবে এই অনুভূতির মাপ INP: একটি ক্লিক, ট্যাপ বা কী চাপা থেকে পর্দায় পরের ছবি আসা পর্যন্ত সময়। পুরো ভিজিটে যত interaction হয়, তার মধ্যে প্রায় সবচেয়ে ধীরটিই INP (অনেক interaction থাকলে ব্যতিক্রমী দু-একটি বাদ দেওয়া হয়)।
আগে এই জায়গায় ছিল FID, যা শুধু প্রথম interaction-এর আগের অপেক্ষা মাপত — handler চালানো আর পর্দা আঁকার সময় ধরত না। ২০২৪ সালের মার্চে INP তার জায়গা নেয়। একটি ট্যাপের ভেতরে কী কী ঘটে, দেখুন:
- ট্যাপ। ব্যবহারকারী “কার্টে যোগ করুন” চাপলেন। ঘড়ি চালু। ঘড়ি থামবে পর্দায় পরের ছবি এলে — কার্টের সংখ্যা ১ থেকে ২ হলে।
- Input delay — অপেক্ষা। ঠিক তখন main thread একটি analytics script-এর long task চালাচ্ছে (৫০ মিলিসেকেন্ডের বেশি একটানা কাজ)। ব্রাউজার একসাথে দুটি JavaScript কাজ চালায় না, তাই ট্যাপটি সারিতে দাঁড়িয়ে থাকে — এখানে ১৮০ মিলিসেকেন্ড। পর্দা জমে আছে।
- Processing — কাজ। এবার ক্লিকের handler চলে। এখানে সে পুরো পণ্য-তালিকা নতুন করে বানাচ্ছে — আরও ৬০ মিলিসেকেন্ড।
- Presentation delay — আঁকা। handler DOM বদলাল; এখন style হিসাব, layout, paint. পাতায় কয়েক হাজার element থাকলে এই হিসাবও ভারী — ৯০ মিলিসেকেন্ড। মোট ৩৩০ মিলিসেকেন্ড: “উন্নতি দরকার”।
- সমাধান। লম্বা কাজকে ছোট টুকরোয় ভাগ করে মাঝে main thread ছেড়ে দেওয়া (JavaScript-এর বিষয়), অদরকারি third-party script পরে লোড করা, handler-এ শুধু যা বদলেছে তা বদলানো, আর DOM ছোট রাখা বা পর্দার বাইরের অংশে
content-visibility(অংশ ৮)। একই ট্যাপ এখন ১২০ মিলিসেকেন্ডে — ভালো।
INP-এর বড় অংশ JavaScript-এর বিষয় (JavaScript কোর্সের event loop-এর পর্ব)। তবে HTML-CSS লেখকেরও ভূমিকা আছে: ছোট DOM, সস্তা CSS, আর যেখানে সম্ভব HTML-এর নিজের <details>, <dialog>, popover (পর্ব ১১) — এগুলো আপনার script-এর সারিতে দাঁড়ায় না।
৫ · মাপুন, অনুমান নয় — Lighthouse, field data আর Performance প্যানেল
পারফরম্যান্সের প্রথম নিয়ম: আগে মাপুন, তারপর ঠিক করুন, তারপর আবার মাপুন। অনুমানে কাজ করলে প্রায়ই এমন জিনিস “ঠিক” করা হয়, যা সমস্যাই ছিল না। মাপ দুই রকম:
🧪 Lab data — ল্যাবের মাপ
একটি নির্দিষ্ট যন্ত্রে, নকল ধীর নেটওয়ার্ক আর ধীর CPU দিয়ে, একবার পাতা খুলে মাপা। বারবার একই অবস্থায় চালানো যায়, তাই কারণ খোঁজা আর আগে-পরে তুলনার জন্য চমৎকার। কিন্তু কেউ ক্লিক করে না, তাই INP সরাসরি মাপা যায় না।
Lighthouse, WebPageTest👥 Field data — আসল মানুষের মাপ
আসল ব্যবহারকারীর আসল ফোন, নেটওয়ার্ক আর ক্লিক থেকে সংগ্রহ। Google-এর CrUX জনপ্রিয় সাইটগুলোর জন্য গত ২৮ দিনের ৭৫তম পারসেন্টাইল দেখায়। Google-এর র্যাঙ্কিংয়ে এটাই গোনা হয়।
CrUX, নিজের RUMLab data আর field data প্রায়ই মেলে না, আর সেটি স্বাভাবিক। Lighthouse-এ ৯৫ স্কোর পেয়েও CrUX-এ INP “খারাপ” হতে পারে, কারণ আসল ব্যবহারকারী পাতা খোলার পর ক্লিক করেন, স্ক্রল করেন, বিজ্ঞাপন দেরিতে আসে — ল্যাবের একবার খোলায় এসব ঘটে না।
| টুল | কোথায় | কী দেখায় |
|---|---|---|
| Lighthouse | DevTools-এর Lighthouse প্যানেল | ল্যাবের স্কোর, LCP, CLS, Total Blocking Time (INP-এর ল্যাব-বিকল্প), আর কী ঠিক করবেন তার তালিকা |
| PageSpeed Insights | pagespeed.web.dev | ওপরে CrUX-এর আসল ব্যবহারকারীর মাপ (ডেটা থাকলে), নিচে Lighthouse-এর ল্যাব মাপ — দুটি একসাথে |
| Performance প্যানেল | DevTools | সাম্প্রতিক Chrome-এ খুললেই নিজের ব্রাউজারে LCP, CLS, INP-এর লাইভ মান; রেকর্ড করলে প্রতিটি মিলিসেকেন্ডে main thread কী করছে, কোনটি long task, কোন element LCP |
| Network প্যানেল | DevTools | প্রতিটি ফাইলের আকার, সময়, অগ্রাধিকার; throttling দিয়ে ধীর নেটওয়ার্কের নকল (পর্ব ৪৭) |
| WebPageTest | webpagetest.org | বিভিন্ন দেশ, যন্ত্র আর নেটওয়ার্ক থেকে পরীক্ষা; লোডের প্রতিটি মুহূর্তের ছবি (filmstrip) আর বিস্তারিত waterfall |
নিজের ল্যাপটপে মাপলে Performance প্যানেলে CPU throttling (যেমন 4×) আর Network-এ “Slow 4G” বেছে নিন — তবেই ল্যাপটপটি একটি সাধারণ ফোনের কাছাকাছি আচরণ করবে। আর মাপুন Incognito জানালায়, যাতে browser extension মাপ নষ্ট না করে।
পাতার ভেতর থেকেও মাপা যায়: ব্রাউজার প্রতিটি নামানো ফাইলের হিসাব রাখে (Resource Timing API), আর web-vitals-এর মতো ছোট library দিয়ে LCP, INP, CLS সংগ্রহ করে নিজের সার্ভারে পাঠানো যায় — একেই বলে নিজের RUM.
৬ · CSS লোডের কৌশল — render-blocking থেকে caching
পর্ব ১৬-এ জেনেছেন, head-এর stylesheet render-blocking — তাই CSS যত বড় আর যত দেরিতে আসে, প্রথম পর্দাও তত দেরিতে।
media attribute — কোন stylesheet আসলে আটকায়
একটি <link rel="stylesheet">-এর media যদি এখনকার পর্দার সাথে না মেলে — যেমন media="print" — ব্রাউজার ফাইলটি কম অগ্রাধিকারে নামায় ঠিকই, কিন্তু তার জন্য render আটকায় না। তাই print-এর CSS আলাদা ফাইলে media="print" দিয়ে রাখলে প্রথম পর্দা হালকা হয়। নিচের প্রিভিউতে তিনটি stylesheet, তিনটি আলাদা media:
<style>
body { font-family: sans-serif; }
p { padding: 8px; border-radius: 6px; }
</style>
<style media="print"> .a { background: #fecaca; } </style>
<style media="(min-width: 400px)"> .b { background: #bbf7d0; } </style>
<style media="screen"> .c { background: #bfdbfe; } </style>
<p class="a">media="print" — শুধু ছাপার সময় লাল</p>
<p class="b">media="(min-width: 400px)" — চওড়া পর্দায় সবুজ</p>
<p class="c">media="screen" — পর্দায় সবসময় নীল</p>
<script>
(function m() {
if (!document.body.offsetWidth) return setTimeout(m, 50); // পর্দার মাপ জানা পর্যন্ত
for (const s of document.styleSheets) {
const q = s.media.mediaText || 'all';
console.log(q + (matchMedia(q).matches
? ' → এখন মেলে: link হলে render আটকাত'
: ' → মেলে না: link হলে নামত, কিন্তু render আটকাত না'));
}
})();
</script>প্রথম অনুচ্ছেদের কোনো রং নেই, কারণ পর্দা “print” নয়। দ্বিতীয়টি সবুজ, যদি প্রিভিউ ৪০০px-এর বেশি চওড়া হয় (ফোনে সাদা থাকতে পারে)। তৃতীয়টি নীল। নিচে চারটি stylesheet-এর তালিকা: all আর screen মেলে; print মেলে না; (min-width: 400px) পর্দার মাপের ওপর নির্ভর করে। এখানে <style> ব্যবহার করা হয়েছে যাতে কিছু নামাতে না হয়; একই media কোনো <link>-এ থাকলে “মেলে না” ফাইলগুলো render আটকাত না। চেষ্টা করুন: (১) 400px-কে 2000px করুন — সবুজ চলে যায়, তালিকায় সেটি “মেলে না” হয়। (২) media="print"-কে media="all" করুন — প্রথম অনুচ্ছেদও লাল হয়।
Critical CSS — প্রথম পর্দার CSS head-এ
দেড়শো পাতার একটি সাইটের পুরো CSS হয়তো কয়েকশো KB, অথচ হোমপেজের প্রথম পর্দা আঁকতে লাগে তার সামান্য অংশ। Critical CSS কৌশলে সেই অংশটুকু <style> হিসেবে HTML-এর ভেতরেই রাখা হয় — আলাদা ফাইলের অপেক্ষা নেই — আর বাকি CSS আসে পরে, render না আটকে:
<head>
<style>
/* critical: শুধু header, hero আর প্রথম পর্দার layout */
body { margin: 0; font-family: "Anek Bangla", system-ui, sans-serif; }
.site-header { display: flex; justify-content: space-between; padding: 12px 16px; }
.hero { min-height: 60vh; display: grid; place-items: center; }
</style>
<!-- বাকি CSS: আগে print হিসেবে নামে (আটকায় না), নামা শেষে সব পর্দায় প্রযোজ্য -->
<link rel="stylesheet" href="/css/site.css?v=20261020"
media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/css/site.css?v=20261020"></noscript>
</head>দামও আছে: inline CSS ক্যাশ হয় না, প্রতিটি পাতায় আবার আসে; হাতে বেছে বের করা কঠিন, তাই সাধারণত build টুল দিয়ে করা হয়; আর critical অংশ ভুল হলে পাতা সাজানোর পরে আবার বদলায়, যা CLS বাড়ায়। ছোট সাইটে প্রায়ই একটিমাত্র ছোট, minify করা, ভালোভাবে ক্যাশ হওয়া CSS ফাইলই যথেষ্ট।
@import-এর শিকল, minification আর compression
@importএড়ান। CSS-এর ভেতরের@importব্রাউজার দেখে প্রথম ফাইল নামার পরে, তাই প্রতিটি ধাপ এক দফা বাড়তি অপেক্ষা। দরকার হলে HTML-এ পাশাপাশি একাধিক<link>দিন — এরা একসাথে নামে। (Google Fonts-এর@importকোডের বদলে তাদের<link>কোডটিই নিন।)- Minification. space, লাইন-ভাঙা আর comment মুছে ফাইল ছোট করা। নিজের কোড পড়ার মতো রেখে, প্রকাশের সময় build টুল (পর্ব ৫০-এর PostCSS, বা cssnano, Lightning CSS) দিয়ে minify করুন।
- Compression — gzip আর Brotli. সার্ভার পাঠানোর আগে ফাইল চেপে ছোট করে; ব্রাউজার
Accept-Encodingheader-এ জানায় সে কোনগুলো বোঝে, আর উত্তরে আসেContent-Encoding: brবাgzip. লেখা-ভিত্তিক HTML, CSS, JS, SVG অনেক ছোট হয়; Brotli সাধারণত gzip-এর চেয়েও ভালো চাপে। ছবি আর WOFF2 আগে থেকেই চাপা। Network প্যানেলে ফাইলের Response Headers-এ দেখে নিন compression চালু কি না।
Caching আর cache-busting
সবচেয়ে দ্রুত ফাইল সেটি, যা নামাতেই হয় না। সার্ভার Cache-Control header দিয়ে ব্রাউজারকে বলে একটি ফাইল কতদিন নিজের কাছে রাখা যাবে। কিন্তু দীর্ঘদিন ক্যাশে রাখলে ফাইল বদলানোর পর পুরোনো কপি দেখানোর ঝুঁকি — তার সমাধান পর্ব ১৬-এর cache-busting: ফাইল বদলালে ঠিকানাও বদলানো (style.css?v=20261020, বা build টুলের style.3f9a1c.css). এই সাইটটি নিজেও এভাবেই চলে — CSS-JS কয়েকদিন ক্যাশে থাকে, আর বদলালে ?v=-এর তারিখ বদলানো হয়। cPanel-এর মতো Apache সার্ভারে .htaccess-এ এমন নিয়ম লেখা যায়:
# ঠিকানায় version থাকা CSS/JS/ফন্ট/ছবি — দীর্ঘ সময় ক্যাশ
<FilesMatch "\.(css|js|woff2|avif|webp|svg)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML — প্রতিবার সার্ভারকে জিজ্ঞেস করো নতুন আছে কি না
<FilesMatch "\.html$">
Header set Cache-Control "no-cache"
</FilesMatch>এক বছরের immutable ক্যাশ নিরাপদ তখনই, যখন প্রতিটি বদলে ঠিকানাও বদলায়। আর no-cache মানে “ক্যাশ কোরো না” নয়, “ব্যবহারের আগে জিজ্ঞেস করো” — ফাইল না বদলালে সার্ভার ছোট্ট 304 Not Modified পাঠায়।
৭ · ফন্ট আর ছবি — পাতার সবচেয়ে ভারী দুই অতিথি
ওয়েব ফন্ট — বিশেষ করে বাংলা
বাংলা ফন্ট ইংরেজির চেয়ে প্রায়ই অনেক ভারী, কারণ যুক্তাক্ষর, কার-চিহ্ন আর তাদের নানা রূপের জন্য ফাইলে অনেক বেশি glyph থাকে (পর্ব ১৪, ২১)। চারটি weight-এর একটি বাংলা ফন্ট মানে চারটি আলাদা ভারী ফাইল। কৌশলগুলো:
/* বাংলা অক্ষরের ফাইল — পাতায় বাংলা থাকলে তবেই নামবে */
@font-face {
font-family: "Anek Bangla";
src: url(/fonts/anek-bangla-bn.woff2) format("woff2");
font-weight: 400 700; /* variable font: এক ফাইলে সব weight */
font-display: swap;
unicode-range: U+0980-09FE, U+200C-200D, U+25CC;
}
/* ইংরেজি অক্ষরের ছোট ফাইল */
@font-face {
font-family: "Anek Bangla";
src: url(/fonts/anek-bangla-latin.woff2) format("woff2");
font-weight: 400 700;
font-display: swap;
unicode-range: U+0000-00FF;
}
body { font-family: "Anek Bangla", "Noto Sans Bengali", system-ui, sans-serif; }font-display: swap— ফন্ট আসার আগে লেখা সিস্টেম ফন্টে দেখায়, অদৃশ্য রাখে না (FOIT নয়, FOUT). সাজসজ্জার ফন্টেoptionalভাবা যায় — ধীর নেটওয়ার্কে ফন্ট বাদ পড়বে, কিন্তু লেখা লাফাবে না।- Subsetting আর
unicode-range— ফন্টকে অক্ষরের দল ধরে কয়েকটি ফাইলে ভাগ করা; ব্রাউজার শুধু সেই ফাইল নামায়, যার অক্ষর পাতায় আছে। Google Fonts বাংলা ফন্টকে এভাবেই “bengali”, “latin” subset-এ ভাগ করে পাঠায়। - কম weight — বেশিরভাগ সাইট দুটিতেই চলে: সাধারণ আর মোটা। অথবা একটি variable font — এক ফাইলে সব weight.
- preconnect আর preload — বাইরের ফন্ট-সার্ভারে
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>(পর্ব ১৩)। নিজের সার্ভারের একটি জরুরি ফন্টে<link rel="preload" href="/fonts/anek-bangla-bn.woff2" as="font" type="font/woff2" crossorigin>— ফন্টের preload-এcrossoriginলাগবেই, নইলে ফাইল দুবার নামে। সব ফন্ট preload করবেন না; preload করা জিনিস অন্য জরুরি জিনিসের সাথে প্রতিযোগিতা করে। - সিস্টেম ফন্টও একটি উত্তর — বেশিরভাগ Android ফোনে বাংলা ফন্ট (যেমন Noto Sans Bengali) আগে থেকেই থাকে: শূন্য byte, শূন্য অপেক্ষা।
ছবি — পর্ব ৩২-এর পুনরাবৃত্তি, পারফরম্যান্সের চোখে
বেশিরভাগ পাতায় byte-এর হিসাবে সবচেয়ে বড় অংশ ছবি। পর্ব ৩২-এ শেখা নিয়মগুলো এখানে একসাথে: srcset আর sizes দিয়ে ফোনে ছোট ছবি; <picture> দিয়ে AVIF/WebP (দুটিই সব প্রধান ব্রাউজারে আছে — WebP বহু বছর ধরে, AVIF তুলনামূলক সাম্প্রতিক; Can I use দেখে নিন) আর পুরোনো ব্রাউজারের জন্য JPEG; প্রথম পর্দার বাইরের ছবিতে loading="lazy" (Widely available); প্রধান ছবিতে fetchpriority="high" (Baseline Newly available — না চিনলে ব্রাউজার শুধু উপেক্ষা করে, কোনো ক্ষতি নেই); আর প্রতিটি ছবিতে width ও height.
শেষেরটির কাজ নিজের চোখে দেখুন। দুটি ছবি — প্রথমটিতে width/height আছে, দ্বিতীয়টিতে নেই। ছবি নামা শুরুর মুহূর্তে আর নামা শেষে দুটির উচ্চতা মাপা হচ্ছে, তারপর Resource Timing থেকে প্রতিটি ছবির সময় আর আকার:
<style>
img { display: block; width: 280px; max-width: 100%; height: auto;
margin-bottom: 8px; background: #e2e8f0; }
</style>
<img id="a" alt="মাপ দেওয়া ছবি" width="1280" height="720" fetchpriority="high">
<img id="b" alt="মাপ ছাড়া ছবি">
<script>
const a = document.getElementById('a'), b = document.getElementById('b');
const r = '?r=' + Math.random(); // ক্যাশ এড়াতে
(function go() {
if (!document.body.offsetWidth) return setTimeout(go, 50);
a.src = '../image/ABCL-TECH-Cover.jpg' + r;
b.src = '../image/ai-and-education.jpg' + r;
console.log('নামার আগে: a = ' + a.offsetHeight + 'px, b = ' + b.offsetHeight + 'px');
let n = 0;
a.onload = b.onload = () => {
if (++n < 2) return;
console.log('নামার পরে: a = ' + a.offsetHeight + 'px, b = ' + b.offsetHeight + 'px');
performance.getEntriesByType('resource')
.filter(e => e.name.includes('/image/'))
.forEach(e => console.log(e.name.split('/').pop().split('?')[0] + ': ' +
Math.round(e.duration) + 'ms-এ নেমেছে'));
};
})();
</script>প্রিভিউতে দুটি ছবি একটির নিচে আরেকটি। নিচের প্রথম লাইনটিই আসল শিক্ষা: নামার আগে a-র উচ্চতা ইতিমধ্যে ঠিক (প্রস্থের ৯/১৬ ভাগ — width/height থেকে ব্রাউজার অনুপাত জেনে গেছে), কিন্তু b-র উচ্চতা 0px বা খুব ছোট। নামার পরে দুটিরই আসল উচ্চতা। অর্থাৎ b আসার মুহূর্তে তার নিচের সবকিছু লাফিয়ে নিচে যেত — CLS. শেষ দুই লাইনে Resource Timing থেকে প্রতিটি ছবি নামতে কত মিলিসেকেন্ড লাগল (প্রিভিউটি একটি আলাদা, নিরাপদ origin-এ চলে, তাই ব্রাউজার এখানে ফাইলের আকার জানায় না; আসল পাতায় transferSize থেকে তা-ও পাওয়া যায়)। চেষ্টা করুন: (১) b-তে width="1320" height="720" যোগ করুন — দুটিরই “নামার আগে” উচ্চতা শূন্যের বেশি হবে। (২) CSS থেকে height: auto; মুছে দিন — দেখুন ছবি কীভাবে চ্যাপ্টা হয়; width/height দিলে CSS-এ height: auto রাখা জরুরি।
৮ · ব্রাউজারের আঁকার খরচ — containment, content-visibility আর layout thrashing
সব ফাইল দ্রুত এলেও পাতা ধীর লাগতে পারে, যদি আঁকতে আর সাজাতে ব্রাউজারকে বেশি খাটতে হয়। পর্ব ৩৮-এ দেখেছেন: transform আর opacity-র অ্যানিমেশন সাধারণত শুধু compositing-এ চলে, আর width, top, margin প্রতি ফ্রেমে layout জাগায়, যা jank আনে। সেই চিন্তা এবার গোটা পাতায়।
contain — ব্রাউজারকে দেওয়া প্রতিশ্রুতি
একটি বড় পাতায় একটি কার্ডের লেখা বদলালে ব্রাউজারকে ধরে নিতে হয়, এর প্রভাব পুরো পাতায় পড়তে পারে — পাশের কার্ড সরতে পারে, ছায়া বাইরে ছড়াতে পারে। contain property দিয়ে আপনি প্রতিশ্রুতি দেন যে “এই বাক্সের ভেতরের বদল বাইরে যাবে না”, আর ব্রাউজার হিসাব ছোট এলাকায় রাখে:
| মান | প্রতিশ্রুতি | পার্শ্বপ্রতিক্রিয়া |
|---|---|---|
layout | ভেতরের layout বাইরেকে প্রভাবিত করবে না | নতুন stacking context আর absolute সন্তানের containing block |
paint | ভেতরের কিছু বাক্সের বাইরে আঁকা হবে না | বাইরে বেরোনো অংশ কেটে যায় (overflow: clip-এর মতো) |
size | বাক্সের মাপ ভেতরের জিনিসের ওপর নির্ভর করবে না | নিজে মাপ না দিলে উচ্চতা শূন্য (size containment) |
content | layout paint style-এর সংক্ষেপ | সাধারণ কার্ড-উইজেটের জন্য নিরাপদ পছন্দ |
contain সব প্রধান ব্রাউজারে বহুদিন ধরে আছে (Baseline Widely available). কিন্তু পার্শ্বপ্রতিক্রিয়াগুলো সত্যিকারের — নিজে দেখুন:
body { font-family: sans-serif; padding: 24px; }
.card {
position: relative; width: 220px; margin-bottom: 28px;
padding: 18px; border-radius: 10px;
background: #ecfeff; border: 1px solid #67e8f9;
}
.badge {
position: absolute; top: -12px; right: -14px;
padding: 2px 8px; border-radius: 99px;
background: #e11d48; color: #fff; font-size: 13px;
}
.contained {
contain: paint; /* বাক্সের বাইরে কিছু আঁকা হবে না */
}দুটি হালকা নীল কার্ড। প্রথমটির ডান কোণে লাল “নতুন” ব্যাজটি বাক্সের বাইরে বেরিয়ে আছে। দ্বিতীয়টিতে contain: paint, তাই ব্যাজের যে অংশ বাক্সের বাইরে, তা কেটে গেছে — শুধু ভেতরের টুকরো দেখা যায়। চেষ্টা করুন: (১) paint-কে layout করুন — ব্যাজ আবার পুরো দেখা যায়, কারণ layout containment কাটে না। (২) size লিখুন — দ্বিতীয় কার্ড চ্যাপ্টা হয়ে শুধু padding-এর উচ্চতায় নামে, কারণ ব্রাউজার আর লেখা দেখে মাপ ঠিক করে না। এরপর পাশে contain-intrinsic-size: 220px 60px; যোগ করুন — কার্ড সেই আন্দাজি মাপ পায়।
content-visibility: auto — পর্দার বাইরের অংশ পরে
একটি লম্বা পাতা ভাবুন — অর্ডারের ইতিহাস, মন্তব্যের দীর্ঘ তালিকা, বা এই কোর্সের মতো লম্বা পর্ব। ব্যবহারকারী প্রথমে দেখেন শুধু ওপরের অংশ, অথচ ব্রাউজার শুরুতেই হাজারো element-এর style আর layout হিসাব করে। content-visibility: auto বলে: “এই অংশ পর্দা থেকে দূরে থাকলে এর ভেতরটা আঁকা আর সাজানো বাদ রাখো; কাছে এলে করো।” মাপ না জেনে বাদ রাখলে বাক্সের উচ্চতা শূন্য হয়ে scrollbar লাফাত — তাই সাথে লাগে contain-intrinsic-size, একটি আন্দাজি মাপ। auto 60px লিখলে প্রথমে 60px, আর একবার আঁকা হলে ব্রাউজার আসল মাপ মনে রাখে।
<style>
.item { padding: 8px; border-bottom: 1px solid #ddd; font: 14px sans-serif; }
.cv .item { content-visibility: auto; contain-intrinsic-size: auto 60px; }
</style>
<p id="out">মাপা হচ্ছে…</p>
<script>
function timeLayout(cls) {
const box = document.createElement('div');
box.className = cls;
for (let i = 1; i <= 3000; i++) {
const d = box.appendChild(document.createElement('div'));
d.className = 'item';
d.innerHTML = '<b>অর্ডার #' + i + '</b> — ঢাকা থেকে খুলনা, ৳ ' + i * 37 +
'<br>অবস্থা: কুরিয়ারের পথে · আনুমানিক ২ দিন';
}
const t0 = performance.now();
document.body.append(box);
box.offsetHeight; // এখনই layout করতে বাধ্য করা
const ms = performance.now() - t0;
box.remove();
return ms.toFixed(1) + 'ms';
}
(function m() {
if (!document.body.offsetWidth) return setTimeout(m, 50);
timeLayout(''); // প্রথম চালানোর বাড়তি খরচ বাদ
console.log('content-visibility ছাড়া: ' + timeLayout(''));
console.log('content-visibility: auto সহ: ' + timeLayout('cv'));
document.getElementById('out').textContent = 'মাপা শেষ — ফল নিচে।';
})();
</script>প্রিভিউতে শুধু “মাপা শেষ” লেখাটি থাকে; ৩,০০০টি অর্ডারের তালিকা বানিয়ে, layout মেপে, সরিয়ে ফেলা হয়েছে। নিচের দুই লাইনে সময়: content-visibility: auto সহ সময় সাধারণত কয়েক গুণ কম, কারণ পর্দার বাইরের আইটেমগুলোর ভেতরটা সাজানোই হয়নি। সংখ্যা যন্ত্রভেদে আলাদা হবে। চেষ্টা করুন: (১) 3000-কে 300 করুন — পার্থক্য খুব ছোট হয়ে যায়; ছোট পাতায় এই কৌশলের দরকারই নেই। (২) contain-intrinsic-size: auto 60px; অংশটুকু মুছে চালান — সময়ে বড় পার্থক্য নেই, কিন্তু আসল পাতায় না-আঁকা আইটেমের উচ্চতা শূন্য হতো আর স্ক্রল করার সময় scrollbar লাফাত।
সাপোর্ট: content-visibility প্রথম আসে Chromium-এ, পরে Firefox আর Safari-তেও এসেছে; এখন এটি Baseline Newly available. যে ব্রাউজার চেনে না, সে স্বাভাবিকভাবে সব আঁকে — তাই এটি নিরাপদ progressive enhancement. আরেকটি সুবিধা: display: none-এর মতো নয়, auto অবস্থায় না-আঁকা লেখাও পাতার ভেতরে খোঁজা (Ctrl+F) যায় আর screen reader-এর কাছে থাকে।
দামি property — যা আঁকতে ফোন হাঁপায়
- বিশাল, ঝাপসা
box-shadow—0 30px 120px-এর মতো ছায়া শত শত কার্ডে, বিশেষ করে স্ক্রল বা অ্যানিমেশনের সময় আবার আঁকতে হলে ভারী। filter: blur()আরbackdrop-filter— বড় এলাকায় glassmorphism দুর্বল GPU-তে ধীর (পর্ব ৪১)। ছোট এলাকায়, অল্প জায়গায় রাখুন।background-attachment: fixed— স্ক্রলের প্রতিটি ধাপে পুরো পটভূমি আবার আঁকা লাগতে পারে; অনেক মোবাইল ব্রাউজার একে উপেক্ষাই করে।- সব কিছুতে
will-change— প্রতিটি আলাদা স্তর GPU-র মেমরি খায়; মেপে, অল্প জায়গায় (পর্ব ৩৮)।
DevTools-এর Rendering প্যানেলে “Paint flashing” চালু করলে আবার আঁকা অংশ সবুজ ঝলক দেয়, আর “Layout Shift Regions” লাফানো অংশ দেখায়।
Layout thrashing — JavaScript-এর লুকানো ফাঁদ
ব্রাউজার অলস, আর সেটি ভালো: style বদলালে সে তখনই layout করে না, ফ্রেম আঁকার আগে একবারে করে। কিন্তু JavaScript যদি বদলের ঠিক পরেই offsetWidth, getBoundingClientRect()-এর মতো মাপ জানতে চায়, ব্রাউজারকে তখনই layout করতে হয় — একে বলে forced synchronous layout. লুপের ভেতরে “লেখো-পড়ো-লেখো-পড়ো” করলে প্রতিটি ঘুরে একটি করে layout — এটাই layout thrashing. মেপে দেখুন:
const wrap = document.getElementById('boxes');
for (let i = 0; i < 600; i++) {
const d = document.createElement('div');
d.style.cssText = 'height:10px; width:20px; background:#0f766e';
wrap.append(d);
}
const items = [...wrap.children];
function thrash() { // পড়ো → লেখো → পড়ো → লেখো …
for (const d of items) d.style.width = (d.offsetWidth + 1) + 'px';
}
function batched() { // আগে সব পড়ো, তারপর সব লেখো
const widths = items.map(d => d.offsetWidth);
items.forEach((d, i) => { d.style.width = (widths[i] + 1) + 'px'; });
}
function measure(fn) {
const t0 = performance.now();
fn();
wrap.offsetHeight; // শেষের layout-টিও হিসাবে ধরা
return (performance.now() - t0).toFixed(1) + 'ms';
}
(function go() { // পাতা সাজানো শুরু হওয়া পর্যন্ত অপেক্ষা
if (!document.body.offsetWidth) return setTimeout(go, 50);
console.log('layout thrashing: ' + measure(thrash));
console.log('batched: ' + measure(batched));
})();প্রিভিউতে ৬০০টি ছোট সবুজ বাক্স, flex দিয়ে সারিতে সাজানো — একটির প্রস্থ বদলালে পুরো সারি নতুন করে সাজাতে হয়। নিচে দুটি সময়: thrash()-এ ৬০০ বার layout হয়েছে, তাই সময় অনেক বেশি; batched()-এ মাত্র দুবার — একবার সব পড়ার শুরুতে, একবার শেষে। চেষ্টা করুন: (১) 600-কে 1500 করুন — thrashing-এর সময় আরও দ্রুত বাড়ে, কারণ প্রতিটি layout-এ হিসাবের element-ও বেশি। (২) thrash()-এ d.offsetWidth-এর বদলে 20 লিখুন — পড়া নেই, তাই thrashing-ও নেই, সময় batched-এর কাছাকাছি।
আরও গভীরে: Chromium-এর rendering ইঞ্জিন প্রতিটি element-এর জন্য “style dirty” আর “layout dirty” চিহ্ন রাখে। style বদলালে শুধু চিহ্ন বসে, আসল হিসাব হয় পরের ফ্রেমের আগে; কিন্তু layout-নির্ভর কোনো মাপ পড়লে চিহ্নিত অংশের হিসাব তখনই করতে হয়। contain আর content-visibility এই চিহ্নের ছড়িয়ে পড়াকে একটি বাক্সের ভেতরে আটকে রাখে, তাই ব্রাউজার পুরো পাতা না ঘুরে ছোট একটি অংশ হিসাব করে। আর performance.now()-এর মাপ ইচ্ছা করেই পুরো নিখুঁত নয় — ২০১৮ সালের Spectre নিরাপত্তা-সমস্যার পর ব্রাউজারগুলো ঘড়ির সূক্ষ্মতা কমিয়েছে, যাতে সময়ের পার্থক্য দেখে গোপন তথ্য আন্দাজ করা না যায়।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা ফ্রন্ট-এন্ড ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে:
- Core Web Vitals কী কী, আর ভালো সীমা কত? — LCP ≤ ২.৫ সেকেন্ড, INP ≤ ২০০ মিলিসেকেন্ড, CLS ≤ ০.১, ৭৫তম পারসেন্টাইলে।
- CSS কেন render-blocking, আর কীভাবে কমাবেন? — সাজসজ্জাহীন পাতার ঝলক ঠেকাতে; ছোট CSS, critical CSS inline,
mediaattribute,@importএড়ানো। - Lab আর field data-র পার্থক্য? — lab একটি নিয়ন্ত্রিত পরীক্ষা (Lighthouse); field আসল ব্যবহারকারীর মাপ (CrUX), র্যাঙ্কিংয়ে এটাই গোনা হয়।
deferআরasync-এর পার্থক্য? — দুটিই parser-কে আটকায় না;deferHTML শেষে ক্রম মেনে চলে,asyncনামামাত্র ক্রম ছাড়া।- Layout thrashing কী? — লুপে style লেখার পরপর layout-এর মাপ পড়ে বারবার জোর করে layout করানো; সমাধান আগে সব পড়া, পরে সব লেখা।
৯ · DOM-এর আকার, third-party script আর একটি চেকলিস্ট
কম element, কম কাজ
প্রতিটি element ব্রাউজারের কাছে একটি বাড়তি হিসাব: style মেলানো, layout, paint, আর মেমরি। অপ্রয়োজনীয় <div>-এর স্তরের পর স্তর (“div-soup”) পাতা বড় করে, আর প্রতিটি ক্লিকের পরের style হিসাবকেও ভারী করে — যা INP-তে ধরা পড়ে। DOM অনেক বড় হলে Lighthouse “excessive DOM size” সতর্কতা দেয়। অর্থবহ HTML (পর্ব ১০) সাধারণত ছোটও হয়।
একটি লুকানো ভার হলো একই inline SVG আইকন বারবার। ৫০টি কার্ডে একই আইকনের পুরো <path> মানে ৫০ গুণ HTML. সমাধান পর্ব ৪৪-এর: একবার <symbol> হিসেবে রেখে প্রতিটি জায়গায় <use href="#cart">, অথবা আইকনটি আলাদা .svg ফাইল হিসেবে <img>-এ — তখন সেটি একবার নেমে ক্যাশে থাকে।
Third-party script — অন্যের কোড, আপনার খরচ
বিজ্ঞাপন, analytics, চ্যাট উইজেট, সোশ্যাল শেয়ার বোতাম, ভিডিও embed — এগুলো third-party script: অন্য কোম্পানির সার্ভার থেকে আসা কোড। এদের আকার আর আচরণ আপনার হাতে নেই; একটি script আরও কয়েকটি script ডেকে আনতে পারে, main thread-এ long task চালাতে পারে, দেরিতে জায়গা নিয়ে CLS বাড়াতে পারে। এই সাইটটিও Google AdSense বিজ্ঞাপন আর analytics ব্যবহার করে — বিজ্ঞাপনই কোর্সগুলোকে বিনামূল্যে রাখে। তাই প্রশ্নটি “থাকবে কি না” নয়, “কীভাবে কম খরচে”:
asyncবাdefer— কোনো third-party script যেন parser না আটকায়। AdSense আর Google Analytics-এর নিজেদের দেওয়া কোডেওasyncথাকে।- জায়গা আগে রাখুন — বিজ্ঞাপন বা embed-এর জন্য আন্দাজি
min-height, যাতে এলে লেখা না লাফায় (অংশ ৩-এর প্রিভিউ)। - দরকার হলে তবেই লোড — YouTube ভিডিওর জায়গায় শুরুতে শুধু ছবি আর play বোতাম (facade), চাপ দিলে তবেই আসল
<iframe>. - হিসাব রাখুন — মাঝে মাঝে তালিকা করুন কোন script আছে আর এখনো কাজে লাগে কি না; ভুলে যাওয়া tracking script নীরব শত্রু।
প্রকাশের আগে — পারফরম্যান্সের চেকলিস্ট
বড় দলে এই সীমাগুলো আগে থেকে লিখে রাখা হয় — একে বলে performance budget. ছোট প্রজেক্টে নিচের তালিকাটিই যথেষ্ট:
১০ · AI-এর যুগে — AI-এর “দ্রুত করার” পরামর্শ যাচাই
“আমার সাইট দ্রুত করে দাও” — AI কয়েক সেকেন্ডে লম্বা তালিকা দেয়। অনেক পরামর্শ ঠিক, কিন্তু অভিজ্ঞতায় দেখা যায় কয়েকটি ভুল বারবার আসে, কারণ AI শিখেছে বহু বছরের পুরোনো ব্লগ থেকে, আর সে আপনার পাতা মেপে দেখেনি:
| AI যা বলতে পারে | সমস্যা | ঠিক কী |
|---|---|---|
| “FID ১০০ মিলিসেকেন্ডের নিচে রাখুন” | FID আর Core Web Vital নয় | INP ≤ ২০০ মিলিসেকেন্ড |
সব ছবিতে loading="lazy" | প্রধান ছবিও দেরিতে, LCP খারাপ | প্রথম পর্দার ছবি eager, fetchpriority="high" |
| সব CSS ফাইল এক করে দিন, sprite বানান | HTTP/1-এর যুগের পরামর্শ; HTTP/2-তে অনেক ছোট ফাইল সমস্যা নয় | মেপে দেখুন; ক্যাশের সুবিধা মাথায় রাখুন |
সব ফন্ট আর সব ছবি preload | সবকিছু জরুরি মানে কিছুই জরুরি নয় | শুধু LCP ছবি আর একটি মূল ফন্ট |
সব element-এ will-change: transform বা translateZ(0) | GPU মেমরি নষ্ট, উল্টো ধীর | মেপে, শুধু যা নড়ে |
| “Lighthouse-এ ১০০ মানেই কাজ শেষ” | ল্যাবের এক মুহূর্তের মাপ | field data (CrUX) দেখুন |
AI-কে কাজ দেওয়ার সময় আপনার আসল মাপ দিন আর সীমা বেঁধে দিন — তখন সে অনুমানের তালিকা না দিয়ে নির্দিষ্ট পরামর্শ দেয়:
AI-এর পারফরম্যান্স-পরামর্শ হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Google কেন FID বাদ দিয়ে INP আনল? একটি মাপ বদলানোয় ডেভেলপারদের কাজে কী বদলায়?
২০২০ সালে Core Web Vitals শুরু হয়েছিল LCP, FID আর CLS নিয়ে। FID মাপত শুধু একটি জিনিস: পাতার প্রথম interaction-এর সময় main thread কতক্ষণ ব্যস্ত ছিল, অর্থাৎ ব্রাউজার handler চালানো শুরু করতে কত দেরি করল। এতে তিনটি বড় ফাঁক ছিল।
- শুধু প্রথম interaction: পাতা খোলার পর প্রথম ক্লিক প্রায়ই সহজ কিছু — একটি মেনু খোলা। কিন্তু আসল ভোগান্তি আসে পরে: ফিল্টার চাপা, কার্টে যোগ করা, ফর্ম জমা দেওয়া।
- শুধু অপেক্ষা, কাজ নয়: handler নিজে ৫০০ মিলিসেকেন্ড চললেও FID তা ধরত না। ব্যবহারকারীর কাছে তো পুরো সময়টাই “কিছু হচ্ছে না”।
- আঁকার সময় বাদ: handler শেষে style, layout, paint — পর্দা না বদলানো পর্যন্ত সাড়া পৌঁছায়নি, কিন্তু FID এটুকুও মাপত না।
ফলে বেশিরভাগ সাইটের FID “ভালো” দেখাত, অথচ মানুষ ধীরগতির অভিযোগ করতেন — মাপটি আসল অভিজ্ঞতা ধরছিল না। INP পুরো ভিজিটের প্রায় সব interaction দেখে, আর প্রতিটির জন্য ইনপুটের অপেক্ষা, handler চলা আর পরের ছবি আঁকা — তিনটিই যোগ করে। Google প্রায় দুই বছর পরীক্ষামূলকভাবে INP চালিয়ে ডেভেলপারদের প্রস্তুত হওয়ার সময় দেয়, তারপর ২০২৪ সালের মার্চে আনুষ্ঠানিকভাবে FID-এর জায়গায় বসায়।
ডেভেলপারের কাজে এর প্রভাব বড়: শুধু পাতা খোলার সময়টুকু নয়, পুরো জীবনকাল নিয়ে ভাবতে হয়। বড় DOM, ভারী CSS selector, layout thrashing, ক্লিকের পরে চলা third-party script — আগে যেগুলো মাপে ধরা পড়ত না, এখন সেগুলো সরাসরি স্কোরে আসে।
মূল উপলব্ধি: কোনো মাপই আসল অভিজ্ঞতা নয়, তার একটি আন্দাজ। মাপ যখন অভিজ্ঞতা থেকে দূরে সরে যায়, তখন মাপ বদলানোই সঠিক; তাই মাপের পেছনে না ছুটে মানুষের অভিজ্ঞতার পেছনে ছুটুন — তাহলে মাপ বদলালেও কাজ বৃথা যায় না।
প্র ০২Lighthouse-এ স্কোর ৯৫, অথচ PageSpeed Insights-এর field data বলছে LCP “খারাপ” — দুটি কীভাবে একসাথে সত্য হতে পারে?
দুটি আলাদা প্রশ্নের উত্তর দিচ্ছে। Lighthouse বলে: “একটি নির্দিষ্ট নকল ফোনে, নকল নেটওয়ার্কে, খালি ক্যাশে, একবার খুললে কী হয়?” CrUX বলে: “গত ২৮ দিনে আসল মানুষেরা, তাঁদের আসল ফোন আর নেটওয়ার্কে, কী পেয়েছেন — আর তাঁদের ধীরতম এক-চতুর্থাংশের সীমা কোথায়?”
অমিলের সাধারণ কারণগুলো:
- যন্ত্র আর নেটওয়ার্ক: Lighthouse-এর নকল ফোন হয়তো আপনার আসল পাঠকের ফোনের চেয়ে দ্রুত। জেলা শহরের সন্ধ্যার ভিড়ের নেটওয়ার্ক কোনো ল্যাব পুরোপুরি নকল করতে পারে না।
- কোন পাতা, কোন পথে: আপনি হয়তো হোমপেজ মেপেছেন, অথচ বেশিরভাগ মানুষ আসেন সার্চ থেকে সরাসরি পণ্যের পাতায় — যার ছবি বড়, script বেশি।
- ক্যাশ আর redirect: সোশ্যাল মিডিয়ার লিংকে ক্লিক করলে প্রায়ই এক-দুটি redirect, আর ভেতরের অ্যাপ-ব্রাউজারে ক্যাশ খালি — TTFB বেড়ে যায়।
উল্টো অমিলও হয়: নিয়মিত পাঠকের কাছে CSS আর ফন্ট ক্যাশে থাকে, তাই field-এর LCP ল্যাবের চেয়ে ভালো আসে। আর INP-এর বেলায় ল্যাব কিছু বলতেই পারে না, কারণ Lighthouse পাতায় ক্লিক করে না — সে Total Blocking Time দিয়ে একটি আন্দাজ দেয় মাত্র।
পেশাদার কাজের ধারা তাই দুটিকে মিলিয়ে চলে: field data বলে সমস্যা আছে কি না আর কোথায় (কোন পাতা, কোন মাপ, মোবাইল নাকি ডেস্কটপ); lab বলে কেন (কোন ফাইল দেরিতে, কোন task লম্বা)। field-এ উন্নতি দেখতে কয়েক সপ্তাহ লাগে, কারণ CrUX ২৮ দিনের চলমান হিসাব।
মূল উপলব্ধি: থার্মোমিটার জ্বর মাপে, কারণ বলে না; আর ল্যাবের থার্মোমিটার আপনার নিজের ঘরের তাপ নয়। আসল মানুষের ডেটা দিয়ে সমস্যা চিনুন, ল্যাব দিয়ে কারণ খুঁজুন।
প্র ০৩Critical CSS inline করলে প্রথম পর্দা দ্রুত হয় — তাহলে পুরো CSS-ই inline করে দিলে আরও ভালো নয় কেন?
কারণ গতি শুধু প্রথম ভিজিটের বিষয় নয়, আর প্রতিটি byte-এর একটি দাম আছে। একটু হিসাব করা যাক।
প্রথম ভিজিট: একটি নতুন TCP সংযোগ শুরুতে একবারে অল্প ডেটাই পাঠাতে পারে — প্রচলিত সেটিংয়ে মোটামুটি ১৪ KB, তারপর ধাপে ধাপে বাড়ে (একে বলে slow start). তাই প্রথম পর্দার HTML আর critical CSS মিলিয়ে চাপা অবস্থায় প্রায় ১৪ KB-এর মধ্যে রাখতে পারলে, প্রথম দফাতেই ব্রাউজার আঁকার মতো সব পেয়ে যায়। এটি একটি মোটা দাগের নিয়ম, আইন নয় — HTTP/3, TLS আর সার্ভারের সেটিং এই হিসাব বদলায়। পুরো ২০০ KB CSS inline করলে HTML নিজেই বড় হয়ে যায়, আর প্রথম লেখা আসতে আরও কয়েক দফা লাগে — উল্টো ফল।
দ্বিতীয় ভিজিট আর পরের পাতা: আলাদা site.css একবার নামলে ক্যাশে থাকে; পরের প্রতিটি পাতায় শূন্য byte. inline CSS প্রতিটি HTML-এর সাথে প্রতিবার আবার আসে। যে সাইটে মানুষ পাঁচ-দশটি পাতা ঘোরেন (যেমন একটি কোর্স), সেখানে পুরো inline মানে প্রতিটি পাতায় বাড়তি ভার।
রক্ষণাবেক্ষণ: কোনটি “critical”, তা পাতা আর পর্দার মাপভেদে আলাদা, আর নকশা বদলালে বদলায় — তাই বড় সাইট build টুল দিয়ে স্বয়ংক্রিয়ভাবে বের করে।
ইতিহাস থেকে শিক্ষা: HTTP/1.1-এর যুগে প্রতিটি ফাইল ছিল ব্যয়বহুল, তাই সব CSS এক ফাইলে জোড়া, আইকন sprite-এ, ছোট ছবি data URL-এ — এগুলোই ছিল নিয়ম। ২০১৫ সালে HTTP/2 এসে একটি সংযোগে অনেক ফাইল একসাথে পাঠানো সহজ করে, আর “সব জোড়া দাও” পরামর্শ অনেকটাই পুরোনো হয়ে যায়। কৌশল বদলায় প্রযুক্তির সাথে; মূল প্রশ্ন একই থাকে — কোন byte কখন দরকার?
মূল উপলব্ধি: পারফরম্যান্সের প্রায় প্রতিটি কৌশল একটি trade-off: প্রথম ভিজিট বনাম পরের ভিজিট, গতি বনাম রক্ষণাবেক্ষণ। “সবসময় X করুন” শুনলে জিজ্ঞেস করুন — কার জন্য, কোন অবস্থায়, আর মেপে দেখেছেন কি?
প্র ০৪বিজ্ঞাপন আর analytics ছাড়া অনেক সাইট চলে না, অথচ এরাই পাতা ধীর করে — এই দ্বন্দ্বের সমাধান কী?
বাংলাদেশের অনেক সংবাদ সাইট, ব্লগ আর শিক্ষামূলক সাইট বিজ্ঞাপনের আয়ে চলে; ব্যবসার সাইট analytics ছাড়া জানে না কোন পাতা কাজ করছে। তাই “সব third-party বাদ দিন” বাস্তব পরামর্শ নয়। আবার উল্টো দিকে, ধীর পাতা মানে কম পাঠক, কম পাতা দেখা — অর্থাৎ শেষ পর্যন্ত কম বিজ্ঞাপন-আয়ও। দুটি লক্ষ্য আসলে একই দিকে টানে, যদি হিসাব করে চলা যায়।
পেশাদার পথগুলো:
- লোডের ক্রম: আসল লেখা আর প্রধান ছবি আগে; বিজ্ঞাপন আর analytics script
async, যাতে parser না আটকায়। ব্যবহারকারী আগে কনটেন্ট পান, বিজ্ঞাপন একটু পরে। - জায়গা আগে রাখা: বিজ্ঞাপনের সম্ভাব্য মাপে
min-height. বিজ্ঞাপন না এলে একটু ফাঁকা জায়গা থাকবে — কিন্তু লেখা লাফানোর চেয়ে সেটি অনেক কম বিরক্তিকর। CLS-এর সবচেয়ে বড় উৎস প্রায়ই এখানেই। - সংখ্যা সীমিত রাখা: প্রতিটি নতুন tracking pixel, heatmap টুল, চ্যাট উইজেট — যোগ করার আগে প্রশ্ন: এর তথ্য কি আসলে কেউ ব্যবহার করবেন?
- মেপে সিদ্ধান্ত: DevTools-এর Network প্যানেলে domain ধরে ফিল্টার করে, বা Performance প্যানেলে third-party-র ভাগ দেখে, প্রতিটি script-এর আসল খরচ জানা যায়।
নৈতিক একটি দিকও আছে: পাঠকের ডেটা-প্যাকেজের টাকায় যে byte নামে, তার একটি অংশ যদি তাঁর কোনো কাজে না লাগে, তা কমানোর দায়িত্ব সাইট-নির্মাতার।
মূল উপলব্ধি: পারফরম্যান্স আর আয় শত্রু নয়; অপরিকল্পিত third-party কোড দুটিরই শত্রু। প্রতিটি script-কে একটি খরচ হিসেবে দেখুন, যাকে নিজের জায়গা অর্জন করতে হয়।
প্র ০৫বাংলা ওয়েব ফন্ট কেন এত ভারী, আর বাংলা সাইটের জন্য ফন্টের সবচেয়ে ভালো কৌশল কী?
একটি সাধারণ Latin ফন্টে কয়েকশো glyph-ই যথেষ্ট। বাংলায় ব্যঞ্জন, স্বর, কার-চিহ্ন ছাড়াও আছে শত শত যুক্তাক্ষর — ক্ষ, জ্ঞ, ন্ত্র, স্ক্র — যাদের অনেকেরই আলাদা আঁকা রূপ। তার ওপর রেফ, য-ফলা, র-ফলার নানা অবস্থান আর OpenType-এর নিয়ম, যা বলে কোন অক্ষরের পরে কোন রূপ বসবে (এই কাজ করে HarfBuzz-এর মতো text shaping ইঞ্জিন, পর্ব ১৪)। ফলে ভালো বাংলা ফন্টে glyph অনেক বেশি, ফাইলও বড় — আর প্রতিটি weight আলাদা ফাইল।
একটু ইতিহাস: Unicode-এর আগে বাংলাদেশে বছরের পর বছর Bijoy-এর মতো ANSI-ভিত্তিক ফন্ট চলত, যেখানে বাংলা অক্ষর ইংরেজি অক্ষরের জায়গায় “লুকিয়ে” রাখা হতো। সেই লেখা ফন্ট ছাড়া অর্থহীন হয়ে যেত, খোঁজা যেত না। Unicode আর আধুনিক OpenType ফন্টে সেই সমস্যা মিটেছে, কিন্তু সঠিক যুক্তাক্ষরের দাম হিসেবে ফাইল বড় হয়েছে।
ভালো কৌশল কয়েক স্তরে:
- প্রথমে প্রশ্ন — ওয়েব ফন্ট লাগবেই কি? বেশিরভাগ Android ফোনে আর আধুনিক Windows ও Apple যন্ত্রে বাংলা ফন্ট আগে থেকেই আছে। লেখাপ্রধান পাতায়
system-uiবা সিস্টেমের বাংলা ফন্ট মানে শূন্য byte. - লাগলে — subset আর
unicode-range: বাংলা অক্ষর এক ফাইলে, Latin আরেকটিতে; যে পাতায় যেটি লাগে, শুধু সেটিই নামে। - কম weight বা variable font: চারটি স্থির weight-এর বদলে একটি variable ফাইল প্রায়ই মোট আকারে ছোট হয়।
font-display: swapআর মাপের মিল: fallback ফন্টের মাপ ওয়েব ফন্টের কাছাকাছি রাখলে swap-এর লাফ কমে;@font-face-এরsize-adjustএজন্যই।
মূল উপলব্ধি: বাংলা ওয়েবে সুন্দর টাইপোগ্রাফি আর দ্রুত পাতা দুটোই সম্ভব, যদি ফন্টকে “সাজসজ্জা” না ভেবে একটি ভারী resource হিসেবে পরিকল্পনা করেন — কোন অক্ষর, কোন weight, কখন, কোথা থেকে।
অনুশীলন
বেশিরভাগ কাজ মাপা আর পড়ার; একটিতে ছোট কোড ঠিক করতে হবে।
-
PageSpeed Insights পড়ুন: pagespeed.web.dev-এ আপনার পরিচিত যেকোনো একটি বড় বাংলাদেশি সংবাদ বা ই-কমার্স সাইটের ঠিকানা দিন। মোবাইল ট্যাবে ওপরের “আসল ব্যবহারকারী” অংশ আর নিচের Lighthouse অংশের LCP আর CLS পাশাপাশি লিখুন। দুটি মিলল কি? না মিললে সম্ভাব্য কারণ কী?
জনপ্রিয় সাইট হলে ওপরে CrUX-এর মাপ আসবে (কম ভিজিটের সাইটে “ডেটা নেই” দেখাতে পারে)। প্রায়ই দেখবেন দুটি আলাদা — যেমন Lighthouse-এ LCP ৬ সেকেন্ড অথচ field-এ ৩ সেকেন্ড, অথবা উল্টো। কারণ: Lighthouse খালি ক্যাশে একটি নির্দিষ্ট ধীর নকল যন্ত্রে মাপে; আসল পাঠকের অনেকের ক্যাশে ফাইল থাকে, যন্ত্রও আলাদা। বিজ্ঞাপন-ভারী সাইটে CLS প্রায়ই field-এ বেশি। ওপরের অংশে INP-ও দেখবেন, যা নিচের Lighthouse অংশে নেই — সেখানে তার জায়গায় Total Blocking Time.
-
LCP element খুঁজুন: যেকোনো একটি পাতা খুলে DevTools-এর Performance প্যানেলে যান। CPU throttling 4× দিয়ে রেকর্ড করুন আর পাতা reload করুন। টাইমলাইনে LCP চিহ্নটিতে চাপ দিয়ে দেখুন কোন element LCP. সেটি কি
<img>, background ছবি, নাকি লেখা? আর তাতেloading="lazy"আছে কি না, Elements প্যানেলে দেখুন।Performance প্যানেলের Timings সারিতে LCP চিহ্নে চাপ দিলে নিচের Summary-তে সংশ্লিষ্ট element-টি দেখায়; সাম্প্রতিক Chrome-এ রেকর্ড ছাড়াই লাইভ অংশেও দেখা যায়। লেখাপ্রধান পাতায় প্রায়ই LCP একটি বড় অনুচ্ছেদ বা শিরোনাম — তখন ফন্টই বড় বিষয়। ছবি হলে দেখুন: HTML-এ আছে কি না (CSS background হলে scanner খুঁজে পায় না), lazy কি না,
fetchpriorityআছে কি না। LCP-এর চার টুকরোর মধ্যে যেটি সবচেয়ে বড়, সেখান থেকে শুরু করুন। -
একটি খারাপ head ঠিক করুন: নিচের কোডে অন্তত পাঁচটি পারফরম্যান্স-সমস্যা খুঁজুন, আর ঠিক করা সংস্করণ লিখুন।
<head> <link rel="stylesheet" href="style.css"> <!-- style.css-এ: @import "fonts.css"; --> <link rel="stylesheet" href="print.css"> <script src="slider.js"></script> <script src="https://example-ads.com/ads.js"></script> </head> <body> <img src="hero.jpg" loading="lazy" alt="ঈদ অফার">সমস্যা: (১)
style.css-এর ভেতরে@import— বাড়তি এক দফা অপেক্ষা; (২)print.css-এmedia="print"নেই, তাই অকারণে render আটকায়; (৩)slider.jsparser আটকায় —deferচাই; (৪) বিজ্ঞাপনের script parser আটকায় —asyncচাই; (৫) প্রধান ছবিতেloading="lazy"— LCP দেরি; (৬) ছবিতেwidth/heightনেই — CLS; (৭) পুরোনো JPEG,fetchpriorityনেই।<head> <link rel="stylesheet" href="fonts.css"> <!-- @import সরিয়ে পাশাপাশি --> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="print.css" media="print"> <script src="slider.js" defer></script> <script src="https://example-ads.com/ads.js" async></script> </head> <body> <picture> <source srcset="hero.avif" type="image/avif"> <img src="hero.jpg" alt="ঈদ অফার" width="1200" height="600" fetchpriority="high"> </picture> -
অব্যবহৃত CSS মাপুন: DevTools-এ Ctrl+Shift+P চেপে “Coverage” লিখে Coverage প্যানেল খুলুন, reload বোতাম চাপুন। আপনার নিজের বা যেকোনো সাইটের প্রধান CSS ফাইলের কত শতাংশ এই পাতায় অব্যবহৃত? সেটি কি সব মুছে ফেলার যুক্তি?
প্রায়ই দেখবেন CSS-এর অর্ধেকের বেশি এই পাতায় ব্যবহার হয়নি (লাল দাগ)। কিন্তু এর মানে মুছে ফেলা নয়: সেই নিয়মগুলো হয়তো অন্য পাতায়, ছোট পর্দায়, hover-এ, dialog খুললে, বা dark mode-এ লাগে — Coverage শুধু এই মুহূর্ত পর্যন্ত যা লেগেছে তা দেখায়। এটি সিদ্ধান্তের তথ্য: প্রথম পর্দায় কতটুকু লাগে (critical CSS-এর আকার), কোনো বড় অংশ কি শুধু একটি পাতার (তাহলে সেটি আলাদা ফাইলে), কোনো পুরোনো library-র পুরো CSS কি অকারণে আসছে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি পাতার প্রথম ছবি পর্দায় আসার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📄 সার্ভার থেকে HTML-এর প্রথম byte আসে
- 🔍 HTML পড়ে DOM তৈরি, scanner CSS-এর ঠিকানা খুঁজে নামায়
- 🎨 CSS এসে CSSOM তৈরি হয়
- 🌳 DOM আর CSSOM মিলে render tree, তারপর layout
- 🖌️ Paint — পর্দায় প্রথম লেখা বা ছবি (FCP)
২০২৪ সালের মার্চে কোন মাপ FID-এর জায়গায় Core Web Vitals-এ যুক্ত হয়?
- LCP
- TTFB
- INP
- CLS
<img>-এ width আর height না দিলে সবচেয়ে সরাসরি কোন মাপ খারাপ হয়?
- CLS
- INP
- TTFB
- ছবির ফাইলের আকার
প্রথম পর্দার বড় hero ছবিটি (যা LCP) কীভাবে লেখা সবচেয়ে ভালো?
loading="lazy"দিয়ে, যাতে ডেটা বাঁচে- CSS-এর
background-imageহিসেবে - JavaScript দিয়ে পরে বসিয়ে
- HTML-এ
<img>,fetchpriority="high",width/height-সহ
<link rel="stylesheet" href="print.css" media="print"> — সাধারণ পর্দায় পাতা খোলার সময় ব্রাউজার কী করে?
- ফাইলটি কখনোই নামায় না
- কম অগ্রাধিকারে নামায়, কিন্তু render আটকায় না
- নামানো শেষ না হওয়া পর্যন্ত render আটকে রাখে
- পর্দাতেই এর নিয়ম প্রয়োগ করে
content-visibility: auto-এর সাথে contain-intrinsic-size দেওয়া হয় কেন?
- যাতে ব্রাউজার অংশটি আরও দ্রুত আঁকে
- যাতে লেখা Ctrl+F দিয়ে খোঁজা যায়
- যাতে না-আঁকা অংশের একটি আন্দাজি মাপ থাকে আর scrollbar না লাফায়
- যাতে পুরোনো ব্রাউজারেও content-visibility কাজ করে
Lighthouse-এর মতো lab টুল আর CrUX-এর মতো field data-র সবচেয়ে বড় পার্থক্য কোনটি?
- Lab নিয়ন্ত্রিত নকল পরীক্ষা; field আসল ব্যবহারকারীদের মাপ
- দুটি সবসময় একই সংখ্যা দেয়
- শুধু lab টুলই INP মাপতে পারে
- Field data শুধু ডেস্কটপের জন্য
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- গতি মানুষের জন্য: ধীর নেটওয়ার্ক আর দুর্বল ফোনেই আসল পরীক্ষা; আগে মাপুন, তারপর ঠিক করুন, আবার মাপুন।
- Core Web Vitals: LCP ≤ ২.৫ সেকেন্ড, INP ≤ ২০০ মিলিসেকেন্ড (২০২৪ থেকে FID-এর জায়গায়), CLS ≤ ০.১ — ৭৫তম পারসেন্টাইলে, field data-য়।
- CSS render আটকায়: ছোট আর minify করা CSS,
@importনয়, প্রথম পর্দার critical CSS,media="print", Brotli/gzip, আর version-সহ দীর্ঘ ক্যাশ। - ফন্ট ও ছবি:
font-display: swap, subset আর কম weight; ছবিতেwidth/height, AVIF/WebP,srcset, নিচে lazy, প্রধান ছবিতেfetchpriority="high". - আঁকার খরচ:
contain,content-visibility: auto+contain-intrinsic-size, দামি ছায়া-blur সীমিত, layout thrashing নয়, ছোট DOM, আর প্রতিটি third-party script-এর হিসাব।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫০ · Sass আর PostCSS — CSS-এর সহকারী টুলপরবর্তী পর্বminify, autoprefix আর CSS গোছানোর টুল।
- HTML ও CSS · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- পর্ব ৩২ · রেসপন্সিভ ছবিফিরে দেখুন
srcset,sizes,<picture>আর lazy loading বিস্তারিত। - JavaScript · পারফরম্যান্স ও মেমরিপাশের ট্র্যাকINP-এর JavaScript দিক — long task ভাগ করা আর মেমরি।
- কম্পিউটার ফান্ডামেন্টালস · ইন্টারনেট কীভাবে কাজ করেভিত্তিDNS, TCP আর HTTP — TTFB-এর পেছনের পথ।