Γιατί ο ιστότοπός σας πρέπει να στέλνει λιγότερα αιτήματα, όχι μικρότερα
Τα μικροσκοπικά αρχεία δεν είναι δωρεάν. Το σύγχρονο HTTP έκανε το κόστος των αιτημάτων μικρότερο, όχι ασήμαντο.
Πίνακας περιεχομένων
- Ο βολικός μύθος του «απλώς κάντε κάθε αρχείο μικρότερο»
- Τα αιτήματα δεν είναι απλώς bytes
- «Μα το HTTP/2 το διόρθωσε αυτό», κυρίως όχι
- Ο καταρράκτης είναι εκεί όπου βρίσκεται η αλήθεια
- Τα μικρότερα αρχεία εξακολουθούν να έχουν σημασία — απλώς όχι στον ίδιο βαθμό
- Το κρυφό κόστος των πολλών μικρών αρχείων
- 1. Καθυστερημένη ανακάλυψη
- 2. Κόστος headers
- 3. Διακοπή του main thread
- 4. Πολυπλοκότητα cache
- Το bundling επιστρέφει, αλλά με κρίση
- Τα third-party αιτήματα αξίζουν επιπλέον καχυποψία
- Μια πρακτική λίστα ελέγχου για μείωση αιτημάτων
- Αφαιρέστε
- Συνδυάστε με σκέψη
- Αναβάλετε
- Κάντε σωστό caching
- Μετρήστε ξανά
- Πώς μοιάζει το καλό
Ο βολικός μύθος του «απλώς κάντε κάθε αρχείο μικρότερο»
Για χρόνια, οι συμβουλές για την απόδοση στο web ακούγονταν απλές: συμπιέστε τα πάντα, κάντε minify τα πάντα, κάντε κάθε asset μικρότερο.
Αυτή η συμβουλή παραμένει σε γενικές γραμμές σωστή. Ένα script 40 KB είναι συνήθως καλύτερο από ένα script 400 KB. Μια βελτιστοποιημένη εικόνα είναι καλύτερη από μια ακατέργαστη εξαγωγή. Brotli, AVIF, CSS minification, tree shaking και font subsetting έχουν όλα σημασία.
Όμως σε πολλούς ιστότοπους παραγωγής, το μεγαλύτερο πρόβλημα δεν είναι πλέον ένα υπερμεγέθες αρχείο. Είναι ο αριθμός των πραγμάτων που πρέπει να ζητήσει ο browser πριν η σελίδα γίνει αισθητά χρησιμοποιήσιμη.
Μια σελίδα μπορεί να φαίνεται πειθαρχημένη ως προς τα μεγέθη αρχείων και παρ’ όλα αυτά να είναι αργή επειδή στέλνει 120 αιτήματα: τμήματα CSS, JavaScript chunks, third-party tags, αρχεία γραμματοσειρών, tracking pixels, icon sprites, JSON endpoints, preloads, analytics beacons και cache revalidations. Κάθε ένα μπορεί να είναι «μικρό». Μαζί, δημιουργούν έναν μακρύ, εύθραυστο καταρράκτη.
Ο πρακτικός κανόνας είναι αυτός: μόλις τα επιμέρους assets είναι εύλογα συμπιεσμένα, η μείωση του αριθμού των αιτημάτων συχνά βελτιώνει την εμπειρία χρήστη περισσότερο από το να αφαιρείτε λίγα kilobytes από κάθε αρχείο.
Τα αιτήματα δεν είναι απλώς bytes
Ένα δικτυακό αίτημα δεν είναι μόνο μια μεταφορά δεδομένων. Είναι μια ακολουθία εργασιών.
Ο browser πρέπει να ανακαλύψει τον πόρο, να αποφασίσει πότε θα τον ανακτήσει, να τον προγραμματίσει σε σχέση με άλλους πόρους, να στείλει headers, να περιμένει τον server, να λάβει headers, να αναλύσει την απόκριση, συχνά να την αποσυμπιέσει και μετά να κάνει κάτι χρήσιμο με αυτήν.
Αυτό το «κάτι χρήσιμο» μπορεί να είναι ακριβό. Ένα αρχείο JavaScript πρέπει να γίνει parsed, compiled και executed. Ένα αρχείο CSS μπορεί να μπλοκάρει το rendering. Ένα αρχείο γραμματοσειράς μπορεί να καθυστερήσει το αναγνώσιμο κείμενο ή να προκαλέσει layout shifts. Μια εικόνα μπορεί να επηρεάσει το στοιχείο Largest Contentful Paint. Ένα third-party script μπορεί να φέρει τη δική του αλυσίδα εξαρτήσεων.
Γι’ αυτό ο αριθμός αιτημάτων εξακολουθεί να έχει σημασία ακόμη κι όταν τα αρχεία είναι μικρά. Ένα script 3 KB μπορεί να είναι χειρότερο από μια εικόνα 30 KB αν μπλοκάρει το rendering, φτάνει αργά και εκτελείται στο main thread τη λάθος στιγμή.
Αν διαβάζετε μια αναφορά Lighthouse και νιώθετε ότι σας τιμωρεί με δεκάδες ξεχωριστές προειδοποιήσεις, ξεκινήστε κοιτάζοντας τον καταρράκτη αιτημάτων αντί για τις επιμέρους βαθμολογίες. Έχουμε έναν πρακτικό οδηγό στο πώς να διαβάζετε μια αναφορά Lighthouse χωρίς πανικό, αλλά η σύντομη εκδοχή είναι: βρείτε τι μπλοκάρει το πρώτο render και τι καθυστερεί το κύριο περιεχόμενο.
«Μα το HTTP/2 το διόρθωσε αυτό», κυρίως όχι
Το HTTP/2 και το HTTP/3 άλλαξαν τα οικονομικά των αιτημάτων. Εισήγαγαν multiplexing, συμπίεση headers και καλύτερη συμπεριφορά σύνδεσης. Με απλά λόγια, οι browsers έγιναν πολύ καλύτεροι στο να στέλνουν πολλαπλά αιτήματα πάνω από λιγότερες συνδέσεις.
Αυτό ήταν πραγματική βελτίωση. Επίσης σκότωσε κάποιες παλιές συνήθειες, όπως τα ακραία CSS sprites και τα τεράστια concatenated bundles που φτιάχνονταν μόνο για να αποφευχθούν τα όρια συνδέσεων.
Όμως το HTTP/2 δεν έκανε τα αιτήματα δωρεάν.
Το multiplexing βοηθά όταν πολλοί πόροι μοιράζονται μια σύνδεση, αλλά ο browser εξακολουθεί να πρέπει να τους ιεραρχήσει. Οι servers εξακολουθούν να πρέπει να απαντήσουν. Ο client εξακολουθεί να πρέπει να επεξεργαστεί κάθε απόκριση. Συμφόρηση, απώλεια πακέτων, TLS negotiation, DNS lookup, cache misses και πίεση στο main thread εξακολουθούν να υπάρχουν.
Το HTTP/3 βελτιώνει κάποια συμπεριφορά μεταφοράς, ειδικά γύρω από connection migration και head-of-line blocking στο επίπεδο μεταφοράς. Δεν αφαιρεί το κόστος ανακάλυψης, προγραμματισμού, λήψης, parsing και εκτέλεσης πόρων.
Άρα ο σύγχρονος στόχος δεν είναι «βάλτε τα πάντα σε ένα τεράστιο αρχείο». Είναι «στείλτε λιγότερα κρίσιμα αιτήματα και κάντε τα υπόλοιπα αιτήματα σκόπιμα».
Ο καταρράκτης είναι εκεί όπου βρίσκεται η αλήθεια
Τα προβλήματα απόδοσης σπάνια αναγγέλλουν την παρουσία τους σε ένα μόνο metric. Εμφανίζονται ως σχήμα.
Ανοίξτε το network panel του browser και κοιτάξτε τα πρώτα λίγα δευτερόλεπτα. Ρωτήστε:
- Πόσα αιτήματα ξεκινούν πριν εμφανιστεί το κύριο περιεχόμενο;
- Ποια αιτήματα μπλοκάρουν το rendering;
- Ανακαλύπτονται αργά σημαντικοί πόροι;
- Ανταγωνίζονται third-party scripts με first-party CSS, γραμματοσειρές ή εικόνες;
- Επιστρέφουν πολλά αρχεία αποκρίσεις 304 αντί να σερβίρονται απευθείας από την cache;
- Είναι icons, γραμματοσειρές ή τμήματα UI χωρισμένα σε περισσότερα αρχεία απ’ όσα χρειάζεται η σελίδα;
Μια γρήγορη σελίδα έχει συνήθως έναν βαρετό αρχικό καταρράκτη. Ένας μικρός αριθμός κρίσιμων πόρων φτάνει νωρίς. Οι μη κρίσιμοι πόροι περιμένουν. Τα third-party scripts καθυστερούν, περιορίζονται ή αφαιρούνται. Ο browser δεν αναγκάζεται να διαχειριστεί είκοσι προτεραιότητες πριν μπορέσει να ζωγραφίσει τη σελίδα.
Μια αργή σελίδα συχνά έχει έναν νευρικό καταρράκτη: πολλά μικρά αρχεία, πολλές προελεύσεις και πολλές καθυστερημένες ανακαλύψεις.
Τα μικρότερα αρχεία εξακολουθούν να έχουν σημασία — απλώς όχι στον ίδιο βαθμό
Αυτό δεν είναι επιχείρημα κατά της συμπίεσης ή της βελτιστοποίησης. Είναι επιχείρημα κατά της βελτιστοποίησης των bytes ενώ αγνοείται ο συντονισμός.
Τα μικρότερα αρχεία έχουν τη μεγαλύτερη σημασία όταν ο πόρος είναι μεγάλος, render-blocking ή μέρος της διαδρομής του κύριου περιεχομένου. Για παράδειγμα:
- Η hero image πρέπει να έχει σωστό μέγεθος και encoding.
- Το render-blocking CSS πρέπει να είναι λιτό.
- Το JavaScript που χρειάζεται για την πρώτη αλληλεπίδραση πρέπει να είναι ελάχιστο.
- Οι γραμματοσειρές πρέπει να είναι subset, συμπιεσμένες και περιορισμένες στα weights που πράγματι χρησιμοποιούνται.
Οι γραμματοσειρές είναι ένα συνηθισμένο παράδειγμα. Οι ομάδες συχνά εμμονικά ασχολούνται με το αν ένα αρχείο γραμματοσειράς είναι 24 KB ή 31 KB, ενώ στέλνουν έξι weights, δύο styles και πολλαπλές οικογένειες. Η καλύτερη λύση δεν είναι να αφαιρέσετε 7 KB από ένα αρχείο. Είναι να στείλετε λιγότερα αρχεία γραμματοσειρών. Αν η τυπογραφία είναι μέρος της δουλειάς σας για την απόδοση, οι web fonts παραμένουν μία από τις πιο εύκολες νίκες στους περισσότερους ιστότοπους.
Οι εικόνες ακολουθούν το ίδιο μοτίβο. Το AVIF ή το WebP μπορούν να εξοικονομήσουν σημαντικά bytes, αλλά το να στέλνετε δέκα διακοσμητικές εικόνες above the fold παραμένει κακό σχέδιο. Επιλέξτε καλύτερα formats, ναι, αλλά αμφισβητήστε επίσης αν κάθε εικόνα χρειάζεται να ζητηθεί καθόλου. Για αποφάσεις σχετικά με formats, ο οδηγός μας για το πότε το AVIF ξεπερνά το WebP και πότε όχι είναι χρήσιμος συνοδός σε αυτή τη δουλειά με τον αριθμό αιτημάτων.
Το κρυφό κόστος των πολλών μικρών αρχείων
Πολλά μικρά αιτήματα τείνουν να δημιουργούν προβλήματα που δεν φαίνονται αν κοιτάζετε μόνο τα συνολικά μεταφερθέντα bytes.
1. Καθυστερημένη ανακάλυψη
Οι browsers δεν μπορούν να ζητήσουν κάτι που δεν έχουν ανακαλύψει. Ένα αρχείο CSS μπορεί να αναφέρεται σε μια γραμματοσειρά. Ένα script μπορεί να εισάγει ένα άλλο script. Ένα component μπορεί να ζητήσει JSON μετά το hydration. Κάθε εξάρτηση δημιουργεί ένα ακόμη βήμα στην αλυσίδα.
Όσο βαθύτερη είναι η αλυσίδα, τόσο αργότερα ξεκινά η σημαντική δουλειά.
2. Κόστος headers
Κάθε αίτημα και απόκριση περιλαμβάνει headers. Η συμπίεση headers βοηθά, ειδικά πάνω από HTTP/2 και HTTP/3, αλλά δεν εξαλείφει το κόστος. Τα cookies μπορούν να το κάνουν πολύ χειρότερο. Αν ο ιστότοπός σας στέλνει μεγάλα cookies με κάθε αίτημα, τα μικροσκοπικά assets γίνονται στην πράξη λιγότερο μικροσκοπικά.
Αυτός είναι ένας λόγος για τον οποίο τα static assets πρέπει συχνά να βρίσκονται σε cookie-free paths ή domains, και γιατί τα cache headers αξίζουν προσοχή. Αν τα headers συμπεριφέρονται περίεργα στην παραγωγή, το debugging redirects και HTTP headers είναι συνήθως ταχύτερο από τις εικασίες.
3. Διακοπή του main thread
Πολλά JavaScript chunks μπορούν να δημιουργήσουν επαναλαμβανόμενη δουλειά parsing και εκτέλεσης. Ακόμη κι αν κάθε chunk είναι μικρό, ο browser μπορεί να συνεχίζει να σταματά για να αξιολογήσει κώδικα. Αυτό μπορεί να βλάψει το Interaction to Next Paint και να κάνει τη σελίδα να μοιάζει ασταθής.
Ο χρήστης δεν νοιάζεται ότι κάθε αρχείο ήταν μικρό. Τον νοιάζει ότι το πάτημα ενός μενού πήρε 600 milliseconds.
4. Πολυπλοκότητα cache
Το splitting των assets μπορεί να βελτιώσει το caching όταν γίνεται προσεκτικά. Ένα σταθερό vendor bundle και ένα μεταβαλλόμενο app bundle μπορεί να είναι καλός διαχωρισμός.
Όμως το υπερβολικό chunking μπορεί να γυρίσει μπούμερανγκ. Περισσότερα αρχεία σημαίνουν περισσότερα cache lookups, περισσότερες ευκαιρίες revalidation, περισσότερο συντονισμό εκδόσεων και περισσότερους τρόπους να ακυρώσετε κατά λάθος πόρους που δεν χρειαζόταν να αλλάξουν.
Το bundling επιστρέφει, αλλά με κρίση
Η πρώτη εποχή της απόδοσης στο web αγαπούσε το bundling επειδή οι browsers είχαν αυστηρά όρια συνδέσεων. Μετά ήρθε το HTTP/2 και πολλές ομάδες κινήθηκαν έντονα προς επιθετικό code splitting. Κάποιο από αυτό ήταν χρήσιμο. Κάποιο έγινε δεισιδαιμονία.
Η λογική μέση λύση είναι το route-aware bundling.
Για έναν τυπικό marketing site ή content site:
- Κάντε inline ή φορτώστε μόνο το CSS που χρειάζεται για το αρχικό rendering.
- Κρατήστε το global JavaScript μικρό.
- Αποφύγετε να χωρίζετε μικροσκοπικά modules σε ξεχωριστά δικτυακά αιτήματα.
- Καθυστερήστε διαδραστικές λειτουργίες που δεν χρειάζονται αμέσως.
- Αφαιρέστε third-party scripts που δεν δικαιολογούν το κόστος τους.
Για μια εφαρμογή:
- Κάντε split ανά route ή κύρια λειτουργία, όχι ανά κάθε component.
- Κρατήστε τις κοινές εξαρτήσεις σταθερές και cacheable.
- Κάντε preload μόνο πόρους που σίγουρα θα χρειαστούν σύντομα.
- Αποφύγετε να φορτώνετε κώδικα admin, dashboard, editor ή experiment σε δημόσιες σελίδες.
- Μετρήστε το κόστος αλληλεπίδρασης, όχι μόνο το bundle size.
Το bundling δεν είναι αυτόματα καλό. Το code splitting δεν είναι αυτόματα καλό. Η χρήσιμη ερώτηση είναι: βοηθά αυτός ο διαχωρισμός τον browser να παραδώσει την επόμενη ουσιαστική εμπειρία χρήστη νωρίτερα;
Τα third-party αιτήματα αξίζουν επιπλέον καχυποψία
Τα first-party αιτήματα είναι τουλάχιστον υπό τον έλεγχό σας. Τα third-party αιτήματα είναι συχνά πιο αργά, λιγότερο προβλέψιμα και πιο ακριβά απ’ όσο φαίνονται.
Ένας μόνο tag manager μπορεί να ενεργοποιήσει analytics, ads, heatmaps, chat widgets, A/B testing, consent tools και personalization scripts. Κάθε vendor μπορεί να φέρει περισσότερα αιτήματα. Κάποια θα τρέξουν νωρίς. Κάποια θα μπλοκάρουν το main thread. Κάποια θα αλλάξουν χωρίς τη δική σας διαδικασία release.
Η καλύτερη βελτιστοποίηση third-party είναι η διαγραφή. Η δεύτερη καλύτερη είναι η καθυστέρηση.
Πριν προσθέσετε ένα third-party script, ρωτήστε:
- Χρειάζεται αυτό να φορτώσει πριν ο χρήστης δει τη σελίδα;
- Χρειάζεται να φορτώνει σε κάθε σελίδα;
- Μπορεί να φορτώσει μετά από consent, αλληλεπίδραση ή idle time;
- Ποιος το κατέχει εσωτερικά;
- Ποιο metric αποδεικνύει ότι αξίζει το κόστος απόδοσης;
Εδώ η απόδοση γίνεται διακυβέρνηση. Κάποιος πρέπει να έχει το δικαίωμα να πει όχι.
Μια πρακτική λίστα ελέγχου για μείωση αιτημάτων
Ξεκινήστε με τις σελίδες που έχουν τη μεγαλύτερη σημασία: homepage, pricing page, product page, checkout, signup ή κορυφαίες landing pages. Έπειτα δουλέψτε μέσα από τον καταρράκτη.
Αφαιρέστε
- Διαγράψτε αχρησιμοποίητο JavaScript και CSS.
- Αφαιρέστε παλιά experiments, εγκαταλελειμμένα pixels και duplicate analytics.
- Πετάξτε αχρησιμοποίητα font weights και icon libraries.
- Αντικαταστήστε διακοσμητικές εικόνες με CSS όπου ταιριάζει.
Συνδυάστε με σκέψη
- Κάντε bundle μικροσκοπικά JavaScript modules που φορτώνουν πάντα μαζί.
- Συγχωνεύστε μικρά αρχεία CSS που μπλοκάρουν την ίδια διαδρομή rendering.
- Χρησιμοποιήστε SVG sprites ή inline SVG για επαναλαμβανόμενα icons όταν αυτό μειώνει τα αιτήματα χωρίς να βλάπτει τη συντηρησιμότητα.
Αναβάλετε
- Κάντε lazy-load εικόνες below-the-fold.
- Καθυστερήστε μη κρίσιμα scripts μέχρι μετά το first paint ή την αλληλεπίδραση χρήστη.
- Φορτώστε comments, embeds, maps, chat και video players μόνο όταν χρειάζονται.
Κάντε σωστό caching
- Χρησιμοποιήστε long-lived caching για versioned static assets.
- Αποφύγετε περιττό revalidation για αρχεία που σπάνια αλλάζουν.
- Κρατήστε το HTML φρέσκο, αλλά αφήστε τα hashed assets να παραμένουν cached.
Μετρήστε ξανά
Μετά από κάθε αλλαγή, ελέγξτε ξανά τον καταρράκτη. Ο στόχος δεν είναι η τέλεια βαθμολογία. Ο στόχος είναι λιγότερα κρίσιμα αιτήματα, νωρίτερο χρήσιμο rendering και λιγότερη διατάραξη του main thread.
Πώς μοιάζει το καλό
Μια υγιής σελίδα δεν έχει απαραίτητα τα λιγότερα δυνατά αιτήματα. Έχει μια μικρή, σκόπιμη κρίσιμη διαδρομή.
Ο browser παίρνει HTML, ουσιώδες CSS, την εικόνα κύριου περιεχομένου αν υπάρχει, ίσως ένα μικρό script που απαιτείται για πλοήγηση ή αλληλεπίδραση above-the-fold, και το ελάχιστο σύνολο γραμματοσειρών που χρειάζεται για να γίνει το κείμενο αναγνώσιμο. Όλα τα άλλα περιμένουν τη σειρά τους.
Αυτή είναι η διαφορά ανάμεσα σε μια σελίδα που είναι απλώς βελτιστοποιημένη και σε μια σελίδα που μοιάζει γρήγορη.
Η συρρίκνωση αρχείων εξακολουθεί να αξίζει. Αλλά αν ο ιστότοπος είναι ήδη εύλογα συμπιεσμένος, η επόμενη νίκη απόδοσης συνήθως δεν είναι άλλα 2 KB που σώζονται από ένα bundle. Είναι ένα λιγότερο blocking request, ένα λιγότερο αρχείο γραμματοσειράς, ένα λιγότερο third-party script, μία λιγότερη αλυσίδα εξαρτήσεων.
Λιγότερα αιτήματα κάνουν τη δουλειά του browser απλούστερη. Το απλό είναι γρήγορο πιο συχνά απ’ όσο μας αρέσει να παραδεχόμαστε.