Έχτισα το 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 αξίζει τον χρόνο εγκατάστασης.
