Με λίγα λόγια: Πέντε παγίδες παραγωγής σε δίγλωσσο ελληνικό/αγγλικό site με next-intl: x-default hreflang που δείχνει σε λάθος γλώσσα, ελληνικά slugs που θέλουν ρητή κανονικοποίηση, σειρά middleware που χαλάει σιωπηλά τον εντοπισμό γλώσσας, μορφοποίηση ημερομηνιών ανά locale, και ελληνικά font subsets που προκαλούν layout shift. Καθεμία έχει μικρή, ρητή λύση· το κόστος είναι να ξέρεις ότι υπάρχει η παγίδα.
Έχτισα το klawsfx.com ως δίγλωσση ιστοσελίδα — ελληνικά και αγγλικά — χρησιμοποιώντας το next-intl. Η βιβλιοθήκη είναι καλά τεκμηριωμένη και η εγκατάσταση λειτουργεί. Αυτό που η τεκμηρίωση δεν σε προετοιμάζει είναι το σύνολο από κρυφές παγίδες που εμφανίζονται μόνο όταν τα ελληνικά είναι το μη-προεπιλεγμένο locale και νοιάζεσαι για SEO.
Εδώ είναι τα πέντε πράγματα που μου κόστισαν πραγματικό χρόνο.
1. Το hreflang x-default δείχνει στο λάθος locale από προεπιλογή
Το hreflang λέει στη Google ποια σελίδα να εμφανίσει για ποια περιοχή. Το tag x-default είναι η εναλλακτική — η σελίδα που εμφανίζεται όταν κανένα άλλο hreflang δεν ταιριάζει με το locale του επισκέπτη. Η λογική επιλογή για έναν ελληνο-αγγλικό ιστότοπο είναι τα αγγλικά ως x-default.
Η παγίδα: αν ο ιστότοπός σου έχει σελίδες μόνο στα ελληνικά για συγκεκριμένες διαδρομές — π.χ. έχεις /el/services αλλά όχι /en/services — η βοηθητική συνάρτηση buildAlternates θα δημιουργήσει ένα hreflang x-default που δείχνει στο locale με τη μεγαλύτερη κάλυψη. Αν αυτά είναι τα ελληνικά από προεπιλογή, έχεις πει στη Google να στέλνει μη-ελληνόφωνους χρήστες σε ελληνικές σελίδες. Η πτώση στην οργανική επισκεψιμότητα από αυτό είναι πραγματική και με καθυστέρηση — χρειάζονται εβδομάδες πριν τη δεις στο Search Console.
Η λύση είναι ρητή: όταν κατασκευάζεις tags hreflang, έλεγξε τα availableLocales ανά διαδρομή. Αν τα αγγλικά είναι διαθέσιμα, χρησιμοποίησέ τα για το x-default. Αν όχι, χρησιμοποίησε όποιο locale έχει η σελίδα. Μην κάνεις ποτέ hardcode το defaultLocale ως x-default χωρίς να επαληθεύσεις ότι η διαδρομή υπάρχει σε εκείνο το locale.
function buildAlternates(pathname: string, availableLocales: string[]) {
const entries = availableLocales.map((locale) => ({
hrefLang: locale,
href: `${baseUrl}/${locale}${pathname}`,
}));
const xDefaultLocale = availableLocales.includes('en') ? 'en' : availableLocales[0];
entries.push({
hrefLang: 'x-default',
href: `${baseUrl}/${xDefaultLocale}${pathname}`,
});
return entries;
}
2. Τα ελληνικά γράμματα σε slugs χρειάζονται ρητή κανονικοποίηση
Τα slugs στα ελληνικά είναι είτε πλήρως ελληνικά γράμματα είτε πλήρης λατινική μεταγραφή — η ανάμειξή τους είναι πρόβλημα συντήρησης. Επέλεξε ένα και εφάρμοσέ το παντού.
Χρησιμοποιώ λατινική μεταγραφή για slugs: υπηρεσίες → ypiresies. Αυτό κρατά τα URLs αναγνώσιμα σε πλαίσια κοινής χρήσης και αποφεύγει την κωδικοποίηση ποσοστού στα analytics. Αλλά η αυτόματη δημιουργία slug από ελληνικό τίτλο δεν το κάνει αυτό αυτόματα — τα περισσότερα βοηθητικά slug αφαιρούν τα διακριτικά από λατινικούς χαρακτήρες αλλά αφήνουν ελληνικά σενάρια ως έχουν ή τα παραλείπουν εντελώς, παράγοντας άδεια slugs.
Η λύση είναι ένα βήμα μεταγραφής πριν τη δημιουργία slug:
import { transliterate } from 'transliteration';
import slugify from 'slugify';
export function slugifyGreek(input: string): string {
const latin = transliterate(input); // υπηρεσίες → ypiresies
return slugify(latin, { lower: true, strict: true });
}
Χωρίς αυτό, ένα ελληνικό πεδίο τίτλου στον πίνακα διαχείρισης θα παράγει σιωπηλά ένα σπασμένο ή άδειο slug.
3. Το middleware του next-intl και το edge runtime του Vercel δεν συνεργάζονται καλά με την ανίχνευση locale από το Accept-Language
Η ανίχνευση locale του next-intl βασίζεται στο header αίτησης Accept-Language. Στο edge runtime του Vercel, αυτό λειτουργεί ως επί το πλείστον — αλλά όταν έχεις ένα προσαρμοσμένο middleware που επίσης επιθεωρεί headers (CSP nonces, έλεγχοι ταυτοποίησης, ανακατευθύνσεις), η σειρά εκτέλεσης του middleware έχει σημασία και είναι εύκολο να πάει στραβά.
Το σύμπτωμα: οι χρήστες καταλήγουν στο λάθος locale παρά τη σαφή προτίμηση του browser τους. Ελέγχεις τα response headers και το Set-Cookie για το cookie locale φαίνεται σωστό, αλλά η σελίδα εξακολουθεί να αποδίδεται στη λάθος γλώσσα.
Η αιτία είναι σχεδόν πάντα ένα middleware που επιστρέφει νωρίς (με ανακατεύθυνση ή απόκριση) πριν το middleware locale του next-intl έχει την ευκαιρία να ορίσει το cookie του. Η λύση: βεβαιώσου ότι ο περιτύλιξης createMiddleware του next-intl εκτελείται πρώτος, και μόνο τότε στρωματοποίησε τη δική σου λογική.
// middleware.ts
import createIntlMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
const intlMiddleware = createIntlMiddleware(routing);
export default function middleware(request: NextRequest) {
const intlResponse = intlMiddleware(request);
// Εφάρμοσε τα δικά σου headers στο intlResponse, όχι σε νέο Response
intlResponse.headers.set('X-Custom-Header', 'value');
return intlResponse;
}
4. Μορφοποίηση ημερομηνίας και αριθμών στο ελληνικό locale
Η Ελλάδα χρησιμοποιεί ημερομηνίες ΗΗ/ΜΜ/ΕΕΕΕ, κόμμα ως διαχωριστικό δεκαδικών, και τελεία ως διαχωριστικό χιλιάδων — το αντίθετο της αμερικανικής σύμβασης. Το next-intl χειρίζεται αυτό μέσω του API Intl, αλλά μόνο αν χρησιμοποιείς πραγματικά το useFormatter() και περνάς σωστά το locale.
Το σφάλμα που συνάντησα: χρησιμοποιούσα το new Date().toLocaleDateString() απευθείας σε ένα component, το οποίο λαμβάνει το locale του διακομιστή κατά τη χρόνο αποτύπωσης, όχι το locale του χρήστη από το context του next-intl. Στην παραγωγή στο Vercel, το locale του διακομιστή είναι en-US, οπότε οι Έλληνες χρήστες έβλεπαν 6/2/2026 αντί για 2/6/2026.
// Λάθος — χρησιμοποιεί το locale του διακομιστή
const dateStr = new Date(post.date).toLocaleDateString();
// Σωστό — χρησιμοποιεί το locale του next-intl
const format = useFormatter();
const dateStr = format.dateTime(new Date(post.date), { dateStyle: 'long' });
5. Ελληνικές γραμματοσειρές και η αύξηση CLS
Η φόρτωση μιας γραμματοσειράς που υποστηρίζει ελληνικά (ελληνικό subset της Inter, ή μια αποκλειστικά ελληνική γραμματοσειρά) απαιτεί ρητά αίτημα για το ελληνικό subset χαρακτήρων. Χωρίς αυτό, το next/font του Next.js κατεβάζει ένα subset μόνο για λατινικά και ο browser επιστρέφει στη γραμματοσειρά συστήματος για ελληνικούς χαρακτήρες, προκαλώντας ορατό φλας και αύξηση CLS.
// app/fonts.ts
import { Inter } from 'next/font/google';
export const inter = Inter({
subsets: ['latin', 'greek'], // και τα δύο απαιτούνται
variable: '--font-inter',
display: 'swap',
});
Το display: 'swap' είναι ένας συμβιβασμός: αποτρέπει αόρατο κείμενο αλλά επιτρέπει αλλαγή διάταξης αν η γραμματοσειρά φορτωθεί αργά. Για σελίδες με πολύ ελληνικό περιεχόμενο, το display: 'optional' αξίζει να εξεταστεί — χρησιμοποιεί την εναλλακτική γραμματοσειρά αν η προσαρμοσμένη δεν φορτωθεί μέσα σε ένα μικρό παράθυρο, κάτι που είναι καλύτερο για το CLS σε αργές συνδέσεις.
Μια μετα-παρατήρηση
Τα περισσότερα από τα παραπάνω ζητήματα δεν εμφανίζονται κατά την ανάπτυξη επειδή το μηχάνημά σου έχει το δικό σου locale, ο browser σου είναι σωστά ρυθμισμένος, και το middleware σου τρέχει ενάντια σε μία μόνο αίτηση σε απομόνωση. Εμφανίζονται μόνο στην παραγωγή, συχνά ως σταδιακές πτώσεις επισκεψιμότητας παρά σκληρά σφάλματα. Η δημιουργία ενός περιβάλλοντος staging που προσομοιώνει διαφορετικά headers Accept-Language αξίζει τον χρόνο εγκατάστασης.
