Cache-Control — είναι μια κεφαλίδα HTTP που καθορίζει τους κανόνες αποθήκευσης πόρων στην πλευρά του πελάτη, των διαμεσολαβητών και των CDN με χρήση ενός συνόλου οδηγιών. Σε αντίθεση με την απαρχαιωμένη κεφαλίδα Expires, το Cache-Control υποστηρίζει δεκάδες συνδυασμούς: το max-age ορίζει το χρόνο ζωής σε δευτερόλεπτα, τα private και public διαχειρίζονται τη διαθεσιμότητα της cache, τα no-cache και no-store — εξαναγκασμένο έλεγχο. Σύμφωνα με το Google Web Dev (2025), η σωστή ρύθμιση του Cache-Control μπορεί να μειώσει το χρόνο φόρτωσης σελίδων κατά 50-80% για επαναλαμβανόμενες επισκέψεις. Αυτό καθιστά την κεφαλίδα κρίσιμη για τις επιδόσεις εφαρμογών ιστού και κινητών.
Βασικά σημεία
Cache-Control — είναι μια κεφαλίδα HTTP, τυποποιημένη στο HTTP/1.1 (RFC 7234), που επιτρέπει στον διακομιστή να καθορίζει πώς και για πόσο χρόνο οι πελάτες, οι διαμεσολαβητές και τα CDN μπορούν να αποθηκεύουν την απάντηση. Σε αντίθεση με το Expires (HTTP/1.0), το Cache-Control χρησιμοποιεί οδηγίες — εντολές κειμένου που συνδυάζονται με κόμμα: Cache-Control: public, max-age=3600, must-revalidate. Η κεφαλίδα παρέχει ακριβή έλεγχο πάνω σε κάθε κρίκο της αλυσίδας αποθήκευσης.
Η αποθήκευση είναι ένας από τους θεμελιώδεις μηχανισμούς απόδοσης του ιστού και των εφαρμογών κινητών. Χωρίς αυτήν, κάθε αίτημα χρήστη θα πήγαινε απευθείας στον διακομιστή, προκαλώντας υπερβολικό φορτίο και καθυστερήσεις. Το Cache-Control καθορίζει τρία επίπεδα αποθήκευσης: πρόγραμμα περιήγησης/εφαρμογή (private cache), διαμεσολαβητές (shared cache) και CDN (distributed cache). Κάθε επίπεδο ερμηνεύει τις οδηγίες με τον δικό του τρόπο.
Η λανθασμένη ρύθμιση του Cache-Control είναι μία από τις πιο συχνές αιτίες προβλημάτων απόδοσης. Πολύ επιθετική αποθήκευση οδηγεί σε παλιά δεδομένα για τους χρήστες. Πολύ αδύναμη αποθήκευση οδηγεί σε υπερβολικά αιτήματα προς τον διακομιστή και αργή φόρτωση. Σύμφωνα με το Akamai (2025), η βελτιστοποίηση του Cache-Control για στατικό περιεχόμενο μειώνει το φορτίο του διακομιστή κατά 70-90% και βελτιώνει το χρόνο φόρτωσης κατά 40-60% για χρήστες κινητών.
Το Cache-Control εμφανίστηκε στο HTTP/1.1 (RFC 2616, 1999) ως αντικατάσταση του Expires. Το Expires είχε ένα θεμελιώδες πρόβλημα: χρησιμοποιούσε απόλυτη ημερομηνία που εξαρτόταν από τη ζώνη ώρας του διακομιστή και του πελάτη. Το Cache-Control έλυσε αυτό το πρόβλημα μεταβαίνοντας σε σχετικό χρόνο (max-age σε δευτερόλεπτα από τη στιγμή λήψης της απάντησης). Αργότερα, στο RFC 7234 (2014) προστέθηκαν νέες οδηγίες: immutable για στατικά, stale-while-revalidate και stale-if-error για αναβλημένο έλεγχο.
Το Cache-Control περιλαμβάνει πάνω από 10 οδηγίες χωρισμένες σε τρεις ομάδες: οδηγίες αιτήματος (πελάτης → διακομιστής), οδηγίες απάντησης (διακομιστής → πελάτης) και επεκτάσεις. Στην πράξη, στην ανάπτυξη κινητών χρησιμοποιούνται 6-7 βασικές οδηγίες απάντησης που καλύπτουν το 95% των σεναρίων αποθήκευσης. Ας εξετάσουμε κάθε μία με παραδείγματα και συστάσεις.
| Οδηγία | Σημασία | Παράδειγμα |
|---|---|---|
| max-age | Χρόνος ζωής σε δευτερόλεπτα από τη στιγμή της απάντησης | max-age=3600 — 1 ώρα |
| s-maxage | max-age για shared cache (διαμεσολαβητής, CDN) | s-maxage=86400 — 1 ημέρα για CDN |
| public | Επιτρέπει την αποθήκευση σε όλους (συμπεριλαμβανομένου του διαμεσολαβητή) | public, max-age=3600 |
| private | Επιτρέπει την αποθήκευση μόνο στο πρόγραμμα περιήγησης/εφαρμογή | private, max-age=600 |
| no-cache | Μην χρησιμοποιείτε χωρίς έλεγχο (304 υποχρεωτικό) | no-cache |
| no-store | Πλήρης απαγόρευση αποθήκευσης | no-store |
| must-revalidate | Μετά το max-age, υποχρεωτικός έλεγχος στο origin | max-age=3600, must-revalidate |
| immutable | Ο πόρος δεν θα αλλάξει (για εκδοσιομορφωμένα στατικά) | max-age=31536000, immutable |
max-age — η πιο σημαντική οδηγία. Απαγορεύει στον πελάτη να στέλνει αίτημα στον διακομιστή για καθορισμένο χρονικό διάστημα. Για στατικά (CSS, JS, εικόνες) το max-age ορίζεται συνήθως από 1 ημέρα έως 1 έτος. Για απαντήσεις API — από 0 δευτερόλεπτα (πάντα φρέσκα δεδομένα) έως 5-10 λεπτά (δεδομένα αναφοράς). s-maxage επιτρέπει τη ρύθμιση διαφορετικού χρόνου ζωής για CDN και πρόγραμμα περιήγησης: το CDN αποθηκεύει το αντίγραφο για 1 ημέρα, το πρόγραμμα περιήγησης — 1 ώρα.
Αυτές οι δύο οδηγίες συχνά συγχέονται. Το no-cache δεν απαγορεύει την αποθήκευση — απαιτεί έλεγχο του αποθηκευμένου αντιγράφου σε κάθε χρήση μέσω ενός υπό συνθήκη αιτήματος (If-Modified-Since ή If-None-Match). Εάν ο διακομιστής απαντήσει 304 — ο πελάτης χρησιμοποιεί την cache. Εάν 200 — ενημερώνει. Το no-store απαγορεύει πλήρως την αποθήκευση της απάντησης σε οποιαδήποτε cache, συμπεριλαμβανομένου του δίσκου και της μνήμης. Χρησιμοποιείτε το no-store μόνο για ευαίσθητα δεδομένα — τοκένια, στοιχεία πληρωμής, προσωπικά έγγραφα.
Η κεφαλίδα Expires (HTTP/1.0) επίσης υποδεικνύει το χρόνο ζωής του πόρου, αλλά χρησιμοποιεί απόλυτη ημερομηνία: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age — σχετικός χρόνος από τη στιγμή της απάντησης. Η διαφορά είναι κρίσιμη για κατανεμημένα συστήματα: εάν ο διακομιστής και ο πελάτης βρίσκονται σε διαφορετικές ζώνες ώρας, το Expires μπορεί να ερμηνευτεί λανθασμένα. Το Cache-Control δεν έχει αυτό το πρόβλημα — 3600 δευτερόλεπτα είναι πάντα 3600 δευτερόλεπτα.
Όταν και οι δύο κεφαλίδες είναι παρούσες, το Cache-Control έχει προτεραιότητα έναντι του Expires. Αυτό ορίζεται στο RFC 7234: "Εάν η απάντηση περιέχει Cache-Control με την οδηγία max-age, ο παραλήπτης ΠΡΕΠΕΙ να αγνοήσει το Expires". Στην πράξη, συνιστάται να μην επιστρέφετε το Expires για σύγχρονους πελάτες, καθώς το Cache-Control καλύπτει όλα τα σενάρια του Expires. Ωστόσο, για συμβατότητα προς τα πίσω με παλιούς διαμεσολαβητές και προγράμματα περιήγησης, μπορούν να επιστραφούν και οι δύο κεφαλίδες.
Το Expires έχει παραμείνει κυρίως για στατικό περιεχόμενο σε Nginx και Apache — αυτοί οι διακομιστές αυτόματα προσθέτουν και τις δύο κεφαλίδες. Εάν στο έργο σας συναντάτε Expires χωρίς Cache-Control, αντικαταστήστε το με Cache-Control με max-age: η ακρίβεια διαχείρισης cache αυξάνεται και η εξάρτηση από τη ζώνη ώρας εξαλείφεται. Για μετάβαση, αρκεί να ρυθμίσετε τον διακομιστή να προσθέτει Cache-Control αντί για Expires.
# Nginx: Cache-Control για στατικά αρχεία
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Διαφορετικές πολιτικές για διαφορετικούς τύπους περιεχομένου
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
Στη ρύθμιση Nginx για στατικά αρχεία (CSS, JS, εικόνες) ορίζεται Cache-Control 30 ημερών με το γνώρισμα immutable — αυτό το γνώρισμα ενημερώνει το πρόγραμμα περιήγησης ότι ο πόρος δεν αλλάζει ποτέ σε αυτό το URL (έκδοση μέσω hash στο όνομα αρχείου). Τα API endpoints χρησιμοποιούν no-cache για δυναμικά δεδομένα και public με σύντομο max-age για δεδομένα αναφοράς — λίστες που ζητούνται συχνά και αλλάζουν σπάνια.
Σε εφαρμογές κινητών, το Cache-Control παίζει ιδιαίτερο ρόλο λόγω των περιορισμών των κινητών δικτύων: υψηλή καθυστέρηση, ασταθής σύνδεση, περιορισμοί κίνησης. Η σωστή αποθήκευση επιτρέπει στον χρήστη να βλέπει τα δεδομένα αμέσως, ακόμα και offline, και να τα ενημερώνει στο παρασκήνιο. Το OkHttp στο Android και το URLSession στο iOS διαθέτουν ενσωματωμένα συστήματα αποθήκευσης που λαμβάνουν υπόψη το Cache-Control.
OkHttp χρησιμοποιεί το CacheInterceptor που διαβάζει το Cache-Control από την απάντηση και αυτόματα διαχειρίζεται την αποθήκευση. Εάν ο διακομιστής επέστρεψε Cache-Control: max-age=3600, το OkHttp δεν θα στείλει αίτημα στον διακομιστή για μία ώρα. Μετά τη λήξη του max-age, το OkHttp στέλνει ένα υπό συνθήκη αίτημα με If-Modified-Since και If-None-Match. Ρύθμιση cache στο OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
Ο κώδικας δημιουργεί ένα OkHttpClient με 10 MB cache και αντικαθιστά το Cache-Control μέσω NetworkInterceptor. Εάν ο διακομιστής δεν επιστρέφει Cache-Control ή χρησιμοποιεί Expires, ο interceptor προσθέτει public, max-age=300 (5 λεπτά). Ο interceptor αφαιρεί την απαρχαιωμένη κεφαλίδα Pragma (HTTP/1.0) για συμβατότητα. Με το ίδιο σχήμα, λειτουργεί η αποθήκευση στο iOS μέσω URLCache.shared με ρύθμιση memoryCapacity και diskCapacity.
Η οδηγία stale-while-revalidate επιτρέπει στον χρήστη να βλέπει παλιά cache (stale) ενώ η εφαρμογή φορτώνει φρέσκα δεδομένα στο παρασκήνιο. Αυτό δίνει ένα αποτέλεσμα άμεσης απόκρισης: ο χρήστης βλέπει το περιεχόμενο αμέσως, και μετά από ένα δευτερόλεπτο ενημερώνεται. Υποστηρίζεται από το OkHttp από την έκδοση 3.10 και το URLCache στο iOS 14+. Παράδειγμα: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 ώρα ενεργής cache, στη συνέχεια 5 λεπτά εμφάνισης παλιάς cache με ενημέρωση παρασκηνίου.
Διαφορετικοί τύποι πόρων απαιτούν διαφορετικές στρατηγικές αποθήκευσης. Ας εξετάσουμε τις βέλτιστες ρυθμίσεις για τυπικά σενάρια στην ανάπτυξη κινητών. Για στατικό περιεχόμενο με hash στο όνομα αρχείου (bundle.abc123.js) μπορείτε να ορίσετε max-age έως 1 έτος με immutable. Για λίστες API που ενημερώνονται σπάνια (κατάλογοι, κατηγορίες) — max-age από 5 λεπτά έως 1 ώρα με stale-while-revalidate.
| Τύπος πόρου | Cache-Control | Εξήγηση |
|---|---|---|
| Εκδοσιομορφωμένα στατικά | public, max-age=31536000, immutable | 1 έτος, τα αρχεία δεν αλλάζουν (hash στο URL) |
| Μη εκδοσιομορφωμένα στατικά | public, max-age=86400, must-revalidate | 1 ημέρα με υποχρεωτικό έλεγχο μετά |
| API: δεδομένα αναφοράς | public, max-age=600, stale-while-revalidate=60 | 10 λεπτά cache + 1 λεπτό stale |
| API: δεδομένα χρήστη | private, max-age=60 | 1 λεπτό, μόνο για συγκεκριμένο χρήστη |
| API: ευαίσθητα δεδομένα | no-store | Πλήρης απαγόρευση αποθήκευσης |
| Σελίδες HTML | no-cache, must-revalidate | Έλεγχος σε κάθε αίτημα, 304 αν αμετάβλητο |
Είναι σημαντικό να θυμάστε την ασφάλεια: για απαντήσεις που περιέχουν προσωπικά δεδομένα χρήστη, πάντα ορίζετε private. Χωρίς αυτήν την οδηγία, ένας δημόσιος διαμεσολαβητής (π.χ. εταιρικός) μπορεί να αποθηκεύσει την απάντηση και να τη δώσει σε άλλο χρήστη. Για τοκένια αυθεντικοποίησης και πληροφορίες πληρωμής, χρησιμοποιήστε no-store — ακόμα και η private cache δεν πρέπει να αποθηκεύει αυτά τα δεδομένα στο δίσκο.
Για να ελέγξετε την ορθότητα του Cache-Control, χρησιμοποιήστε την κεφαλίδα Age (πόσα δευτερόλεπτα αποθηκεύτηκε η cache) και το X-Cache (hit/miss στο CDN). Στο πρόγραμμα περιήγησης — η καρτέλα Network, η στήλη Size εμφανίζει "from disk cache" ή "304 Not Modified". Εάν ο πόρος πρέπει να αποθηκεύεται αλλά φορτώνεται κάθε φορά — ελέγξτε εάν ο διακομιστής δεν προσθέτει Cache-Control: no-cache ή Pragma: no-cache μαζί με τις οδηγίες σας.
Συχνές Ερωτήσεις
Το max-age ισχύει για όλες τις cache (συμπεριλαμβανομένων των προγραμμάτων περιήγησης), το s-maxage — μόνο για shared cache (διαμεσολαβητής, CDN). Εάν έχει καθοριστεί s-maxage, το CDN αγνοεί το max-age και χρησιμοποιεί το s-maxage. Αυτό επιτρέπει τη ρύθμιση διαφορετικού χρόνου ζωής για πρόγραμμα περιήγησης και CDN.
Όχι, μετά την αποστολή απάντησης με max-age, ο πελάτης δεν θα στείλει αίτημα μέχρι τη λήξη του χρονομέτρου. Για άμεση ακύρωση της cache, πρέπει να αλλάξετε το URL του πόρου (να προσθέσετε έκδοση/hash) και να στείλετε ειδοποιήσεις push ή μηνύματα WebSocket για εξαναγκασμένη επαναφορά.
Η οδηγία immutable (RFC 8246) ενημερώνει το πρόγραμμα περιήγησης ότι ο πόρος δεν θα αλλάξει ποτέ σε αυτό το URL. Το πρόγραμμα περιήγησης ούτε καν επιχειρεί να στείλει υπό συνθήκη αίτημα κατά την ανανέωση της σελίδας — χρησιμοποιεί την cache μέχρι τη λήξη του max-age. Λειτουργεί μόνο με εκδοσιομορφωμένα αρχεία.
Το Googlebot λαμβάνει υπόψη το Cache-Control: η μακρά αποθήκευση επιταχύνει την εκ νέου σάρωση. Το noindex με γρήγορη cache — εντάξει. Το no-store μπορεί να επιβραδύνει την ευρετηρίαση, επειδή το Googlebot θα φορτώνει τη σελίδα κάθε φορά από την αρχή. Πολύ σύντομο max-age αυξάνει το φορτίο του διακομιστή κατά τη σάρωση.
Μέσω helmet ή middleware: res.set('Cache-Control', 'public, max-age=3600'). Για στατικά, χρησιμοποιήστε express.static με την παράμετρο maxAge: express.static('public', {maxAge: '1y'}). Για δυναμικές διαδρομές — μεμονωμένα σε κάθε χειριστή.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης