Nødsituation

Ved akutte situationer eller nedbrud kan du sende en SMS til vores vagttelefon

Vagttelefon (kun SMS)

+45 29 70 15 95

Send en SMS med følgende information:

  • Dit navn og webshop
  • Beskrivelse af problemet
  • Dit telefonnummer for tilbageringning

Bemærk: Denne service er kun til kritiske situationer hvor din webshop er nede eller har alvorlige problemer. For almindelig support, brug venligst vores normale supportkanaler.

Implementér strukturerede data (Schema.org) i dit Shoporama-tema

Komplet guide til JSON-LD strukturerede data i et Shoporama-tema. Smarty-eksempler til Product, Store, BreadcrumbList og AggregateRating med korrekte safe-metoder.

Læsetid: ca. 19 minutter
Shopejer Udvikler

Strukturerede data (Schema.org) hjælper Google og andre søgemaskiner med at forstå indholdet på dine sider. For en webshop er det især relevant at markere produkter, butiksinformation, brødkrummer og produktanmeldelser, så du kan opnå "rich snippets" i søgeresultaterne med fx pris, lagerstatus og stjernebedømmelse.

Tip: Shoporama tilføjer automatisk grundlæggende Schema.org-markup for produkter via vores indbyggede SEO-funktioner. Læs om automatisk struktureret data og SEO i Shoporama. Denne artikel er til dig, der vil bygge eller udvide markup'et selv i dit tema.

Kodeeditoren i Shoporama-admin med MBN-Delaware-temaets filliste til venstre og after_purchase.html åben i editoren til højre
Kodeeditoren under Tema, hvor du redigerer temaets HTML-filer. Her indsætter du dine JSON-LD-blokke, fx i produkt-templaten.

Hvad er strukturerede data?

Strukturerede data er en standardiseret måde at beskrive sidens indhold på i et format, søgemaskiner forstår. JSON-LD er det anbefalede format og placeres i et <script type="application/ld+json">-tag. Det påvirker ikke sidens visuelle udseende, men giver søgemaskinerne struktureret information, der kan bruges til rich snippets.

De rigtige safe-metoder på SafeProduct, SafeWebshop og SafeCompany

Inden du bygger dit eget JSON-LD-markup er det vigtigt at kende de safe-metoder, der faktisk findes på objekterne i et Shoporama-tema. Disse er de stabile metoder du kan bruge i Smarty.

$product (SafeProduct):

  • getName(), getDescription(), getOwnId(), getGtin(), getMpn(), getBrandName()
  • getPrice(), getSalePrice(), getRealPrice(), getLowest30DayPrice()
  • getStockCount(), getIsInStock() og getAllowNegativeStock()
  • getImage() og getImages() returnerer SafeImage med getSrc($w, $h, $type)
  • getAvgRating($no_round), getReviewCount(), getProductReviews($limit)
  • getUrl() giver produktets relative sti

$webshop (SafeWebshop):

  • getName(), getDescription(), getCurrency(), getUrl(), getLogo()
  • getCompany() returnerer SafeCompany

$webshop->getCompany() (SafeCompany):

  • getName(), getRegNr(), getEmail(), getPhone()
  • getAddress(), getZipcode(), getCity(), getCountry()

Bemærk: Brug ikke $product->getStock(), $product->getAverageRating(), $webshop->getCurrencyCode(), $webshop->getDomain(), $webshop->getEmail() eller $webshop->getPhone(). De findes ikke som safe-metoder, og dit tema vil fejle. Brug i stedet getStockCount(), getAvgRating(), getCurrency(), getUrl() og getCompany()->getEmail()/getPhone().

Escaping: brug @json_encode, ikke escape:javascript

JSON-LD er JSON, og JSON har sine egne regler for, hvordan specialtegn skrives. Det er den klart hyppigste årsag til, at et ellers korrekt markup bliver afvist af Google, så det er værd at få styr på, inden du skriver din første blok.

Brug ikke |escape:'javascript' i JSON-LD. Den laver en apostrof om til \', hvilket er gyldigt i JavaScript, men ikke i JSON, hvor den escape-sekvens slet ikke findes. Ét eneste produkt med en apostrof i navnet, fx "Bo's Kaffe", er derfor nok til, at Google kasserer hele blokken, og siden mister pris, lagerstatus og stjerner på én gang. I Search Console optræder det som "Bad escape sequence in string".

Brug i stedet |@json_encode. Den skriver værdien som en færdig JSON-streng inklusive anførselstegnene, så anførselstegnene skal fjernes fra din template:

Forkert:  "name": "<{$product->getName()|escape:'javascript'}>",
Rigtigt:  "name": <{$product->getName()|@json_encode}>,

Dermed er apostroffer, anførselstegn, backslash og linjeskift håndteret korrekt. Linjeskift er værd at bemærke: en anmeldelsestekst skrevet over flere linjer ødelægger JSON, hvis den ikke er escapet, og det fanges hverken af |escape eller af <{strip}>. Oveni escaper metoden skråstreger, så en </script> i et produktnavn ikke kan lukke din script-blok i utide.

  • Kan feltet være tomt, så sæt |default:"" ind før, fx <{$product->getOwnId()|default:""|@json_encode}>. Ellers bliver resultatet null i stedet for en tom streng.
  • Skal HTML fjernes først, sætter du |strip_tags:true ind før, altså |strip_tags:true|@json_encode.
  • Der findes ingen |escape:'json' i Smarty. Bruger du den, sker der ingen escaping overhovedet.
  • Tal og faste værdier, fx pris, valuta og schema.org-URL'er, skal fortsat stå i almindelige anførselstegn. De indeholder ikke specialtegn.
  • |escape:'javascript' er stadig det rigtige valg i almindelig JavaScript, fx i en dataLayer-push. Det er kun i JSON-LD, den er forkert.

Produkt-markup (Product)

Den vigtigste type for en webshop er Product-markup. Indsæt blokken i din produkt-template (typisk product/view.html):

<{assign var="availability" value=$product->getStructuredDataAvailability()}>
<{assign var="availabilityStarts" value=$product->getStructuredDataAvailabilityStarts()}>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": <{$product->getName()|@json_encode}>,
  "description": <{$product->getDescription()|strip_tags|@json_encode}>,
  "sku": <{$product->getOwnId()|@json_encode}>,
  <{if $product->getGtin()}>
  "gtin": <{$product->getGtin()|@json_encode}>,
  <{/if}>
  <{if $product->getMpn()}>
  "mpn": <{$product->getMpn()|@json_encode}>,
  <{/if}>
  <{if $product->getBrandName()}>
  "brand": {
    "@type": "Brand",
    "name": <{$product->getBrandName()|@json_encode}>
  },
  <{/if}>
  <{if $product->getImage()}>
  "image": "<{$product->getImage()->getSrc(800, 800, 'fit')}>",
  <{/if}>
  "offers": {
    "@type": "Offer",
    "url": "<{$webshop->getUrl()}><{$product->getUrl()}>",
    "price": "<{$product->getRealPrice()|string_format:"%.2f"}>",
    "priceCurrency": "<{$webshop->getCurrency()}>",
    "availability": <{$availability|@json_encode}><{if $availabilityStarts}>,
    "availabilityStarts": <{$availabilityStarts|@json_encode}><{/if}>
  }
}
</script>

Tre vigtige pointer i eksemplet:

  • Pris hentes med getRealPrice(), så eventuel udsalgspris og rabat indgår, og momsen er lagt til.
  • Valuta hentes med getCurrency()$webshop (returnerer fx "DKK" eller "EUR").
  • Lagerstatus hentes med getStructuredDataAvailability(), der returnerer den færdige schema.org-URL, altså https://schema.org/InStock, OutOfStock eller BackOrder. Metoden bruger nøjagtig samme logik som vores Google Shopping-feed, så produktsiden og feedet altid siger det samme. Byg den ikke selv af getIsInStock() og getAllowNegativeStock(): den kombination overser, at butikken selv bestemmer, om restordre-varer skal sendes som "backorder", og så opstår netop den uoverensstemmelse, du ville undgå.
  • getStructuredDataAvailabilityStarts() giver den forventede leveringsdato på en restordre-vare, altså feltet availabilityStarts. Den returnerer tomt, når varen ikke er i restordre, eller når du ikke har udfyldt en leveringstid under Google Shopping, så pak den ind i et <{if}> som i eksemplet.

Vigtigt hvis du bruger Google Shopping: Google sammenligner lagerstatussen på produktsiden med den, du sender i produktfeedet. Passer de to ikke sammen, kan Google deaktivere varen, så den forsvinder fra både Shopping-annoncer og de gratis produktvisninger. Bruger du getStructuredDataAvailability(), kan de to ikke komme på tværs af hinanden, for feedet og produktsiden bygger på samme metode. Skriver du din egen lagerstatus-logik, risikerer du, at siden siger "udsolgt" om en vare, feedet melder som købbar. Shoporamas egne temaer håndterer det allerede. Læs mere i Opsæt Google Shopping Feed.

Førpris (Omnibus-direktivet)

Hvis produktet er på udsalg, anbefaler Google at vise den laveste pris de seneste 30 dage som referencepris. Brug getLowest30DayPrice() til at hente den. Den returnerer null hvis der ikke er nok prishistorik.

Produktanmeldelser (AggregateRating)

Hvis du bruger Shoporamas indbyggede anmeldelsessystem, kan du tilføje en gennemsnitsbedømmelse direkte i Product-markup'et. Det kan give stjerner i Googles søgeresultater. Indsæt blokken inden i Product-objektet:

<{if $product->getReviewCount() > 0}>
,
"aggregateRating": {
  "@type": "AggregateRating",
  "ratingValue": "<{$product->getAvgRating(true)|string_format:"%.1f"}>",
  "reviewCount": "<{$product->getReviewCount()}>"
}
<{/if}>

Parameteren true til getAvgRating() giver et ikke-afrundet gennemsnit, hvilket Google foretrækker (fx 4,3 i stedet for blot 4).

Butiksinformation (Store / LocalBusiness)

Du kan også markere selve webshoppen, fx i din footer-template. Hent kontaktdata fra $webshop->getCompany(), så de oplysninger, du allerede har sat under Firma, Firmaoplysninger, indgår automatisk:

<{$company = $webshop->getCompany()}>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Store",
  "name": <{$webshop->getName()|@json_encode}>,
  "url": "<{$webshop->getUrl()}>"
  <{if $company}>
  ,
  <{if $company->getEmail()}>
  "email": <{$company->getEmail()|@json_encode}>,
  <{/if}>
  <{if $company->getPhone()}>
  "telephone": <{$company->getPhone()|@json_encode}>,
  <{/if}>
  "address": {
    "@type": "PostalAddress",
    "streetAddress": <{$company->getAddress()|@json_encode}>,
    "postalCode": <{$company->getZipcode()|@json_encode}>,
    "addressLocality": <{$company->getCity()|@json_encode}>,
    "addressCountry": <{$company->getCountry()|@json_encode}>
  }
  <{/if}>
  <{if $webshop->getLogo()}>
  ,
  "logo": "<{$webshop->getLogo()->getSrc(400, 400, 'fit')}>"
  <{/if}>
}
</script>

Sørg for at firmaoplysninger er fyldt korrekt ud i admin under Firma, Firmaoplysninger. Hvis du har en fysisk butik, kan du bruge LocalBusiness eller en mere specifik subtype (fx ClothingStore) som @type.

Brødkrummer (BreadcrumbList)

På produkt- og kategorisider er det god SEO-praksis at markere brødkrummer. Eksempel for et produkt med hovedkategori:

<{if $product->getMainCategory()}>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": <{$webshop->getName()|@json_encode}>,
      "item": "<{$webshop->getUrl()}>"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": <{$product->getMainCategory()->getName()|@json_encode}>,
      "item": "<{$webshop->getUrl()}><{$product->getMainCategory()->getUrl()}>"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": <{$product->getName()|@json_encode}>,
      "item": "<{$webshop->getUrl()}><{$product->getUrl()}>"
    }
  ]
}
</script>
<{/if}>

Test dine strukturerede data

Brug Googles og Schema.org's værktøjer til at validere dit markup, inden du går live:

  • Rich Results Test tjekker om dit markup giver rich snippets i Google
  • Schema Markup Validator validerer JSON-LD-syntaksen
  • I Google Search Console kan du se rapporter over dine produkter og rich results, efter siderne er crawlet

Tip: Hvis du allerede har aktiveret automatisk struktureret data i Shoporama, så vær opmærksom på at undgå dubletter. Tjek hvad der allerede er på siden, inden du tilføjer dit eget markup.

Ofte stillede spørgsmål

Skal jeg overhovedet selv kode JSON-LD?

Nej, ikke nødvendigvis. Shoporama har indbygget automatisk struktureret data for produkter, og det dækker det meste. Selv-kodning er primært relevant, hvis du vil tilføje yderligere felter eller bruge en mere specifik @type, fx for hoteller, restauranter eller events.

Hvilke schema-typer kan jeg bruge ud over Product?

I et Shoporama-tema kan du bygge alle Schema.org-typer, der findes som JSON-LD. De mest brugte for webshops er Product, Offer, AggregateRating, Review, BreadcrumbList, Store, LocalBusiness, Organization, WebSite med SearchAction og FAQPage til faste FAQ-sider.

Hvorfor får jeg fejl i Rich Results Test?

De typiske fejl er manglende felter (fx price uden priceCurrency), forkert formatering af tal, eller specialtegn der ødelægger JSON-syntaksen. Langt den hyppigste årsag er escaping: kør alle tekstværdier igennem |@json_encode og ikke |escape:'javascript', som laver ugyldig JSON ud af en apostrof. Se afsnittet om escaping længere oppe.

Hvor lang tid går der, før jeg ser stjerner i Google?

Google skal først crawle siden igen, før dit nye markup tages i brug. Det kan tage fra få dage til et par uger. I Search Console kan du anmode om hurtig genindeksering af enkeltsider.

Påvirker det mine placeringer i Google?

Strukturerede data er ikke en direkte rangeringsfaktor, men rich snippets med fx pris og stjerner gør dit søgeresultat mere synligt og kan øge klikraten. På sigt er det med til at forbedre din samlede SEO.

Hvordan håndterer jeg moms i pris-feltet?

Brug getRealPrice() som returnerer prisen inkl. moms. Det er den pris, kunden møder i butikken, og den, Google forventer i Product-markup på en B2C-shop. Skal du vise pris ekskl. moms (B2B), brug getPriceExVat() i stedet.

Skal jeg have anmeldelser før jeg viser AggregateRating?

Ja. Tilføj kun AggregateRating, hvis produktet faktisk har anmeldelser. Eksemplet ovenfor tjekker getReviewCount() > 0, så markup ikke tilføjes til produkter uden anmeldelser. Falske eller tomme ratings kan udløse en straf fra Google.

Hvor finder jeg mine firmaoplysninger til Store-markup?

Gå ind i admin under Firma, Firmaoplysninger. De data du udfylder her bliver tilgængelige via $webshop->getCompany() i dit tema, så Store-blokken bliver udfyldt automatisk.

Hvor skal JSON-LD placeres på siden?

Du kan placere det enten i <head> eller i <body>. Begge dele er gyldige ifølge Google. Til produktsider er det almindeligt at placere det i bunden af produkt-templaten.

Min webshop sælger på flere sprog. Skal jeg have markup pr. sprog?

Ja, navne og beskrivelser i markup'et skal afspejle den side, kunden ser. Da $product->getName() og $product->getDescription() automatisk returnerer den oversatte tekst for det aktive sprog, sker det helt automatisk når du bruger eksemplerne her.

Har du brug for hjælp med strukturerede data? Kontakt os på support@shoporama.dk.

Relaterede artikler

SEO i Shoporama

Komplet guide til SEO-funktionerne i Shoporama – fra meta-tags og sitemap til Google Shopping og AI-assistent.

Produktanmeldelser

Lær hvordan du bruger produktanmeldelser i Shoporama. Indsaml kundeanmeldelser automatisk, moderer dem og vis stjernebedømmelser på dine produktsider.

Variabler i et Shoporama-tema

Oversigt over globale og side-specifikke Smarty-variabler tilgængelige i Shoporama-temaer.

Implementer tracking i et Shoporama-tema

Udvikler-guide til e-commerce tracking i et Shoporama-tema: data layer, Google Tag Manager, custom events og korrekte produkt-id'er med...

Blog tilknyttet din shop

Komplet guide til blogfunktionen i Shoporama – opret indlæg, planlæg publicering, tilknyt produkter, optimér til søgemaskiner og brug dynamiske...

Prishistorik og Omnibus-direktivet

Shoporama logger automatisk prisændringer og beregner den korrekte førpris iht. Omnibus-direktivet (EU 2019/2161). Standardtemaerne understøtter...

Sæt noindex på en side, produkt eller kategori

Guide til at sætte noindex i Shoporama, så søgemaskiner ikke indexerer bestemte sider.

Opsæt Google Shopping Feed

Guide til at opsætte Google Shopping Feed i Shoporama: kategorier, custom labels, produkt-id og katalogmatch samt lands- og sprogfeeds.

Få stjerner i Google Shopping med produktanmeldelser

Sådan får du dine produktanmeldelser vist som stjerner i Google Shopping med Shoporamas indbyggede review-feed: aktivering, krav fra Google, og...

Produkt-id'er og katalogmatch: sådan matcher dine annoncer dit produktfeed

Forklaring af hvilket produkt-id Shoporamas tracking sender, hvorfor det altid matcher Google Shopping-feedet, hvad katalogmatch betyder for dine...