Το συμβόλαιο απόδοσης
Αυτή είναι η μοναδική επίσημη λίστα απαιτήσεων που πρέπει να ικανοποιεί το <head> κάθε frontend όταν αποδίδει δεδομένα SEO Rankbeam. Είναι η πηγή αλήθειας για:
- τις δοκιμές μονάδας μορφής renderer του core (
tests/Unit/Services/RenderingContractTest.php) — το γρήγορο σκέλος χωρίς framework που καλύπτει το CI του πακέτου· - τις εφαρμογές αναφοράς ανά stack στο
rankbeam-examples(Blade, Inertia + Vue / React / Svelte, Livewire), των οποίων οι δοκιμές browser και SSR επαληθεύουν τις ίδιες απαιτήσεις σε πραγματικό DOM· - τους οδηγούς framework (Blade, Inertia και JSON, Livewire), οι οποίοι δεν πρέπει ποτέ να τεκμηριώνουν προσέγγιση που το παραβιάζει.
Αν ένα stack δεν μπορεί να ικανοποιήσει έναν όρο, αυτό είναι ελάττωμα ή τεκμηριωμένος περιορισμός — όχι λόγος να αποδυναμωθεί το συμβόλαιο. Το επίπεδο δεδομένων (SEOResolver → αμετάβλητο SEOData → TagRenderer) είναι ανεξάρτητο από framework· μόνο το πώς τα επιλυμένα δεδομένα φτάνουν στο DOM, επιβιώνουν από την πλοήγηση πελάτη και παραμένουν ορατά στους ανιχνευτές διαφέρει ανά stack, και αυτό ακριβώς ορίζει το συμβόλαιο.
Αυτή η προδιαγραφή ενισχύθηκε μέσω ανεξάρτητης ανασκόπησης σχεδιασμού. Επαναλάβετε την ανασκόπηση μόνο αν αλλάξει ουσιωδώς.
1. Τιμές — τι περιέχει ένα συμμορφούμενο <head>
Τίτλος, περιγραφή, κανονική διεύθυνση URL
- Ακριβώς ένα
<title>, με τον επιλυμένο τίτλο — ποτέ διπλό επίθημα (ο resolver προσθέτει τοseo.title_suffixμία φορά, προστατεύοντας από τίτλο που ήδη τελειώνει με αυτό). - Μία meta description, μόνο όταν επιλύθηκε περιγραφή (χωρίς κενή ετικέτα).
- Ένα
<link rel="canonical">.
Robots
- Εκπέμπεται
<meta name="robots">μόνο όταν η οδηγία διαφέρει από την προεπιλογή ιστοτόπου. Ένα περιττόindex,followείναι θόρυβος, και η απουσία του είναι ακριβώς ό,τι αντιμετωπίζει ο ανιχνευτής ωςindex,follow. Η σύγκριση αγνοεί τα κενά (index, follow≡index,follow)· μια διαφορετική οδηγία εκπέμπεται αυτούσια. Τοseo.robots.emit_default = trueεπιβάλλει την ετικέτα. - Υποστηρίζονται ντετερμινιστικές προηγμένες οδηγίες:
noindex,nofollow,noarchive,nosnippet,max-snippet,max-image-preview,max-video-preview,notranslate,unavailable_after. Είναι επιλυμένες τιμές συμβολοσειρών· η προτεραιότητά τους είναι η αλυσίδα resolver (καθολικά → διαδρομή → μοντέλο → ρητά). Ίδιες είσοδοι ⇒ ίδια έξοδος.
Open Graph
og:title,og:description,og:type,og:url,og:site_name,og:locale.article:*(published_time,modified_time,author,section,tag) μόνο ότανog:type === 'article'και η τιμή είναι πραγματική — ποτέ επινοημένη, ποτέ σε σελίδα που δεν είναι άρθρο.og:imageμεog:image:width/og:image:height/og:image:altκαιog:image:typeόταν είναι γνωστά. Πολλές εικόνες ομαδοποιούνται — κάθεog:imageακολουθείται αμέσως από τις δικές της ιδιότητες διαστάσεων/alt/type.
Twitter Cards
twitter:card,twitter:title,twitter:description,twitter:imageκαιtwitter:image:alt(όταν είναι γνωστό το alt της εικόνας).- Τα
twitter:siteκαιtwitter:creatorείναι προαιρετικά και ανεξάρτητα — το ένα μπορεί να υπάρχει χωρίς το άλλο, και κανένα δεν επινοείται από το άλλο.
hreflang και γλώσσα
- Το hreflang έχει εγγενή διαδρομή επίλυσης μέσω του hook
getSEOAlternates()ενός μοντέλου. - Οι εναλλακτικές hreflang, όταν υπάρχουν, είναι απόλυτες, κανονικοποιημένες, μοναδικές ανά γλώσσα και αμοιβαίες όπου τα δεδομένα είναι πλήρη. Το
x-defaultμόνο όταν έχει ρυθμιστεί. - Το
og:locale:alternateαντικατοπτρίζει μόνο γλώσσες που έχουν πραγματική κοινωνική παραλλαγή (αντιστοίχισηen-US→en_US· σύγκριση στην αντιστοιχισμένη μορφή, χωρίς απαίτηση κυριολεκτικής ισότητας). - Ισοτιμία
<html lang>με την επιλυμένη γλώσσα (ο όρος ανήκει στο συμβόλαιο, παρότι η εφαρμογή εκπέμπει το στοιχείο<html>).
JSON-LD ανά σελίδα
- Αναλύσιμο και ασφαλές έναντι
</script>(το payload κωδικοποιείται μεJSON_HEX_TAGώστε καμία τιμή να μην μπορεί να κλείσει πρόωρα το στοιχείο script — προστασία από αποθηκευμένο XSS). - Είναι αποδεκτά τόσο πολλά τμήματα
<script>όσο και ένα συνδυασμένο@graph. - Σταθερό
@idχρησιμοποιείται μόνο όπου οι οντότητες συνδέονται πραγματικά (Organization ↔ WebSite ↔ WebPage)· σταθερό@idδεν είναι υποχρεωτικό σε αυτόνομους κόμβους.
2. Κανονικοποίηση και αμετάβλητοι κανόνες
- Απόλυτες URL
http(s)γιαcanonical,og:url,og:image,twitter:image. Καμία κενή ετικέτα ή ετικέτα null δεν φτάνει ποτέ στο DOM. - Τα
canonicalκαιog:urlΠΡΕΠΕΙ να επιλύονται στην ίδια κανονικοποιημένη URL. Η διαφωνία είναι ΑΥΣΤΗΡΗ αποτυχία, όχι προειδοποίηση. - Η πολιτική κανονικοποίησης της canonical είναι συνεπής σε όλο το σύστημα: σχήμα / host / θύρα / πεζά-κεφαλαία διαδρομής / επιτρεπόμενες παράμετροι query / τελική κάθετος αντιμετωπίζονται με τον ίδιο τρόπο κάθε φορά. Οι ευρετηριάσιμες σελίδες αναφέρονται στον εαυτό τους· μια σελίδα
noindexδεν κληρονομεί τη στρατηγική canonical άλλης σελίδας. - Η διαφυγή εξαρτάται από τον προορισμό: χαρακτηριστικό HTML, κείμενο και JSON χρησιμοποιούν το καθένα τον σωστό κωδικοποιητή. Οι έλεγχοι συγκρίνουν αποκωδικοποιημένες σημασιολογικές τιμές, όχι byte.
- Η ισοτιμία μεταξύ renderer είναι σημασιολογική, όχι byte προς byte.
render()(HTML) ≡toArray()≡toInertiaHead()μετά την κανονικοποίηση — οι τρεις αναπαραστάσεις δικαιολογημένα διαφέρουν σε σειρά και μορφή ετικετών. Οι κανόνες μοναδικών και επαναλαμβανόμενων ιδιοτήτων είναι ρητοί (έναog:title· πολλάarticle:tag). - Ιδιοκτησία ετικετών: ο renderer πελάτη αντικαθιστά ετικέτες του πακέτου (με κλειδιά, δείτε §4) χωρίς να διαγράφει άσχετες ετικέτες της εφαρμογής.
3. Συμπεριφορά — πλοήγηση στην πλευρά πελάτη
Μετά από κάθε επίσκεψη Inertia ή wire:navigate του Livewire:
- υπάρχει ακριβώς ένα από κάθε μοναδικό στοιχείο (
<title>, περιγραφή, canonical, κάθεog:*/twitter:*), κανένα παρωχημένο· - το JSON-LD δεν συσσωρεύεται — το schema προηγούμενης σελίδας αφαιρείται, δεν προστίθεται από πάνω (το Livewire αντιμετωπίζει το
<script>ως μη αφαιρούμενο asset, οπότε τα script schema φέρουνdata-seo-schemaκαι αναγνωριστικό ανά URL, και αυτά της προηγούμενης σελίδας αφαιρούνται στοlivewire:navigated— δείτε τον οδηγό Livewire)· - η πλοήγηση από σελίδα πλούσια σε μεταδεδομένα σε λιτή σελίδα αφαιρεί τις επιπλέον ετικέτες (η λιτή σελίδα δεν κρατά description/og/schema της πλούσιας)·
- μηδενικές προειδοποιήσεις hydration, και τα μεταδεδομένα είναι σημασιολογικά ίδια πριν και μετά το hydration.
4. head-keys του Inertia (ιδιοκτησία ετικετών)
Το toInertiaHead() προσθέτει σταθερό head-key σε κάθε εγγραφή meta/link. Το Inertia αφαιρεί διπλότυπα στοιχεία head με βάση αυτό το χαρακτηριστικό: μια ετικέτα <Head> σελίδας με ίδιο head-key με ετικέτα layout την αντικαθιστά, αντί να προσθέσει διπλότυπο.
- Βασικό κλειδί =
name ?? propertyγια meta,relγια συνδέσμους. - Οι επαναλαμβανόμενες ετικέτες διαφοροποιούνται ώστε καθεμία να κρατά μοναδικό κλειδί:
article:tag→article:tag,article:tag:1, …· hreflang →alternate:en-US,alternate:fr-FR.
Συνδέστε το στα πρότυπα ως :head-key — όχι ως :key του Vue (που είναι το άσχετο κλειδί συμφιλίωσης v-for και δεν επηρεάζει την αφαίρεση διπλότυπων head του Inertia).
5. Ορατότητα ανιχνευτών (ρητές λειτουργίες)
- Το SSR / prerender ΠΡΕΠΕΙ να εκπέμπει όλο το συμβόλαιο στο ακατέργαστο HTML της HTTP απόκρισης — αυτό δοκιμάζεται χωριστά από το DOM μετά το hydration (με ανενεργό JS).
- Το CSR μόνο δεν μπορεί να ισχυρίζεται συμμόρφωση για ανιχνευτές. Το προεπιλεγμένο Inertia (χωρίς SSR) εισάγει τα μεταδεδομένα στην πλευρά πελάτη: το αρχικό HTML που ανακτά ένας ανιχνευτής δεν έχει μεταδεδομένα SEO. Αυτό τεκμηριώνεται, δεν κρύβεται — τα ορατά σε ανιχνευτές μεταδεδομένα απαιτούν Inertia SSR ή prerendering (και το JSON-LD για ανιχνευτές πρέπει να αποδίδεται στον διακομιστή).
6. Εκτός πεδίου / μη στόχοι
- Ζητήματα εφαρμογής, όχι renderer:
charset,viewport, favicons. (Σημείωση: το<meta charset>πρέπει να προηγείται κάθε μεταδεδομένου εκτός ASCII, οπότε η εφαρμογή καθορίζει τη σειρά αυτών των στοιχείων head.) - Η δοκιμή e2e ελέγχει μόνο την παραγόμενη έξοδο. Δεν επιβεβαιώνει ευρετηρίαση Google, επιλογή κανονικής διεύθυνσης URL, επιλεξιμότητα εμπλουτισμένων αποτελεσμάτων ή κατάταξη· και δεν επιβεβαιώνει MIME/διαθεσιμότητα απομακρυσμένων εικόνων. Αυτά ανήκουν σε προαιρετικές δοκιμές ενσωμάτωσης/HTTP, ποτέ στον πίνακα δοκιμών browser.
7. Κατάσταση συμμόρφωσης
Τι αποδεικνύει κάθε όρο σήμερα. Μονάδας = RenderingContractTest (core, CI πακέτου). Browser/SSR = rankbeam-examples (προγραμματισμένος πίνακας δοκιμών). Εφαρμογή = ευθύνη της εφαρμογής υποδοχής. Προγραμματισμένο = στόχος στο συμβόλαιο, αλλά τα δεδομένα δεν μοντελοποιούνται ακόμη από το SEOData, οπότε ο renderer εκπέμπει το ασφαλές υποσύνολο.
| Όρος | Κατάσταση |
|---|---|
Ακριβώς ένα επιλυμένο <title>, χωρίς διπλό επίθημα | Μονάδας + Browser |
| Meta description μόνο όταν υπάρχει | Μονάδας + Browser |
Ένα <link rel="canonical">, ποτέ κενό | Μονάδας + Browser |
Robots μόνο όταν διαφέρει από την προεπιλογή· αυτούσιο· διακόπτης emit_default | Μονάδας + Browser |
| Προηγμένες οδηγίες robots μέσω προτεραιότητας resolver | Μονάδας (resolver) |
og:title/description/type/url/site_name/locale· γλώσσα en-US→en_US | Μονάδας + Browser |
article:* μόνο όταν og:type=article και είναι πραγματικό | Μονάδας + Browser |
og:image παρόν και απόλυτο | Μονάδας + Browser |
og:image:width/height/alt, og:image:type, ομαδοποίηση πολλών εικόνων | Προγραμματισμένο — το SEOData έχει μία συμβολοσειρά ogImage· δεν μοντελοποιούνται ακόμη διαστάσεις/alt/type. Ο renderer εκπέμπει ένα απόλυτο og:image. |
twitter:card/title/description/image· site/creator ανεξάρτητα | Μονάδας + Browser |
twitter:image:alt | Προγραμματισμένο — δεν μοντελοποιείται ακόμη πεδίο alt εικόνας. |
| hreflang απόλυτο, μοναδικό ανά γλώσσα | Μονάδας + Browser |
Αμοιβαιότητα hreflang, x-default όταν ρυθμίζεται | Browser (εξαρτάται από τα δεδομένα) |
og:locale:alternate αντικατοπτρίζει πραγματικές κοινωνικές παραλλαγές | Προγραμματισμένο — δεν μοντελοποιείται ακόμη αντιστοίχιση κοινωνικών παραλλαγών ανά γλώσσα. |
Ισοτιμία <html lang> | Εφαρμογή (+ το ελέγχει ο Browser) |
JSON-LD αναλύσιμο και ασφαλές έναντι </script> | Μονάδας + Browser |
Πολλά script Ή @graph· σταθερό @id όπου συνδέονται οντότητες | Μονάδας (γράφος merchant) + Browser |
| Απόλυτες URL· χωρίς κενές ετικέτες/τιμές null | Μονάδας + Browser |
canonical ≡ og:url (αυστηρή αποτυχία αν διαφέρουν) | Μονάδας + Browser |
| Συνεπής κανονικοποίηση canonical· αυτοαναφορά· απομόνωση noindex | Browser |
| Διαφυγή ανά προορισμό· αποκωδικοποιημένη σημασιολογική ισοτιμία | Μονάδας |
Σημασιολογική ισοτιμία μεταξύ renderer (render() ≡ toArray() ≡ toInertiaHead()) | Μονάδας |
Σταθερό head-key Inertia και διαφοροποίηση επαναλαμβανόμενων | Μονάδας + Browser |
| Πλοήγηση πελάτη: ένα μοναδικό στοιχείο, κανένα παρωχημένο, χωρίς συσσώρευση JSON-LD, αφαίρεση | Browser — ο renderer παρέχει τα hooks data-seo-schema που χρειάζεται ο καθαρισμός |
| Μηδενικές προειδοποιήσεις hydration· ισοτιμία πριν/μετά | Browser |
| SSR εκπέμπει όλο το συμβόλαιο στο ακατέργαστο HTML· CSR μόνο τεκμηριώνεται ως μη συμμορφούμενο | Browser + τεκμηρίωση |
Οι προγραμματισμένοι όροι είναι συνειδητά, τεκμηριωμένα κενά — το συμβόλαιο είναι ο διαρκής στόχος και πρόκειται για προσθετικές, συμβατές προς τα πίσω επεκτάσεις για μελλοντική εργασία (απαιτούν νέα πεδία / στήλες SEOData, έκδοση SemVer-minor). Ο renderer εκπέμπει σήμερα το ασφαλές υποσύνολο· δεν επινοεί ποτέ τιμή που δεν έχει.