### Website Schema Example (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js An example of the configuration object for the schema.org Website type. This includes properties like URL, search path, query input, name, headline, and keywords. ```javascript { url: 'https://www.quintype.com/', searchpath: 'search?q={q}', queryinput: 'required name=q', name: 'Quintype', headline: 'Quintype - Discover news', keywords: 'news,quintype' } ``` -------------------------------- ### SEO Class Initialization with Configuration Source: https://developers.quintype.com/quintype-node-seo/SEO This snippet demonstrates how to instantiate the SEO class with a comprehensive configuration object. The configuration includes static tags, social media card settings, structured data for organizations, and AMP story page enablement. This setup is crucial for customizing the SEO output for a given page. ```javascript new SEO({ staticTags: { "twitter:site": "Quintype", "twitter:domain": "quintype.com", "twitter:app:name:ipad": "Quintype", "twitter:app:name:googleplay": "Quintype (Official App)", "twitter:app:id:googleplay": "com.quintype", "twitter:app:name:iphone": "Quintype for iPhone", "twitter:app:id:iphone": "42", "apple-itunes-app": "app-id=42", "google-play-app": "app-id=com.quintype", "fb:app_id": "42", "fb:pages": "42", "og:site_name": "Quintype" }, enableTwitterCards: true, enableOgTags: true, enableNews: true, structuredData: { organization: { name: "Quintype", url: "http://www.quintype.com/", logo: "https://quintype.com/logo.png", sameAs: ["https://www.facebook.com/quintype","https://twitter.com/quintype_in","https://plus.google.com/+quintype","https://www.youtube.com/user/Quintype"], }, enableNewsArticle: true }, ampStoryPages: true }); ``` -------------------------------- ### Generate Live Blog Posting Data (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Constructs structured data for a live blog post, including headline, description, author, coverage times, and a list of live blog updates. It processes each card to generate schema.org BlogPosting data, including titles, images, and article body content. Dependencies include helper functions like `get`, `getSchemaMainEntityOfPage`, `getSchemaPublisher`, `authorData`, `stripMillisecondsFromTime`, `imageUrl`, `findStoryElementField`, and `getSchemaBlogPosting`. ```javascript function generateLiveBlogPostingData(structuredData = {}, story = {}, publisherConfig = {}, timezone) { const imageWidth = 1200; const imageHeight = 675; const authorSchema = (structuredData.authorSchema && structuredData.authorSchema(story)) || []; const { website: { url = "" } = {} } = structuredData; const orgUrl = get(structuredData, ["organization", "url"], ""); const { mainEntityOfPage } = getSchemaMainEntityOfPage(`${url}/${story.slug}`); const { publisher } = getSchemaPublisher(structuredData.organization, orgUrl); return { headline: story.headline, description: story.summary || get(story, ["seo", "meta-description"]) || story.subheadline, author: authorData(story.authors, authorSchema, publisherConfig), coverageEndTime: stripMillisecondsFromTime(new Date(story["last-published-at"] ), timezone), coverageStartTime: stripMillisecondsFromTime(new Date(story["first-published-at"] ), timezone), dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"] ), timezone), mainEntityOfPage, publisher, liveBlogUpdate: story.cards.map((card) => { const storyElements = get(card, ["story-elements"]); const cardArticleBody = getCompleteTextOfOneCard(storyElements, structuredData.stripHtmlFromArticleBody) || ""; return getSchemaBlogPosting( card, findStoryElementField(card, "title", "text", story.headline), imageUrl( publisherConfig, findStoryElementField(card, "image", "image-s3-key", story["hero-image-s3-key"]), imageWidth, imageHeight ), structuredData, story, timezone, cardArticleBody ); }), }; } ``` -------------------------------- ### Get SEO Data with Nested Configuration Search Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Retrieves SEO data by searching through nested configurations based on owner type and ID. It handles different page types like 'home-page' and 'section-page', falling back to home page SEO data if necessary. ```javascript // The findRelevantConfig method call has no ownerId for home page. // This causes the seoMetadata to be undefined. // So the default value for the ownerId is set to null. function getSeoData(config, pageType, data, url = {}, seoConfig = {}) { function findRelevantConfig(ownerType, ownerId = null) { const seoMetadata = config["seo-metadata"].find((page) => page["owner-type"] === ownerType && page["owner-id"] === ownerId) || {}; const { sections = [] } = config; const section = sections.find((section) => ownerType == "section" && section.id === ownerId) || {}; const customSeo = get(data, ["data", "customSeo"], {}); const sectionCollMetadata = get(data, ["data", "collection", "metadata"], {}); if (seoMetadata.data || section.id || !isEmpty(customSeo)) { const result = Object.assign( {}, { "page-title": customSeo["page-title"] || section.name, title: customSeo.title || section.name, canonicalUrl: customSeo["canonicalUrl"] || sectionCollMetadata["canonical-url"] || section["section-url"] || undefined, }, seoMetadata.data ); if (!result.description) { const homeSeoData = config["seo-metadata"].find((page) => page["owner-type"] === "home") || { data: { description: "" }, }; result.description = customSeo.description || homeSeoData.data.description; } result.ogTitle = customSeo.ogTitle || result.title; result.ogDescription = customSeo.ogDescription || result.description; return result; } } if (seoConfig.customTags && seoConfig.customTags[pageType]) { return buildCustomTags(seoConfig.customTags, pageType); } function getShellSeoData(config) { const seoMetadata = config["seo-metadata"].find((meta) => meta["owner-type"] === "home") || {}; return Object.assign({}, seoMetadata.data, { canonicalUrl: SKIP_CANONICAL }); } switch (pageType) { case "home-page": return findRelevantConfig("home"); case "section-page": return ( findRelevantConfig("section", get(data, ["data", "section", "id"])) || getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url) ); } } ``` -------------------------------- ### Get Page Title with SEO Configuration Source: https://developers.quintype.com/quintype-node-seo/index.js This function retrieves the SEO title for a given page. It utilizes the SEO configuration, general configuration, page type, data, and optional parameters to dynamically generate the title. This is crucial for search engine indexing and user readability. ```javascript getTitle(config, pageType, data, params = {}) { return getTitle(this.seoConfig, config, pageType, data, params); } ``` -------------------------------- ### Generate Video Article Data (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Generates structured data for a video article, combining common data with video-specific information. It extracts metadata like keywords, dates, description, and headline, and uses `getEmbedUrl` to find the video's embed URL. It also calls `generateCommonData` (assumed to be defined elsewhere) for shared properties. Dependencies include `get`, `getEmbedUrl`, `authorData`, `stripMillisecondsFromTime`, and `generateCommonData`. ```javascript function generateVideoArticleData(structuredData = {}, story = {}, publisherConfig = {}, timezone) { const metaKeywords = (story.seo && story.seo["meta-keywords"]) || []; const storyCards = get(story, ["cards"], []); const embedUrl = getEmbedUrl(storyCards); const socialShareMsg = get(story, ["summary"], ""); const metaDescription = get(story, ["seo", "meta-description"], ""); const subHeadline = get(story, ["subheadline"], ""); const headline = get(story, ["headline"], ""); const imageWidth = 1200; const imageHeight = 675; const authorSchema = (structuredData.authorSchema && structuredData.authorSchema(story)) || []; return Object.assign({}, generateCommonData(structuredData, story, publisherConfig, timezone), { author: authorData(story.authors, authorSchema, publisherConfig), keywords: metaKeywords.join(","), dateCreated: stripMillisecondsFromTime(new Date(story["first-published-at"] ), timezone), dateModified: stripMillisecondsFromTime(new Date(story["last-published-at"] ), timezone), description: socialShareMsg || metaDescription || subHeadline || headline, name: story.headline, }); } ``` -------------------------------- ### Get Embed URL from Card Elements (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Iterates through the story elements of cards to find and return an embed URL for various video types like Dailymotion, Instagram, Facebook, Twitter, Vimeo, Brightcove, or YouTube. It maps element subtypes to specific metadata fields to retrieve the correct URL. Returns an empty string if no embed URL is found. ```javascript function getEmbedUrl(cards) { const playerUrlMapping = { "dailymotion-embed-script": "dailymotion-url", instagram: "instagram-url", "facebook-video": "facebook-url", tweet: "tweet-url", "vimeo-video": "vimeo-url", "brightcove-video": "player-url", }; for (const card of cards) { const storyElements = get(card, ["story-elements"], []); for (const elem of storyElements) { if (elem.subtype && elem.subtype in playerUrlMapping) { const playerUrlField = playerUrlMapping[elem.subtype]; if (elem.metadata && elem.metadata[playerUrlField]) { return elem.metadata[playerUrlField]; } } if (elem.type === "youtube-video" && elem.subtype === null) { if (elem.url) { return elem.url; } } } } return ""; } ``` -------------------------------- ### Generate Common SEO Tags (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Constructs an array of common SEO tags, including title, canonical links, author, and news keywords, based on page type and configuration. It merges basic, OG, and Twitter tags, and handles conditional additions like canonical URLs and news standouts. Dependencies include `objectToTags`, `get`, and `SKIP_CANONICAL`. ```javascript export function generateCommonTags(seoConfig, seoData, pageType, data, currentUrl, SKIP_CANONICAL) { const customSeo = get(data, ["customSeo"], {}); const ogTags = seoData.ogTags || {}; const twitterTags = seoData.twitterTags || {}; const basicTags = { "twitter:description": customSeo.twitterDescription || seoData.ogDescription }; const allTags = Object.assign(basicTags, ogTags, twitterTags); const commonTags = [{ tag: "title", children: customSeo.title || data.title || seoData["page-title"] }]; const canonical = seoData.canonicalUrl || currentUrl; if (canonical != SKIP_CANONICAL) { commonTags.push({ tag: "link", rel: "canonical", href: canonical }); } if (pageType === "story-page" || pageType === "story-page-amp") { commonTags.push({ name: "author", content: seoData.author }); } if ((pageType === "story-page" || pageType === "story-page-amp") && seoConfig.enableNews) { commonTags.push({ name: "news_keywords", content: seoData.keywords }); if (get(data, ["data", "story", "seo", "meta-google-news-standout"])) commonTags.push({ tag: "link", rel: "standout", href: seoData.storyUrl || currentUrl }); } return commonTags.concat(objectToTags(allTags)); } ``` -------------------------------- ### Get SEO Data From Collection (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Retrieves SEO data specifically for collection pages. It extracts `name`, `summary`, and `metadata` from the collection data, prioritizing custom SEO settings if available. If a summary is missing, it falls back to the home page description. The function returns an object containing various SEO fields like `page-title`, `title`, `ogTitle`, `description`, `ogDescription`, and `canonicalUrl`. It depends on the `get` utility and `getSeoData` function. ```javascript function getSeoDataFromCollection(config, data) { if (get(data, ["data", "collection", "name"])) { let { name, summary, metadata } = get(data, ["data", "collection"]); const customSeo = get(data, ["data", "customSeo"], {}); if (!summary) { summary = (getSeoData(config, "home-page", data) || {}).description; } const title = customSeo.title || name; const pageTitle = customSeo["page-title"] || name; const ogTitle = customSeo.ogTitle || title; const description = customSeo.description || summary; const ogDescription = customSeo.ogDescription || summary; const collCanonicalUrl = metadata["canonical-url"] || SKIP_CANONICAL; return { "page-title": pageTitle, title, ogTitle, description, ogDescription, canonicalUrl: collCanonicalUrl, }; } } ``` -------------------------------- ### Get Text Elements from Card (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Filters an array of story elements to return only those with the type 'text'. This function is a helper for extracting textual content from a card's story elements. ```javascript function getTextElementsOfOneCard(storyElements = []) { return storyElements.filter((element) => element.type === "text"); } ``` -------------------------------- ### Get Complete Text Content of a Card (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Retrieves all text elements from a card, optionally stripping HTML, and joins them into a single string separated by periods. It utilizes `getTextElementsOfOneCard` and `getPlainText` (assumed to be defined elsewhere) for processing. ```javascript function getCompleteTextOfOneCard(storyElements, stripHtmlFromArticleBody) { return getTextElementsOfOneCard(storyElements) .map((item) => (stripHtmlFromArticleBody ? getPlainText(item.text) : item.text)) .join("."); } ``` -------------------------------- ### Configure Structured Data Options Source: https://developers.quintype.com/quintype-node-seo/global StructuredDataConfig allows fine-grained control over various schema.org structured data types. It supports enabling breadcrumbs, live blogs, video objects, and different configurations for news articles. It also allows specifying organization and website details, header/footer selectors, custom structured data tags, and subscription or product schema. ```javascript StructuredDataConfig { enableBreadcrumbList: true, enableLiveBlog: false, enableVideo: true, enableNewsArticle: "withoutArticleSchema", // or true organization: Organization, website: Website, header: { cssSelector: ".header" }, footer: { cssSelector: ".footer" }, structuredDataTags: ["section-page", "tag-page"], isSubscriptionsEnabled: false, isShowcaseProduct: false, authorSchema: (story, config) => getAuthorWithUrl(story, config) } ``` -------------------------------- ### JavaScript: Construct AMP URL for stories Source: https://developers.quintype.com/quintype-node-seo/src_amp-tags.js The `StoryAmpTags` function generates the necessary `` tag for stories that support AMP. It constructs the AMP URL based on various configurations, including whether to append the host, decode the URL, and use a custom base path. It handles different story templates and respects settings to disable AMP for specific stories or templates. ```javascript import get from "lodash/get"; import { isStoryPublic } from "./utils"; function showAmpTag({ ampStoryPages = true }, pageType, story) { if (!ampStoryPages || pageType !== "story-page") { return false; } const isAmpDisabled = get(story, ["metadata", "story-attributes", "disable-amp-for-single-story", "0"], "false"); if (isAmpDisabled === "true") { return false; } if (ampStoryPages === "public" && !isStoryPublic(story)) { return false; } return true; } const getDomain = (url, domainSlug) => { const domain = domainSlug ? new URL(url).origin : ""; try { return domain; } catch (err) { return ""; } }; /** * StoryAmpTags adds the amphref to stories which support amp. * * To disable adding amphref for a specific story, you need to create a story attribute in bold with the slug {disable-amp-for-single-story} and values {true} and {false}. Set its value to "true" in the story which you want to disable amp. Please make sure to name the attributes and values in the exact same way as mentioned * attribute slug: "disable-amp-for-single-story" values: "true" , "false" * * @extends Generator * @param {*} seoConfig * @param {(boolean|"public")} seoConfig.ampStoryPages Should amp story pages be shown for all stories (true), not shown (false), or only be shown for public stories ("public"). Default: true * @param {(boolean)} seoConfig.appendHostToAmpUrl If set to true, the url to be appended to the slug is computed based on the currentHostUrl and the domain slug, else the url is taken as the sketches host. Default: false * @param {(boolean)} seoConfig.decodeAmpUrl If set to true, the storySlug that goes as the amp href url is decoded, else the storyslug is encoded. Default: false * @param {(boolean)} seoConfig.ignoreAmpHtmlStoryTemplates pass all the story templates you want to ignore from add rel="amphtml" tag. Default: ["visual story"] * @param {...*} params See {@link Generator} for other Parameters */ export function StoryAmpTags(seoConfig, config, pageType, data = {}, opts) { const templatesToIgnore = seoConfig.ignoreAmpHtmlStoryTemplates || ["visual-story"]; const story = get(data, ["data", "story"], {}); const { currentHostUrl = "", domainSlug } = data; // TODO: Remove this condition and always make absolute URL if that's better for AMP discoverability. const ampUrlAppend = seoConfig.appendHostToAmpUrl ? getDomain(currentHostUrl, domainSlug) || config["sketches-host"] : ""; const storySlug = seoConfig.decodeAmpUrl ? decodeURIComponent(story.slug) : encodeURIComponent(story.slug); const { ampPageBasePath = "/amp/story" } = seoConfig; const ampUrl = story["story-template"] === "visual-story" ? `${ampUrlAppend}/${storySlug}` : `${ampUrlAppend}${ampPageBasePath}/${storySlug}`; const ignoreStoryTemplate = templatesToIgnore.includes(story["story-template"]); if (showAmpTag(seoConfig, pageType, story) && !ignoreStoryTemplate) { return [ { tag: "link", rel: "amphtml", href: ampUrl, }, ]; } else { return []; } } ``` -------------------------------- ### Structured Data Configuration API Source: https://developers.quintype.com/quintype-node-seo/global Configure structured data for your website to improve search engine understanding. This includes options for BreadcrumbList, LiveBlog, VideoObject, NewsArticle, Organization, Website, headers, footers, and more. ```APIDOC ## Structured Data Configuration API ### Description Enables and configures various schema.org structured data types to enhance search engine visibility and rich results. ### Method Not Applicable (Configuration Object) ### Endpoint Not Applicable (Configuration Object) ### Parameters #### StructuredDataConfig Properties - **enableBreadcrumbList** (boolean) - Should breadcrumbs be enabled (default true). - **enableLiveBlog** (boolean) - Should LiveBlog schema be implemented for live blogs (default false). - **enableVideo** (boolean) - Should VideoObject be enabled for video stories (default false). - **enableNewsArticle** (string | boolean) - Enables Article and NewsArticle schema. If set to `"withoutArticleSchema"`, only NewsArticle is implemented. (Default false). - **organization** (Organization) - The organization details for schema.org markup. See Organization type for example. - **website** (Website) - The website and search URLs for schema.org markup. See Website type for example. - **header** (Object) - Enable WPHeader tag. Example: `{cssSelector: ".header"}`. - **footer** (Object) - Enable WPFooter tag. Example: `{cssSelector: ".footer"}`. - **structuredDataTags** (Array) - An array of tags describing the publisher. Example: `["section-page", "tag-page"]`. - **isSubscriptionsEnabled** (boolean) - Enable subscription-based schema (default false). - **isShowcaseProduct** (boolean) - Should product type be showcase (default false, fallback: basic). - **authorSchema** (function) - Overrides author-url in Person schema. Expects an array of authors with name and URL. Example: `(story)=> getAuthorWithUrl(story, config);` ### Request Example ```json { "enableBreadcrumbList": true, "enableNewsArticle": "withoutArticleSchema", "organization": { "name": "Quintype", "url": "http://www.quintype.com/", "logo": "https://quintype.com/logo.png", "sameAs": ["https://www.facebook.com/quintype"] }, "website": { "url": "https://www.quintype.com/", "searchpath": "search?q={q}", "name": "Quintype" } } ``` ### Response #### Success Response (Configuration is applied, no direct response body) #### Response Example (N/A) ``` -------------------------------- ### Meta Tag List Generation with React Source: https://developers.quintype.com/quintype-node-seo/index.js The MetaTagList class generates a list of unique meta tags from an array of tag objects. It uses React and ReactDOMServer to render these tags as an HTML string, ensuring only unique tags are included based on a generated key. This is useful for creating SEO-friendly meta tag outputs. ```javascript 1. import { flatMap, get, omit, uniqBy } from "lodash"; 2. import React from "react"; 3. import ReactDomServer from "react-dom/server"; 4. import { StoryAmpTags } from "./src/amp-tags.js"; 5. import { AuthorTags } from "./src/author-tags.js"; 6. import { generateStaticData, generateStructuredData } from "./src/generate-common-seo"; 7. import { ImageTags } from "./src/image-tags.js"; 8. import { StaticTags } from "./src/static-tags.js"; 9. import { StructuredDataTags } from "./src/structured-data/structured-data-tags.js"; 10. import { TextTags, getTitle } from "./src/text-tags.js"; 11. 12. export { 13. AuthorTags, 14. ImageTags, 15. StaticTags, 16. StoryAmpTags, 17. StructuredDataTags, 18. TextTags, 19. generateStaticData, 20. generateStructuredData, 21. }; 22. 23. function tagToKey(tag) { 24. switch (tag.tag || "meta") { 25. case "meta": 26. return `meta-${tag.name || tag.itemprop || "name"}-${tag.property || "property"}`; 27. case "link": 28. return `link-${tag.rel}`; 29. case "title": 30. return `title`; 31. default: 32. return Math.random().toString(); 33. } 34. } 35. 36. export class MetaTagList { 37. constructor(tags) { 38. this.tags = tags; 39. } 40. 41. toString() { 42. const uniqueTags = uniqBy(this.tags.reverse(), tagToKey).reverse(); 43. return ReactDomServer.renderToStaticMarkup( 44. uniqueTags.map((tag) => React.createElement(tag.tag || "meta", omit(tag, "tag"))) 45. ); 46. } 47. 48. addTag() { 49. return new MetaTagList(this.tags.concat([].slice.call(arguments))); 50. } 51. } ``` -------------------------------- ### Main Image Picking Logic for SEO Source: https://developers.quintype.com/quintype-node-seo/src_image-tags.js Orchestrates the image selection process for SEO meta tags based on the page type and available data. It delegates to specific functions like pickImageFromCard, pickImageFromStory, and pickImageFromCollection depending on the context (e.g., story page with card ID, story page, collection page). Dependencies include lodash and other image-picking functions. Returns an object with 'image' and 'alt' or undefined if no image is found. ```javascript import { get } from "lodash"; // Assuming pickImageFromCard, pickImageFromStory, pickImageFromCollection, and getAttribution are defined in the same scope function pickImage({ pageType, config, seoConfig, data, url }) { if (pageType === "story-page" && url.query && url.query.cardId) { const story = get(data, ["data", "story"]) || {}; return pickImageFromCard(story, url.query.cardId) || pickImageFromStory({ story, seoConfig, config }); } else if (pageType === "visual-story" && url.query && url.query.cardId) { const story = get(data, ["story"]) || {}; return pickImageFromCard(story, url.query.cardId) || pickImageFromStory({ story, seoConfig, config }); } else if (pageType === "story-page" || pageType === "story-page-amp") { const story = get(data, ["data", "story"]) || {}; return pickImageFromStory({ story, seoConfig, config }); } else if (pageType === "visual-story") { const story = get(data, ["story"]) || {}; return pickImageFromStory({ story, seoConfig, config }); } else if (get(data, ["data", "collection"])) { return pickImageFromCollection(get(data, ["data", "collection"])); } else { return { image: undefined, alt: undefined }; } } ``` -------------------------------- ### StructuredDataTags Class Initialization (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js The constructor for the StructuredDataTags class. It initializes the class with SEO configuration, a general config object, page type, response data, and URL. It sets up default values for structured data options. ```javascript /** * StructuredData adds tags for schema.org's structured data * * @extends Generator * @param {*} seoConfig * @param {StructuredDataConfig} seoConfig.structuredData Please see {@link StructuredDataConfig} for a full list of supported options * @param {...*} params See {@link Generator} for other Parameters */ export function StructuredDataTags({ structuredData = {} }, config, pageType, response = {}, { url }) { const tags = []; const { story = {}, timezone = null } = response.data || {}; const entities = get(response, ["data", "linkedEntities"], null) || []; const { config: publisherConfig = {} } = response; const { articleType = "" } = publisherConfig["publisher-settings"] || {}; const isStructuredDataEmpty = Object.keys(structuredData).length === 0; const enableBreadcrumbList = get(structuredData, ["enableBreadcrumbList"], true); const structuredDataTags = get(structuredData, ["structuredDataTags"], []); const enableEventsData = get(structuredData, ["enableEventsData"], null); const enableStorySeoEventsData = get(story, ["enableSeoEventsData"], null); let articleData = {}; if (!isStructuredDataEmpty) { articleData = generateArticleData(structuredData, story, publisherConfig, timezone); structuredDataTags.map((type) => { if (pageType === type) { tags.push(ldJson("Organization", structuredData.organization)); if (structuredData.website) { tags.push(ldJson("Website", Object.assign({}, generateWebSiteData(structuredData, story, publisherConfig)))); } } }); } if (!isStructuredDataEmpty && pageType === "home-page") { tags.push(ldJson("Organization", structuredData.organization)); if (structuredData.website) { tags.push(ldJson("Website", Object.assign({}, generateWebSiteData(structuredData, story, publisherConfig)))); } } if (!isStructuredDataEmpty && enableBreadcrumbList) { tags.push(ldJson("BreadcrumbList", generateBreadcrumbListData(pageType, publisherConfig, response.data))); } if (enableEventsData && pageType === "story-page" && enableStorySeoEventsData) { // ... remaining logic } // ... rest of the function } ``` -------------------------------- ### Get Page Title (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Retrieves the page title from various sources, prioritizing custom SEO titles, then direct data titles, and finally falling back to a generated SEO data title. It handles nested data structures and uses a helper function `getSeoData`. Inputs include configuration objects, page type, and data payload. ```javascript export function getTitle(seoConfig, config, pageType, data, params) { const customSeo = get(data, ["data", "customSeo"], {}); if (get(data, ["title"])) return customSeo.title || get(data, ["title"]); if (get(data, ["data", "title"])) return customSeo.title || get(data, ["data", "title"]); const seoData = getSeoData(config, pageType, data, undefined, seoConfig) || {}; return customSeo.title || seoData["page-title"]; } ``` -------------------------------- ### Define Website Schema Source: https://developers.quintype.com/quintype-node-seo/global The Website schema provides essential information about the website itself. This includes the main URL, search path configuration, query input name, site name, headline, and keywords. This helps search engines understand the website's purpose and content. ```json { "url": "https://www.quintype.com/", "searchpath": "search?q={q}", "queryinput": "required name=q", "name": "Quintype", "headline": "Quintype - Discover news", "keywords": "news,quintype" } ``` -------------------------------- ### pickImageFromStory Function Source: https://developers.quintype.com/quintype-node-seo/global Determines the priority order for selecting an image to use in social meta tags. ```APIDOC ## pickImageFromStory ### Description Determines the priority order for selecting an image for social meta tags. ### Method Not Applicable (Internal function) ### Endpoint Not Applicable (Internal function) ### Parameters None ### Request Example None ### Response None (Internal logic) ### Response Example None ``` -------------------------------- ### SEO Class for Managing Tag Generators Source: https://developers.quintype.com/quintype-node-seo/index.js The SEO class acts as the main orchestrator for generating SEO tags. It accepts a configuration object that can include custom generators or override default ones. The class iterates through a list of generators, applying them to the provided data to produce a comprehensive set of SEO meta tags. ```javascript 99. export class SEO { 100. /** 101. * Create a new SEO Object 102. * @param {Object} seoConfig Configuration that is passed as the first argument to each {@link Generator} 103. * @param {Array} seoConfig.generators List of generators to run (optional) 104. * @param {Array} seoConfig.extraGenerators List of generators to run after the main generators run. Generators here can override tags that are returned by earlier generator. See [Custom SEO Malibu Tutorial](https://developers.quintype.com/malibu/tutorial/custom-seo). 105. * @param {Object} seoConfig.pageTypeAliases A map of aliases to their original page type. This is a convenience if you want to have a different page type for some sections. ex: `{"budget-page":"section-page"}` 106. */ 107. constructor(seoConfig = {}) { 108. this.seoConfig = seoConfig; 109. this.generators = ( 110. seoConfig.generators || [TextTags, ImageTags, AuthorTags, StaticTags, StructuredDataTags, StoryAmpTags] 111. ).concat(seoConfig.extraGenerators || []); 112. } 113. 114. getMetaTags(config, pageType, data, params = {}) { 115. pageType = get(this.seoConfig, ["pageTypeAliases", pageType], pageType); 116. return new MetaTagList( ``` -------------------------------- ### Get SEO Data Based on Page Type (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js This function acts as a dispatcher, determining the appropriate SEO data retrieval method based on the provided page type. It handles various page types including collection, tag, story, author, static, not-found, and shell pages, falling back to a default home-page SEO data if no specific handler is found. Dependencies include helper functions like `getSeoDataFromCollection`, `buildTagsFromTopic`, `buildTagsFromStory`, `buildTagsFromAuthor`, `buildTagsFromStaticPage`, `buildTagsFromNotfoundPage`, and `getShellSeoData`. ```javascript function({ "collection-page": () => getSeoDataFromCollection(config, data) || getSeoData(config, "home-page", data, url), "tag-page": () => ( buildTagsFromTopic(config, get(data, ["data", "tag"]), url, data) || getSeoData(config, "home-page", data, url) ), "story-page": () => ( buildTagsFromStory(config, get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url) ), "visual-story": () => buildTagsFromStory(config, get(data, ["story"]), url, data) || getSeoData(config, "home-page", data, url), "story-page-amp": () => ( buildTagsFromStory(config, get(data, ["data", "story"]), url, data) || getSeoData(config, "home-page", data, url) ), "author-page": () => ( buildTagsFromAuthor(config, get(data, ["data", "author"], {}), url, data) || getSeoData(config, "home-page", data, url) ), "static-page": () => ( buildTagsFromStaticPage(config, get(data, ["data", "page"], {}), url, data) || getSeoData(config, "home-page", data, url) ), "not-found": () => buildTagsFromNotfoundPage(config, url, data) || getSeoData(config, "home-page", data, url), "shell": () => getShellSeoData(config), default: () => getSeoData(config, "home-page", data, url) }[pageType] || (() => getSeoData(config, "home-page", data, url)))(); ``` -------------------------------- ### Generate SEO Data using Generators Source: https://developers.quintype.com/quintype-node-seo/index.js This method iterates through various SEO generators, applying them with the provided configuration and data to produce comprehensive SEO information. It takes SEO configuration, general configuration, page type, data, and parameters as input. ```javascript flatMap(this.generators, (generator) => generator(this.seoConfig, config, pageType, data, params)) ``` -------------------------------- ### TextTags Generator for SEO Meta Tags (JavaScript) Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js The `TextTags` function is a generator that constructs essential SEO meta tags including canonical URLs, titles, descriptions, and keywords. It supports Open Graph (OG) tags for Facebook and Twitter Card tags, along with specific tags for Google News. The function can also incorporate custom tags defined for specific page types. It relies on `getSeoData` to fetch base SEO information and `get` for data access, and it uses a `SKIP_CANONICAL` constant to manage canonical URL inclusion. ```javascript export function TextTags(seoConfig, config, pageType, data, { url }) { const seoData = getSeoData(config, pageType, data, url, seoConfig); const customSeo = get(data, ["data", "customSeo"], {}); if (!seoData) return []; const currentUrl = `${config["sketches-host"]}${url.pathname}`; const basicTags = { description: customSeo.description || seoData.description, title: customSeo.title || seoData.title, keywords: customSeo.keywords || seoData.keywords, }; const ogUrl = customSeo.ogUrl || seoData.ogUrl || seoData.canonicalUrl || currentUrl; const ogTags = seoConfig.enableOgTags ? { "og:type": pageType === "story-page" || pageType === "story-page-amp" ? "article" : "website", "og:url": ogUrl === SKIP_CANONICAL ? undefined : ogUrl, "og:title": customSeo.ogTitle || seoData.ogTitle, "og:description": customSeo.ogDescription || seoData.ogDescription, } : undefined; const twitterTags = seoConfig.enableTwitterCards ? { "twitter:card": "summary_large_image", "twitter:title": customSeo.twitterTitle || seoData.ogTitle, ``` -------------------------------- ### Helper Function: visualStorySchema Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Generates a JSON-LD schema for visual stories, specifically creating an 'ImageGallery' with 'ImageObject' items. It extracts image data, titles, descriptions, and captions from story cards and includes a hero image if available. It also incorporates meta description, subheadline, and headline for the main schema properties. ```javascript function visualStorySchema() { if (!story || !publisherConfig) return null; const storyCards = get(story, ["cards"], []).filter((card) => getAllowedCards(card)); const galleryItems = storyCards.map((card) => { const imageElement = card["story-elements"].find((el) => el.type === "image"); if (!imageElement) return; // for now schema is added only for images const titleElement = card["story-elements"].find((el) => el.type === "title"); const textElements = card["story-elements"].filter((el) => el.type === "text" && el.subtype !== "cta"); const description = textElements.reduce( (acc, current) => (acc ? `${acc}. ${getPlainText(current.text) || ""}` : getPlainText(current.text) || ""), "" ); const imgUrl = imageUrl(publisherConfig, imageElement["image-s3-key"]); const strippedImgUrl = stripQueryParams(imgUrl); return { "@type": "ImageObject", image: strippedImgUrl, name: get(titleElement, ["text"]), contentUrl: strippedImgUrl, description: description, caption: getPlainText(get(imageElement, ["title"]) || ""), }; }); const heroImgSrc = story["hero-image-s3-key"]; if (heroImgSrc) { const imgUrl = imageUrl(publisherConfig, heroImgSrc); const strippedImgUrl = stripQueryParams(imgUrl); galleryItems.unshift({ "@type": "ImageObject", image: strippedImgUrl, name: get(story, ["headline"]), contentUrl: strippedImgUrl, description: get(story, ["subheadline"]), caption: getPlainText(get(story, ["hero-image-caption"]) || ""), }); } const metaDescription = get(story, ["seo", "meta-description"], ""); const subHeadline = get(story, ["subheadline"], ""); const headline = get(story, ["headline"], ""); const schema = Object.assign({}, getSchemaMainEntityOfPage(story.url), { headline: story.headline || "Media Gallery", description: metaDescription || subHeadline || headline, hasPart: { "@type": "ImageGallery", associatedMedia: galleryItems }, }); } ``` -------------------------------- ### Helper Function: storyTags Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js This function returns different schema types based on the story's template and enabled features. It can generate 'LiveBlogPosting' for live blogs, 'VideoObject' for videos, or a generic 'Article' schema. It defaults to an empty object if no conditions are met. ```javascript function storyTags() { if (structuredData.enableLiveBlog && story["story-template"] === "live-blog") { return ldJson( "LiveBlogPosting", Object.assign({}, generateLiveBlogPostingData(structuredData, story, publisherConfig, timezone)) ); } if (structuredData.enableVideo && story["story-template"] === "video") { return ldJson("VideoObject", generateVideoArticleData(structuredData, story, publisherConfig, timezone)); } if (structuredData.enableNewsArticle !== "withoutArticleSchema") { return ldJson("Article", articleData); } return {}; } ``` -------------------------------- ### Generate WPHeader and WPFooter Schema Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Adds JSON-LD schemas for 'WPHeader' and 'WPFooter' if structured data is not empty and header/footer information is available. These are likely used for site-wide navigation and structural information. ```javascript if (!isStructuredDataEmpty && structuredData.header) { tags.push(ldJson("WPHeader", getSchemaHeader(structuredData.header))); } if (!isStructuredDataEmpty && structuredData.footer) { tags.push(ldJson("WPFooter", getSchemaFooter(structuredData.footer))); } ``` -------------------------------- ### Build SEO Tags from Static Page Data Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Constructs SEO tags for static pages using metadata from the page, custom SEO settings, and global configuration. It merges information from various sources to create a comprehensive set of SEO meta tags. ```javascript function buildTagsFromStaticPage(config, page, url = {}, data) { const seoData = get(page, ["metadata", "seo"], {}); const customSeo = get(data, ["data", "customSeo"], {}); const { "meta-title": metaTitle, "meta-description": metaDescription, "meta-keywords": keywords } = seoData; const title = customSeo.title || metaTitle || page.title; const pageTitle = customSeo["page-title"] || title; const description = customSeo.description || metaDescription; const ogTitle = customSeo.ogTitle || title; const staticPageUrl = `${config["sketches-host"]}${url.pathname}`; const ogDescription = customSeo.ogDescription || description; const canonicalUrl = customSeo.canonicalUrl || staticPageUrl; return { title, "page-title": pageTitle, description, keywords: `${title},${config["publisher-name"]}`, canonicalUrl, ogUrl: staticPageUrl, ogTitle, ogDescription, keywords: customSeo.keywords || keywords, }; } ``` -------------------------------- ### Build Custom SEO Tags Source: https://developers.quintype.com/quintype-node-seo/src_text-tags.js Retrieves custom SEO tag configurations based on a page type. If a configuration exists for the specified page type, it's returned; otherwise, an empty object is returned. ```javascript function buildCustomTags(customTags = {}, pageType = "") { const configObject = customTags[pageType]; if (configObject) { return configObject; } return {}; } ``` -------------------------------- ### Generate Media Gallery JSON-LD Schema Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js This function generates a JSON-LD schema for a MediaGallery. It takes a schema object as input and returns the structured data. This is typically used for SEO purposes to provide search engines with detailed information about the media content. ```javascript return ldJson("MediaGallery", schema); ``` -------------------------------- ### Pick Image from Collection for SEO Source: https://developers.quintype.com/quintype-node-seo/src_image-tags.js Retrieves a cover image and alt text from a collection's metadata for SEO. It looks for a 'cover-image-s3-key' within the collection's metadata. Dependencies include Quintype's FocusedImage class. It returns an object with 'image' and 'alt' properties or an empty object if no cover image is found. ```javascript import { FocusedImage } from "quintype-js"; function pickImageFromCollection(collection) { const coverImage = get(collection, ["metadata", "cover-image"]) || {}; if (!coverImage["cover-image-s3-key"]) return {}; const alt = collection.summary || collection.name || null; return { image: new FocusedImage(coverImage["cover-image-s3-key"], coverImage["cover-image-metadata"] || {}), alt }; } ``` -------------------------------- ### Format Image URL for CDN Source: https://developers.quintype.com/quintype-node-seo/src_structured-data_structured-data-tags.js Constructs a CDN-ready image URL with specified dimensions and optimization parameters. It handles absolute image URLs and constructs them based on the publisher's CDN configuration. Ensures images are formatted for web delivery. ```javascript function imageUrl(publisherConfig, s3Key, width, height) { const imageSrc = /^https?.*/.test(publisherConfig["cdn-image"]) ? publisherConfig["cdn-image"] : `https://${publisherConfig["cdn-image"]}`; width = width || ""; height = height || ""; return `${imageSrc}/${s3Key}?w=${width}&h=${height}&auto=format%2Ccompress&fit=max&enlarge=true`; } ```