[{"data":1,"prerenderedAt":790},["ShallowReactive",2],{"blog-schema-org-structured-data-b2b-shop-de":3},{"slug":4,"published":5,"minutes":6,"category":7,"tags":8,"keywords":14,"about":26,"sources":35,"cover":72,"og":73,"expertise":74,"locales":75,"lang":77,"title":79,"description":80,"coverAlt":81,"metaTitle":82,"takeaways":83,"faq":89,"toc":105,"blocks":139,"others":562},"schema-org-structured-data-b2b-shop","2026-10-17",10,"web",[9,10,11,12,13],"Schema.org","Structured data","B2B e-commerce","Technical SEO","Nuxt",[15,16,17,18,19,20,21,22,23,24,25],"schema.org b2b shop","product structured data b2b","merchant listing structured data","product snippet vs merchant listing","unitpricespecification referencequantity","productgroup variants structured data","organization schema vatid","b2b prices behind login seo","json-ld nuxt schema org","merchant return policy structured data","mpn gtin structured data",[27,29,32],{"name":9,"url":28},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSchema.org",{"name":30,"url":31},"JSON-LD","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON-LD",{"name":33,"url":34},"Business-to-business","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBusiness-to-business",[36,39,42,45,48,51,54,57,60,63,66,69],{"title":37,"url":38},"Google Search Central: Introduction to Product structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fproduct",{"title":40,"url":41},"Google Search Central: Merchant listing (Product, Offer) structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fmerchant-listing",{"title":43,"url":44},"Google Search Central: Product snippet structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fproduct-snippet",{"title":46,"url":47},"Google Search Central: Product variant structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fproduct-variants",{"title":49,"url":50},"Google Search Central: Organization structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Forganization",{"title":52,"url":53},"Google Search Central: Merchant return policy structured data","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Freturn-policy",{"title":55,"url":56},"Google Search Central: General structured data guidelines","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fsd-policies",{"title":58,"url":59},"Google Search Central: Generate structured data with JavaScript","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fgenerate-structured-data-with-javascript",{"title":61,"url":62},"Schema.org: Offer","https:\u002F\u002Fschema.org\u002FOffer",{"title":64,"url":65},"Schema.org: UnitPriceSpecification","https:\u002F\u002Fschema.org\u002FUnitPriceSpecification",{"title":67,"url":68},"Unhead: Product schema (defineProduct)","https:\u002F\u002Funhead.unjs.io\u002Fdocs\u002Fschema-org\u002Fapi\u002Fschema\u002Fproduct",{"title":70,"url":71},"Nuxt SEO: Schema.org identity setup","https:\u002F\u002Fnuxtseo.com\u002Fdocs\u002Fschema-org\u002Fguides\u002Fsetup-identity","\u002Fimages\u002Fblog\u002Fschema-org-structured-data-b2b-shop\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fschema-org-structured-data-b2b-shop\u002Fog.jpg","b2b-ecommerce-developer",[76,77,78],"en","de","hu","Schema.org-Strukturdaten für B2B-Shops: Preise hinter Logins, Einheiten und Varianten","Produkt-Markup für B2B-Shops: Snippet oder Händlereintrag, welcher Preis, Artikelnummern, Grundpreise, Varianten, Organization und SSR in Nuxt.","Ein Produktrechteck mit dünnem Rahmen und einem kleinen Feld, das eine niedrige Wand verdeckt.","Schema.org-Strukturdaten für B2B-Shops · Balázs Csorba",[84,85,86,87,88],"Google hat zwei Produktfunktionen: Händlereinträge brauchen eine kaufbare Seite mit einem Preis über null, Produkt-Snippets nicht.","Nur auszeichnen, was der Besucher sieht; ein kundenspezifischer Preis hinter einem Login gehört nicht in öffentliches JSON-LD.","Artikelnummern sind der B2B-Vorteil: sku, mpn, gtin und brand ins Markup, aus denselben PIM-Feldern, die die Seite anzeigt.","UnitPriceSpecification mit referenceQuantity für Preise pro Meter, Kilogramm oder Packung verwenden und ProductGroup für echte Variantenfamilien.","JSON-LD auf dem Server rendern; Google warnt, dass per JavaScript erzeugtes Product-Markup Shopping-Crawls seltener und unzuverlässiger macht.",[90,93,96,99,102],{"q":91,"a":92},"Kann ein B2B-Shop mit Preisen nur nach Login Product-Strukturdaten nutzen?","Ja, meist aber als Produkt-Snippets statt als Händlereinträge. Produkt-Snippets brauchen einen Namen und eines von review, aggregateRating oder offers. Händlereinträge brauchen ein Angebot mit einem Preis über null, den der Besucher sieht; eine Seite ohne Preis kommt dafür nicht infrage.",{"q":94,"a":95},"Welcher Preis gehört ins Markup?","Der Preis, den ein anonymer Besucher auf der Seite sieht, in der angezeigten Währung. Google verlangt, dass das Markup sichtbare Inhalte beschreibt. Verhandelte Preise oder Preise einer Kundengruppe, die erst nach dem Login erscheinen, gehören nicht hinein.",{"q":97,"a":98},"Wie zeichne ich einen Preis pro Meter oder pro Packung aus?","Den aktiven Preis in eine UnitPriceSpecification setzen und referenceQuantity mit Wert und UN\u002FCEFACT-unitCode ergänzen. Google dokumentiert das für Grundpreise, die in der EU bei Waren nach Volumen, Länge oder Gewicht wichtig sind.",{"q":100,"a":101},"Sollte ich ProductGroup für jedes Produkt mit Optionen nutzen?","Dann, wenn die Optionen echte Varianten eines Produkts sind, etwa Größen oder Farben mit eigener SKU. Jede Variante braucht eine eindeutige ID wie sku oder gtin, die Gruppe eine productGroupID. Konfigurierbare Produkte mit Tausenden Kombinationen beschreibt man besser als ein Produkt mit einem Ab-Preis.",{"q":103,"a":104},"Helfen Strukturdaten KI-Assistenten und Answer Engines?","Google dokumentiert, was die eigenen Suchfunktionen nutzen. Andere Systeme lesen womöglich dasselbe JSON-LD, es gibt aber keine öffentliche Spezifikation, wie sie es gewichten. Saubere sichtbare Inhalte und konsistente Kennungen sind der Teil, den man selbst steuert.",[106,109,112,115,118,121,124,127,130,133,136],{"id":107,"title":108},"why-b2b-is-different","Warum B2B-Produktseiten andere Markup-Entscheidungen brauchen",{"id":110,"title":111},"snippet-or-merchant-listing","Produkt-Snippet oder Händlereintrag",{"id":113,"title":114},"which-price","Welchen Preis man veröffentlicht, und wie",{"id":116,"title":117},"identifiers","Kennungen: der Teil, den B2B-Shops geschenkt bekommen",{"id":119,"title":120},"units-and-packs","Einheiten, Packungen und Preise pro Meter",{"id":122,"title":123},"variants","Varianten, ohne das Markup aufzublähen",{"id":125,"title":126},"organization","Organization, Rückgabe- und Versandrichtlinien",{"id":128,"title":129},"nuxt-ssr","JSON-LD in Nuxt serverseitig rendern",{"id":131,"title":132},"data-pipeline","Eine Datenquelle vom PIM bis zum Markup",{"id":134,"title":135},"testing-markup","Testen und überwachen",{"id":137,"title":138},"sources","Quellen",[140,144,147,150,153,198,199,236,242,249,250,265,276,277,313,322,323,346,348,351,352,377,385,386,423,441,442,457,459,466,467,470,479,487,488,491,508,521,522],{"type":141,"content":142},"paragraph",[143],"Die meisten Anleitungen zu Strukturdaten sind für Endkunden-Shops geschrieben: ein öffentlicher Preis, eine Sternebewertung, kostenloser Versand. Ein B2B-Shop bricht die meisten dieser Annahmen. Preise hängen vom Kunden ab und erscheinen oft erst nach dem Login, Produkte werden pro Meter oder in 50er-Packungen verkauft, ein einzelner Artikel kann Hunderte Varianten haben, und die wertvollsten Suchbegriffe sind Herstellerartikelnummern statt Produktnamen.",{"type":141,"content":145},[146],"Dieser Beitrag geht die Entscheidungen durch, die ich treffe, wenn ich B2B-Shops auf Basis von Nuxt mit Commerce- oder PIM-Backend mit schema.org-Markup versehe. Er hält sich an das, was Google für seine Produktfunktionen dokumentiert, weil das der Teil mit einer öffentlichen Spezifikation ist, und zeigt, wo B2B-Daten eine andere Antwort brauchen als der Endkunden-Standard.",{"type":148,"level":149,"id":107,"text":108},"heading",2,{"type":141,"content":151},[152],"Für Strukturdaten gilt eine Regel, die fast jede B2B-Entscheidung bestimmt. Googles allgemeine Richtlinien sagen: keine Inhalte auszeichnen, die für Leser der Seite nicht sichtbar sind. Markup beschreibt die Seite und ist kein Nebenkanal für Daten, die man lieber nicht zeigt. Alles Weitere folgt aus der Frage, was ein anonymer Besucher und der Googlebot für jede Eigenschaft tatsächlich sehen.",{"type":154,"head":155,"rows":162},"table",[156,158,160],[157],"B2B-Merkmal",[159],"Endkunden-Standard",[161],"B2B-Antwort",[163,170,177,184,191],[164,166,168],[165],"Preise nach Login",[167],"Offer mit öffentlichem Preis",[169],"Snippet ohne Preis oder öffentlicher Listenpreis, falls angezeigt",[171,173,175],[172],"Verkauf pro Meter, kg oder Packung",[174],"Preis pro Stück",[176],"UnitPriceSpecification mit referenceQuantity",[178,180,182],[179],"Suche nach Artikelnummer",[181],"Name und Marke",[183],"sku, mpn, gtin, brand aus dem PIM",[185,187,189],[186],"Hunderte Varianten",[188],"Größe und Farbe",[190],"ProductGroup nur für echte SKU-Varianten",[192,194,196],[193],"Firmenidentität zählt",[195],"Logo und Social Links",[197],"Organization mit Firmenname, UID, Rückgaberichtlinie",{"type":148,"level":149,"id":110,"text":111},{"type":141,"content":200},[201,202,206,207,210,211,214,215,218,219,210,221,224,225,227,228,231,232,235],"Google teilt Product-Markup in zwei Funktionen auf. Produkt-Snippets sind für Seiten, auf denen man nicht direkt kaufen kann; sie brauchen einen ",{"tag":203,"children":204},"code",[205],"name"," und mindestens eines von ",{"tag":203,"children":208},[209],"review",", ",{"tag":203,"children":212},[213],"aggregateRating"," oder ",{"tag":203,"children":216},[217],"offers",". Händlereinträge sind für Seiten, auf denen Kunden bei Ihnen kaufen können; sie brauchen ",{"tag":203,"children":220},[205],{"tag":203,"children":222},[223],"image"," und ein ",{"tag":203,"children":226},[217],"-Objekt mit ",{"tag":203,"children":229},[230],"price"," und ",{"tag":203,"children":233},[234],"priceCurrency",", und anders als Snippets verlangen sie einen Preis über null. Laut Google macht das Ergänzen der Händlereintrags-Eigenschaften eine Seite meist auch für Snippets geeignet.",{"type":141,"content":237},[238,239,241],"Für einen B2B-Shop ergibt sich die Wahl aus dem, was die öffentliche Seite zeigt. Sehen anonyme Besucher einen Listenpreis und können bestellen, oder sehen sie zumindest, was sie zahlen würden, zielt man auf Händlereinträge. Steht beim Preis „Einloggen, um Ihren Preis zu sehen“, ist man im Snippet-Bereich: Name, Bild, Kennungen, Marke und Beschreibung auszeichnen und ",{"tag":203,"children":240},[217]," weglassen, statt eine Zahl zu erfinden. B2B-Käufer schreiben im Shop selten Bewertungen, daher haben solche Seiten in der Praxis oft gar kein Rich Result, und das ist in Ordnung; die Kennungen helfen Google trotzdem, die Seite Suchen nach Artikelnummern zuzuordnen.",{"type":243,"variant":244,"title":245,"body":246},"callout","warn","Kundenpreise nie in öffentliches Markup",[247],[248],"Serverseitiges Rendering mit eingeloggter Session kann einen verhandelten Preis ins JSON-LD bringen, das ein Cache dann an alle ausliefert. Markup nur aus dem anonymen Preiskontext erzeugen und eingeloggte Antworten von gemeinsamen Caches ausschließen.",{"type":148,"level":149,"id":113,"text":114},{"type":141,"content":251},[252,253,256,257,260,261,264],"Wer einen Preis veröffentlicht, veröffentlicht den auf der Seite, in der Währung der Seite, mit der steuerlichen Darstellung der Seite. Viele B2B-Shops zeigen Geschäftskunden Nettopreise; das Markup wiederholt einfach diese Zahl. Google kennt drei Arten von Preisen: den aktiven Preis, einen Streichpreis mit ",{"tag":203,"children":254},[255],"priceType"," gleich ",{"tag":203,"children":258},[259],"https:\u002F\u002Fschema.org\u002FStrikethroughPrice"," und einen Mitgliederpreis mit ",{"tag":203,"children":262},[263],"validForMemberTier",". Die beiden Kennzeichnungen dürfen nicht in einer Preisangabe kombiniert werden, und der aktive Preis trägt keine von beiden.",{"type":141,"content":266},[267,268,271,272,275],"Staffelpreise, die Tabelle „ab 100 Stück“ auf vielen B2B-Seiten, haben keine eigene Google-Funktion. Schema.org bietet ",{"tag":203,"children":269},[270],"eligibleQuantity"," an Offer und Preisangaben, um das Mengenintervall eines Preises auszudrücken, und ",{"tag":203,"children":273},[274],"eligibleCustomerType"," für die Kundenart, für die ein Angebot gilt. Das ist gültiges Vokabular, aber Googles Doku zu Händlereinträgen führt es nicht auf; ich ergänze es daher nur, wenn das PIM saubere Daten hat, und erwarte keine sichtbare Wirkung. Der aktive Preis im Markup sollte der Einzelpreis sein, den die Seite zuerst zeigt.",{"type":148,"level":149,"id":116,"text":117},{"type":141,"content":278},[279,280,283,284,287,288,291,292,295,296,210,299,210,302,305,306,309,310,312],"B2B-Käufer suchen nach ",{"tag":203,"children":281},[282],"6ES7 214-1AG40-0XB0",", nicht nach „kleine SPS“. Ihr PIM hält die Kennungen, mit denen solche Suchen funktionieren, schon vor, also veröffentlichen Sie sie: ",{"tag":203,"children":285},[286],"sku"," für die eigene Artikelnummer, ",{"tag":203,"children":289},[290],"mpn"," für die Herstellerartikelnummer, ",{"tag":203,"children":293},[294],"gtin"," (oder ",{"tag":203,"children":297},[298],"gtin8",{"tag":203,"children":300},[301],"gtin13",{"tag":203,"children":303},[304],"gtin14","), wo das Produkt eine hat, und ",{"tag":203,"children":307},[308],"brand"," als verschachtelte Brand. Google beschreibt ",{"tag":203,"children":311},[290]," als Kennung, die ein Produkt für einen bestimmten Hersteller eindeutig identifiziert. Dieselben Werte auch sichtbar in den Produktdetails zeigen; Markup und Seite müssen übereinstimmen.",{"type":141,"content":314},[315,316,321],"Artikelnummern treiben auch die interne Suche. Wer an dieser Seite arbeitet: ",{"tag":317,"to":318,"children":319},"link","\u002Fblog\u002Fsemantic-product-search-b2b",[320],"Semantische Produktsuche für B2B"," zeigt, wie exakte Kennungen und semantisches Retrieval zusammenpassen, und beides hängt an denselben sauberen PIM-Feldern.",{"type":148,"level":149,"id":119,"text":120},{"type":141,"content":324},[325,326,329,330,333,334,337,338,341,342,345],"Kabel als Meterware, Schrauben in 200er-Schachteln, Granulat pro Kilogramm: Grundpreise sind B2B-Alltag. Google dokumentiert sie für Händlereinträge und weist darauf hin, dass sie in der EU, in Neuseeland und Australien bei Waren nach Volumen, Länge oder Gewicht besonders wichtig sind. Zeigt die Seite einen Grundpreis, den aktiven Preis in eine ",{"tag":203,"children":327},[328],"UnitPriceSpecification"," setzen und ",{"tag":203,"children":331},[332],"referenceQuantity"," mit einem ",{"tag":203,"children":335},[336],"QuantitativeValue"," verwenden, wobei ",{"tag":203,"children":339},[340],"unitCode"," ein UN\u002FCEFACT-Code ist und ein optionales ",{"tag":203,"children":343},[344],"valueReference"," die Bezugsmenge angibt.",{"type":203,"code":347},"{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"Product\",\n  \"name\": \"Control cable 5G1.5, grey, sold by the metre\",\n  \"image\": \"https:\u002F\u002Fshop.example.at\u002Fmedia\u002Fcable-5g15.jpg\",\n  \"sku\": \"KAB-5G15-GR\",\n  \"mpn\": \"1119305\",\n  \"brand\": { \"@type\": \"Brand\", \"name\": \"ExampleCable\" },\n  \"offers\": {\n    \"@type\": \"Offer\",\n    \"url\": \"https:\u002F\u002Fshop.example.at\u002Fp\u002Fkab-5g15-gr\",\n    \"availability\": \"https:\u002F\u002Fschema.org\u002FInStock\",\n    \"itemCondition\": \"https:\u002F\u002Fschema.org\u002FNewCondition\",\n    \"priceSpecification\": {\n      \"@type\": \"UnitPriceSpecification\",\n      \"price\": 145.00,\n      \"priceCurrency\": \"EUR\",\n      \"referenceQuantity\": {\n        \"@type\": \"QuantitativeValue\",\n        \"value\": 100,\n        \"unitCode\": \"MTR\",\n        \"valueReference\": { \"@type\": \"QuantitativeValue\", \"value\": 1, \"unitCode\": \"MTR\" }\n      }\n    }\n  }\n}",{"type":141,"content":349},[350],"Das Beispiel sagt: Ein 100-Meter-Ring kostet 145 Euro, angezeigt pro Meter. Die Seite muss dasselbe zeigen: Preis, Ringlänge und den Meterpreis. Verkauft der Shop nur ganze Ringe, gehört das in Name und Beschreibung, denn ein Käufer, der 7 Meter bestellen will, springt sonst ab.",{"type":148,"level":149,"id":122,"text":123},{"type":141,"content":353},[354,355,358,359,210,362,231,365,368,369,214,371,373,374,376],"Für echte Variantenfamilien, also Größen oder Farben mit eigener SKU, unterstützt Google ",{"tag":203,"children":356},[357],"ProductGroup"," mit ",{"tag":203,"children":360},[361],"hasVariant",{"tag":203,"children":363},[364],"variesBy",{"tag":203,"children":366},[367],"productGroupID",". Jede Variante braucht eine eindeutige ID wie ",{"tag":203,"children":370},[286],{"tag":203,"children":372},[294],", jede Gruppe eine eindeutige ID, und jede Variante weiterhin die normalen Eigenschaften eines Händlereintrags. Bei Variantenauswahl auf einer Seite muss es eine kanonische URL für die Gruppe geben, typischerweise die Seite ohne vorausgewählte Variante. Google empfiehlt, die Varianten unter der ",{"tag":203,"children":375},[357]," zu verschachteln, weil das die kompakteste Darstellung ist.",{"type":141,"content":378},[379,380,384],"Konfigurierbare B2B-Produkte sind ein anderer Fall. Ein Ventil mit 12 Optionen und Tausenden gültigen Kombinationen ist keine Variantenfamilie, sondern ein Produkt mit Konfigurator. Es als ein Product mit dem Ab-Preis auszeichnen, den die Seite zeigt, und den Rest dem Konfigurator überlassen, wie in ",{"tag":317,"to":381,"children":382},"\u002Fblog\u002Fheadless-product-configurator-b2b",[383],"Headless-Produktkonfiguratoren für B2B"," beschrieben. Generierte Kombinationen als Varianten aufzulisten erzeugt Markup für Produkte, zu denen niemand eine Seite findet.",{"type":148,"level":149,"id":125,"text":126},{"type":141,"content":387},[388,389,210,392,210,395,214,398,210,401,404,405,408,409,412,413,210,416,231,419,422],"Im B2B zählt der Verkäufer so viel wie das Produkt. Googles Organization-Markup hat keine Pflichtfelder und empfiehlt, so viele passende Eigenschaften wie möglich anzugeben. Für einen B2B-Händler sind nützlich: ",{"tag":203,"children":390},[391],"legalName",{"tag":203,"children":393},[394],"address",{"tag":203,"children":396},[397],"vatID",{"tag":203,"children":399},[400],"taxID",{"tag":203,"children":402},[403],"iso6523Code"," (Google empfiehlt ihn mit Präfix 0199 statt ",{"tag":203,"children":406},[407],"leiCode"," und mit Präfix 0060 statt ",{"tag":203,"children":410},[411],"duns","), ",{"tag":203,"children":414},[415],"logo",{"tag":203,"children":417},[418],"contactPoint",{"tag":203,"children":420},[421],"sameAs",". Einmal siteweit angeben, üblicherweise auf der Start- oder Über-uns-Seite.",{"type":141,"content":424},[425,426,429,430,231,433,436,437,440],"Rückgabe- und Versandrichtlinien können inzwischen auf Organization-Ebene stehen: ",{"tag":203,"children":427},[428],"hasMerchantReturnPolicy"," mit einer ",{"tag":203,"children":431},[432],"MerchantReturnPolicy",{"tag":203,"children":434},[435],"hasShippingService"," für eine Versandrichtlinie. Google empfiehlt eine globale Versandrichtlinie unter Organization, statt ",{"tag":203,"children":438},[439],"shippingDetails"," an jedem Angebot zu wiederholen, und behält die Eigenschaften am Angebot für Ausnahmen. B2B-Rückgaberegeln sind oft strenger als im Endkundengeschäft, ohne gesetzliches Widerrufsrecht für Unternehmen; veröffentlichen Sie daher die Richtlinie, die Sie tatsächlich anwenden, mit der echten Anzahl an Tagen und der Art der Gebühren.",{"type":148,"level":149,"id":128,"text":129},{"type":141,"content":443},[444,445,448,449,452,453,456],"Google kann Strukturdaten verarbeiten, die JavaScript in das DOM einfügt, aber der JavaScript-Leitfaden enthält eine spezielle Warnung für Product-Markup: Dynamisch erzeugtes Markup kann Shopping-Crawls seltener und unzuverlässiger machen, was sich schnell ändernden Daten wie Preis und Verfügbarkeit schadet. In Nuxt heißt das: JSON-LD beim SSR rendern, aus denselben Daten, die das Template nutzt. Das Nuxt-Schema.org-Modul (auf Basis von Unhead) erledigt das mit ",{"tag":203,"children":446},[447],"useSchemaOrg"," und Helfern wie ",{"tag":203,"children":450},[451],"defineProduct","; die Site-Identität gehört in die ",{"tag":203,"children":454},[455],"nuxt.config.ts",".",{"type":203,"code":458},"\u003C!-- pages\u002Fp\u002F[slug].vue -->\n\u003Cscript setup lang=\"ts\">\nconst route = useRoute()\n\u002F\u002F anonymous price context only: never the logged-in customer's price list\nconst { data: p } = await useFetch(`\u002Fapi\u002Fcatalog\u002F${route.params.slug}`, { query: { priceContext: 'public' } })\n\nuseSchemaOrg([\n  defineProduct({\n    name: p.value.name,\n    image: p.value.images.map(i => i.url),\n    sku: p.value.sku,\n    mpn: p.value.mpn,\n    ...(p.value.gtin ? { gtin: p.value.gtin } : {}),\n    brand: { '@type': 'Brand', name: p.value.brand },\n    \u002F\u002F publish an offer only when the page shows a public price\n    ...(p.value.publicPrice ? {\n      offers: {\n        price: p.value.publicPrice.net,\n        priceCurrency: 'EUR',\n        availability: p.value.inStock ? 'https:\u002F\u002Fschema.org\u002FInStock' : 'https:\u002F\u002Fschema.org\u002FBackOrder',\n      },\n    } : {}),\n  }),\n])\n\u003C\u002Fscript>\n",{"type":141,"content":460},[461,462,456],"Die wichtige Zeile ist der Preiskontext. Katalogdaten für das Markup ausdrücklich mit der öffentlichen Preisliste abrufen und sicherstellen, dass CDN und Route-Caching nie eine Antwort speichern, die für einen eingeloggten Kunden gerendert wurde. Unhead prüft Googles Eignungsregeln nicht zur Laufzeit, die Validierung gehört also weiterhin in die eigene Pipeline. Hat der Shop mehrere Sprachen, zeichnet jede Locale ihre eigene URL und Währungsdarstellung aus, passend zur ",{"tag":317,"to":463,"children":464},"\u002Fblog\u002Fnuxt-i18n-multilingual-seo-hreflang",[465],"Einrichtung von hreflang und Canonicals",{"type":148,"level":149,"id":131,"text":132},{"type":141,"content":468},[469],"Markup-Fehler in B2B-Shops sind selten Schemafehler. Es sind Datenfehler: ein GTIN-Feld mit einer internen Nummer, ein Preis im Markup aus einer anderen Liste als auf der Seite, eine Verfügbarkeit aus dem ERP-Export von gestern. Die Lösung ist architektonisch: sichtbare Seite und JSON-LD aus derselben API-Antwort erzeugen und Daten an der Quelle korrigieren statt im Template.",{"type":471,"attrs":472,"inner":476,"caption":477},"diagram",{"viewBox":473,"role":474,"aria-labelledby":475},"0 0 720 180","img","sob2b-flow-t sob2b-flow-d","\u003Ctitle id=\"sob2b-flow-t\">Von PIM und ERP zu Strukturdaten\u003C\u002Ftitle>\u003Cdesc id=\"sob2b-flow-d\">PIM und ERP speisen eine Katalog-API; die Nuxt-Seite rendert HTML und JSON-LD aus einer Antwort; ein CI-Check vergleicht beides.\u003C\u002Fdesc>\u003Cdefs>\u003Cmarker id=\"sob2b-flow-ah\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\">\u003Cpath d=\"M0 0L10 5L0 10z\" class=\"d-head\" \u002F>\u003C\u002Fmarker>\u003C\u002Fdefs>\u003Crect x=\"20\" y=\"52\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"79\" y=\"86\" text-anchor=\"middle\" class=\"d-text\">PIM + ERP\u003C\u002Ftext>\u003Ctext x=\"79\" y=\"108\" text-anchor=\"middle\" class=\"d-small\">IDs, Einheiten, Bestand\u003C\u002Ftext>\u003Cpath d=\"M138 90H158\" class=\"d-line\" marker-end=\"url(#sob2b-flow-ah)\" \u002F>\u003Crect x=\"160\" y=\"52\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"220\" y=\"86\" text-anchor=\"middle\" class=\"d-text\">Katalog-API\u003C\u002Ftext>\u003Ctext x=\"220\" y=\"108\" text-anchor=\"middle\" class=\"d-small\">öffentliche Preise\u003C\u002Ftext>\u003Cpath d=\"M279 90H299\" class=\"d-line\" marker-end=\"url(#sob2b-flow-ah)\" \u002F>\u003Crect x=\"301\" y=\"52\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-fill-accent\" \u002F>\u003Ctext x=\"360\" y=\"86\" text-anchor=\"middle\" class=\"d-text\">Nuxt SSR\u003C\u002Ftext>\u003Ctext x=\"360\" y=\"108\" text-anchor=\"middle\" class=\"d-small\">HTML + JSON-LD\u003C\u002Ftext>\u003Cpath d=\"M419 90H439\" class=\"d-line\" marker-end=\"url(#sob2b-flow-ah)\" \u002F>\u003Crect x=\"441\" y=\"52\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"500\" y=\"86\" text-anchor=\"middle\" class=\"d-text\">CI-Check\u003C\u002Ftext>\u003Ctext x=\"500\" y=\"108\" text-anchor=\"middle\" class=\"d-small\">Markup = Seite\u003C\u002Ftext>\u003Cpath d=\"M560 90H580\" class=\"d-line\" marker-end=\"url(#sob2b-flow-ah)\" \u002F>\u003Crect x=\"582\" y=\"52\" width=\"118\" height=\"76\" rx=\"10\" class=\"d-box\" \u002F>\u003Ctext x=\"641\" y=\"86\" text-anchor=\"middle\" class=\"d-text\">Search Console\u003C\u002Ftext>\u003Ctext x=\"641\" y=\"108\" text-anchor=\"middle\" class=\"d-small\">Berichte\u003C\u002Ftext>",[478],"Markup und sichtbaren Inhalt aus derselben Antwort erzeugen und vor und nach dem Deploy prüfen, ob sie übereinstimmen.",{"type":141,"content":480},[481,482,486],"Das ist dieselbe Disziplin wie bei jeder Integration von PIM und Shop; kommen die Produktdaten per Delta-Sync aus SAP oder Infor, gelten die Hinweise aus ",{"tag":317,"to":483,"children":484},"\u002Fblog\u002Fpimcore-erp-delta-sync",[485],"Pimcore-ERP-Delta-Sync"," direkt, denn ein veralteter Preis im Markup ist einfach ein veralteter Preis.",{"type":148,"level":149,"id":134,"text":135},{"type":141,"content":489},[490],"Googles eigener Ablauf ist der Test für Rich Results für einzelne URLs, dann das URL-Prüftool, um zu sehen, wie Google eine veröffentlichte Seite rendert, dann die Berichte in der Search Console. Für einen Shop mit Zehntausenden Produkten ergänze ich eine automatisierte Schicht, die diese Werkzeuge nicht bieten: einen CI-Job, der eine Stichprobe von Produktseiten rendert und das JSON-LD mit dem sichtbaren DOM vergleicht.",{"type":492,"items":493},"list",[494,496,498,504,506],[495],"Der Preis im Markup entspricht dem sichtbaren Preis, und die Währung stimmt.",[497],"Auf Seiten mit „Einloggen für Preis“ gibt es kein Angebot.",[499,231,501,503],{"tag":203,"children":500},[286],{"tag":203,"children":502},[290]," erscheinen in den sichtbaren Produktdetails.",[505],"GTIN-Werte haben eine gültige Prüfziffer; interne Nummern in GTIN-Feldern sind ein häufiger PIM-Fehler.",[507],"Eine mit Login-Cookie gerenderte Seite wird nie aus dem gemeinsamen Cache ausgeliefert.",{"type":141,"content":509},[510,511,515,516,520],"Strukturdaten retten keine dünnen Produktseiten und lassen einen Katalog nur für eingeloggte Nutzer nicht wie einen Endkunden-Shop ranken. Was sie leisten: Sie beschreiben Ihre Produkte in einem Format, das Suchmaschinen und andere Maschinen ohne Raten auswerten können. Zur größeren Frage, wie KI-Systeme Ihre Seiten lesen, siehe mein ",{"tag":317,"to":512,"children":513},"\u002Fblog\u002Fgenerative-engine-optimization-audit",[514],"GEO-Audit"," und den Überblick über ",{"tag":317,"to":517,"children":518},"\u002Fblog\u002Fagentic-commerce-protocols-ucp-acp-guide",[519],"Agentic-Commerce-Protokolle",", die auf denselben sauberen Produktdaten aufbauen.",{"type":148,"level":149,"id":137,"text":138},{"type":492,"ordered":523,"items":524},true,[525,529,532,535,538,541,544,547,550,553,556,559],[526],{"tag":527,"href":38,"children":528},"a",[37],[530],{"tag":527,"href":41,"children":531},[40],[533],{"tag":527,"href":44,"children":534},[43],[536],{"tag":527,"href":47,"children":537},[46],[539],{"tag":527,"href":50,"children":540},[49],[542],{"tag":527,"href":53,"children":543},[52],[545],{"tag":527,"href":56,"children":546},[55],[548],{"tag":527,"href":59,"children":549},[58],[551],{"tag":527,"href":62,"children":552},[61],[554],{"tag":527,"href":65,"children":555},[64],[557],{"tag":527,"href":68,"children":558},[67],[560],{"tag":527,"href":71,"children":561},[70],[563,619,671,739],{"slug":564,"published":5,"minutes":565,"category":7,"tags":566,"keywords":571,"about":583,"sources":591,"cover":613,"og":614,"expertise":74,"locales":615,"lang":77,"title":616,"description":617,"coverAlt":618},"ai-assistant-b2b-shop-customer-support",9,[11,567,568,569,570],"AI assistant","Customer support","LLM agents","EU AI Act",[572,573,574,575,576,577,578,579,580,581,582],"ai assistant b2b shop","ai customer support b2b ecommerce","llm chatbot order status","ai agent customer service tools","chatbot liability air canada","klarna ai assistant results","human handoff chatbot design","eu ai act chatbot disclosure","owasp excessive agency","ai support resolution rate","b2b customer portal chatbot",[584,587,590],{"name":585,"url":586},"Customer service","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCustomer_service",{"name":588,"url":589},"Chatbot","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FChatbot",{"name":33,"url":34},[592,595,598,601,604,607,610],{"title":593,"url":594},"Klarna: AI assistant handles two-thirds of customer service chats in its first month (press release, 2024)","https:\u002F\u002Fwww.klarna.com\u002Finternational\u002Fpress\u002Fklarna-ai-assistant-handles-two-thirds-of-customer-service-chats-in-its-first-month\u002F",{"title":596,"url":597},"Entrepreneur: Klarna is hiring customer service agents again, according to its CEO (May 2025)","https:\u002F\u002Fwww.entrepreneur.com\u002Fbusiness-news\u002Fklarna-ceo-reverses-course-by-hiring-more-humans-not-ai\u002F491396",{"title":599,"url":600},"ABA Business Law Today: BC tribunal confirms companies remain liable for information provided by AI chatbot","https:\u002F\u002Fwww.americanbar.org\u002Fgroups\u002Fbusiness_law\u002Fresources\u002Fbusiness-law-today\u002F2024-february\u002Fbc-tribunal-confirms-companies-remain-liable-information-provided-ai-chatbot\u002F",{"title":602,"url":603},"OWASP GenAI Security Project: LLM06:2025 Excessive Agency","https:\u002F\u002Fgenai.owasp.org\u002Fllmrisk\u002Fllm062025-excessive-agency\u002F",{"title":605,"url":606},"OWASP GenAI Security Project: LLM05:2025 Improper Output Handling","https:\u002F\u002Fgenai.owasp.org\u002Fllmrisk\u002Fllm052025-improper-output-handling\u002F",{"title":608,"url":609},"EU AI Act, Article 50: Transparency obligations","https:\u002F\u002Fartificialintelligenceact.eu\u002Farticle\u002F50\u002F",{"title":611,"url":612},"Intercom: Fin pricing and billable outcomes","https:\u002F\u002Ffin.ai\u002Fpricing","\u002Fimages\u002Fblog\u002Fai-assistant-b2b-shop-customer-support\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fai-assistant-b2b-shop-customer-support\u002Fog.jpg",[76,77,78],"Ein KI-Assistent für den Support im B2B-Shop: Tools, Berechtigungen und die Übergabe an Menschen","Wie man einen KI-Support-Assistenten für einen B2B-Shop baut: was automatisieren, Tools im Kundenkontext, Übergabe an Menschen, AI Act und Kennzahlen.","Eine hohe Mauer mit schmalem Durchgang: Ein Pfeil führt in ein Feld mit drei Quadraten, ein zweiter nach unten zu einer Ablage.",{"slug":620,"published":621,"minutes":622,"category":7,"tags":623,"keywords":628,"about":639,"sources":648,"cover":664,"og":665,"expertise":666,"locales":667,"lang":77,"title":668,"description":669,"coverAlt":670},"nuxt-i18n-multilingual-seo-hreflang","2026-10-16",8,[13,624,625,626,627],"i18n","SEO","hreflang","Multilingual",[629,630,631,632,633,634,635,636,637,638],"nuxt i18n seo","nuxt hreflang","useLocaleHead","nuxt i18n canonical","hreflang x-default","prefix_except_default","multilingual seo nuxt","nuxt i18n sitemap hreflang","hreflang self-referencing","nuxt i18n baseUrl",[640,642,645],{"name":13,"url":641},"https:\u002F\u002Fnuxt.com\u002F",{"name":643,"url":644},"Hreflang","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHreflang",{"name":646,"url":647},"Internationalization and localization","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FInternationalization_and_localization",[649,652,655,658,661],{"title":650,"url":651},"Nuxt i18n docs: SEO (useLocaleHead)","https:\u002F\u002Fi18n.nuxtjs.org\u002Fdocs\u002Fguide\u002Fseo",{"title":653,"url":654},"Nuxt i18n docs: Routing strategies","https:\u002F\u002Fi18n.nuxtjs.org\u002Fdocs\u002Fguide",{"title":656,"url":657},"Google Search Central: Tell Google about localized versions of your page","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fspecialty\u002Finternational\u002Flocalized-versions",{"title":659,"url":660},"Google Search Central: Managing multi-regional and multilingual sites","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fspecialty\u002Finternational\u002Fmanaging-multi-regional-sites",{"title":662,"url":663},"Google Search Central: How to specify a canonical URL","https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fcrawling-indexing\u002Fconsolidate-duplicate-urls","\u002Fimages\u002Fblog\u002Fnuxt-i18n-multilingual-seo-hreflang\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fnuxt-i18n-multilingual-seo-hreflang\u002Fog.jpg","vue-nuxt-developer",[76,77,78],"Mehrsprachiges SEO mit Nuxt i18n: URL-Strategie, hreflang, Canonicals und Sitemaps","So richtet man Nuxt i18n ein, damit Suchmaschinen jede Sprache indexieren: Routing-Strategie, useLocaleHead, hreflang-Regeln, Teilübersetzungen, Sitemaps.","Ein großes Blatt, durch drei waagrechte Verbindungen an eine Spalte aus drei kleineren Blättern gebunden.",{"slug":672,"published":621,"minutes":673,"category":7,"tags":674,"keywords":679,"about":692,"sources":699,"cover":733,"og":734,"expertise":666,"locales":735,"lang":77,"title":736,"description":737,"coverAlt":738},"llm-endpoint-abuse-denial-of-wallet",14,[13,675,676,677,678],"LLM security","Rate limiting","OWASP","Cost control",[680,681,682,683,684,685,686,687,688,689,690,691],"denial of wallet llm","owasp llm10 unbounded consumption","rate limit llm api nuxt","nuxt security rate limiter","protect openai api key frontend","llm token budget per user","limit llm cost per user","nitro api rate limiting","cloudflare turnstile nuxt","anthropic spend limit","openai hard spend limit","llm prompt size limit",[693,695,698],{"name":676,"url":694},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",{"name":696,"url":697},"Denial-of-service attack","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDenial-of-service_attack",{"name":13,"url":641},[700,703,706,709,712,715,718,721,724,727,730],{"title":701,"url":702},"OWASP GenAI Security Project: LLM10:2025 Unbounded Consumption","https:\u002F\u002Fgenai.owasp.org\u002Fllmrisk\u002Fllm102025-unbounded-consumption\u002F",{"title":704,"url":705},"Nuxt Security: Rate Limiter middleware","https:\u002F\u002Fnuxt-security.vercel.app\u002Fmiddleware\u002Frate-limiter",{"title":707,"url":708},"Nuxt Security: Request Size Limiter middleware","https:\u002F\u002Fnuxt-security.vercel.app\u002Fmiddleware\u002Frequest-size-limiter",{"title":710,"url":711},"nuxt-auth-utils: README (GitHub)","https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils",{"title":713,"url":714},"h3: request utilities (getRequestIP, readValidatedBody)","https:\u002F\u002Fh3.dev\u002Futils\u002Frequest",{"title":716,"url":717},"Upstash: Ratelimiting algorithms","https:\u002F\u002Fupstash.com\u002Fdocs\u002Fredis\u002Fsdks\u002Fratelimit-ts\u002Falgorithms",{"title":719,"url":720},"Cloudflare Turnstile: Validate the token","https:\u002F\u002Fdevelopers.cloudflare.com\u002Fturnstile\u002Fget-started\u002Fserver-side-validation\u002F",{"title":722,"url":723},"Claude Platform Docs: Rate limits and spend limits","https:\u002F\u002Fplatform.claude.com\u002Fdocs\u002Fen\u002Fapi\u002Frate-limits",{"title":725,"url":726},"Claude Platform Docs: Token counting","https:\u002F\u002Fplatform.claude.com\u002Fdocs\u002Fen\u002Fbuild-with-claude\u002Ftoken-counting",{"title":728,"url":729},"OpenAI API Docs: Spend limits","https:\u002F\u002Fdevelopers.openai.com\u002Fapi\u002Fdocs\u002Fguides\u002Fspend-limits",{"title":731,"url":732},"OpenAI API Docs: Rate limits","https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fguides\u002Frate-limits","\u002Fimages\u002Fblog\u002Fllm-endpoint-abuse-denial-of-wallet\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fllm-endpoint-abuse-denial-of-wallet\u002Fog.jpg",[76,77,78],"Denial of Wallet: öffentliche LLM-Endpunkte in einer Nuxt-App absichern","Missbrauch und ausufernde Kosten auf einer öffentlichen LLM-Route in Nuxt stoppen: Auth, Rate Limits, Prompt-Limits, Token-Budgets, Turnstile, Spend Limits.","Kleine Punkte drängen an einem schmalen Schlitz in einer Wand, nur zwei kommen durch, darüber eine Schiene.",{"slug":740,"published":741,"minutes":565,"category":7,"tags":742,"keywords":746,"about":754,"sources":761,"cover":783,"og":784,"expertise":785,"locales":786,"lang":77,"title":787,"description":788,"coverAlt":789},"llms-txt-vs-markdown-content-negotiation","2026-10-07",[743,744,745,13],"llms.txt","Content negotiation","AI agents",[743,747,748,749,750,751,752,753],"markdown for agents","content negotiation","AI crawlers","generative engine optimization","Accept text\u002Fmarkdown header","llms-full.txt","agent-ready website",[755,758],{"name":756,"url":757},"Markdown","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMarkdown",{"name":759,"url":760},"Hypertext Transfer Protocol","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHypertext_Transfer_Protocol",[762,765,768,771,774,777,780],{"title":763,"url":764},"llmstxt.org: The \u002Fllms.txt file (proposal, version 2)","https:\u002F\u002Fllmstxt.org\u002F",{"title":766,"url":767},"llmstxt.org: Changes from v1 to v2","https:\u002F\u002Fllmstxt.org\u002Fchanges.html",{"title":769,"url":770},"Cloudflare changelog: Markdown for Agents (12 February 2026)","https:\u002F\u002Fdevelopers.cloudflare.com\u002Fchangelog\u002Fpost\u002F2026-02-12-markdown-for-agents\u002F",{"title":772,"url":773},"Cloudflare docs: Markdown for Agents","https:\u002F\u002Fdevelopers.cloudflare.com\u002Ffundamentals\u002Freference\u002Fmarkdown-for-agents\u002F",{"title":775,"url":776},"Ahrefs: 137K sites analyzed, 97% of llms.txt files never get read (June 2026)","https:\u002F\u002Fahrefs.com\u002Fblog\u002Fllmstxt-study\u002F",{"title":778,"url":779},"Checkly: The current state of content negotiation for AI agents (February 2026)","https:\u002F\u002Fwww.checklyhq.com\u002Fblog\u002Fstate-of-ai-agent-content-negotation\u002F",{"title":781,"url":782},"Suganthan: tracking Cloudflare Markdown for Agents on one site","https:\u002F\u002Fsuganthan.com\u002Fblog\u002Fcloudflare-markdown-for-agents\u002F","\u002Fimages\u002Fblog\u002Fllms-txt-vs-markdown-content-negotiation\u002Fcover.webp","\u002Fimages\u002Fblog\u002Fllms-txt-vs-markdown-content-negotiation\u002Fog.jpg","ai-engineer",[76,77,78],"llms.txt versus Markdown Content Negotiation: was Agenten wirklich abrufen","llms.txt ist ein Vorschlag, Markdown Content Negotiation ein Header. Was KI-Agenten abrufen, was die Logs zeigen und wie man beides aus Nuxt und nginx ausliefert.","Zwei Türen nebeneinander mit quadratischem und rundem Griff, ein ankommendes Quadrat wendet sich rechts.",1792222777472]