Skip to content

Commit

Permalink
Merge pull request Shopify#47 from Shopify/fd-sitemap
Browse files Browse the repository at this point in the history
Add sitemap.xml
  • Loading branch information
frandiox authored Sep 30, 2022
2 parents ef25ca9 + cb3b35d commit 7a28e7b
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
61 changes: 61 additions & 0 deletions app/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
ProductConnection,
ProductVariant,
SelectedOptionInput,
PageConnection,
Shop,
} from "@shopify/hydrogen-ui-alpha/storefront-api-types";
import {
Expand Down Expand Up @@ -791,3 +792,63 @@ export async function getTopProducts({ count = 4 }: { count?: number } = {}) {

return data.products;
}

const SITEMAP_QUERY = `#graphql
query sitemaps($urlLimits: Int, $language: LanguageCode)
@inContext(language: $language) {
products(
first: $urlLimits
query: "published_status:'online_store:visible'"
) {
edges {
node {
updatedAt
handle
onlineStoreUrl
title
featuredImage {
url
altText
}
}
}
}
collections(
first: $urlLimits
query: "published_status:'online_store:visible'"
) {
edges {
node {
updatedAt
handle
onlineStoreUrl
}
}
}
pages(first: $urlLimits, query: "published_status:'published'") {
edges {
node {
updatedAt
handle
onlineStoreUrl
}
}
}
}
`;

interface SitemapQueryData {
products: ProductConnection;
collections: CollectionConnection;
pages: PageConnection;
}

export async function getSitemap(variables: {
language: string;
urlLimits: number;
}) {
return getStorefrontData<SitemapQueryData>({
query: SITEMAP_QUERY,
variables,
});
}
139 changes: 139 additions & 0 deletions app/routes/[sitemap.xml].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import type { LoaderArgs } from "@remix-run/cloudflare";
import { flattenConnection } from "@shopify/hydrogen-ui-alpha";
import { getSitemap } from "~/data";

const MAX_URLS = 250; // the google limit is 50K, however, SF API only allow querying for 250 resources each time

export async function loader({ request }: LoaderArgs) {
const data = await getSitemap({
language: "EN",
urlLimits: MAX_URLS,
});

return new Response(
shopSitemap({ data, baseUrl: new URL(request.url).origin }),
{
headers: {
"content-type": "application/xml",
// Cache for 24 hours
"cache-control": `max-age=${60 * 60 * 24}`,
},
}
);
}

interface ProductEntry {
url: string;
lastMod: string;
changeFreq: string;
image?: {
url: string;
title?: string;
caption?: string;
};
}

function shopSitemap({
data,
baseUrl,
}: {
data: Awaited<ReturnType<typeof getSitemap>>;
baseUrl: string;
}) {
const productsData = flattenConnection(data.products)
.filter((product) => product.onlineStoreUrl)
.map((product) => {
const url = `${baseUrl}/products/${product.handle}`;

const finalObject: ProductEntry = {
url,
lastMod: product.updatedAt!,
changeFreq: "daily",
};

if (product.featuredImage?.url) {
finalObject.image = {
url: product.featuredImage!.url,
};

if (product.title) {
finalObject.image.title = product.title;
}

if (product.featuredImage!.altText) {
finalObject.image.caption = product.featuredImage!.altText;
}
}

return finalObject;
});

const collectionsData = flattenConnection(data.collections)
.filter((collection) => collection.onlineStoreUrl)
.map((collection) => {
const url = `${baseUrl}/collections/${collection.handle}`;

return {
url,
lastMod: collection.updatedAt,
changeFreq: "daily",
};
});

const pagesData = flattenConnection(data.pages)
.filter((page) => page.onlineStoreUrl)
.map((page) => {
const url = `${baseUrl}/pages/${page.handle}`;

return {
url,
lastMod: page.updatedAt,
changeFreq: "weekly",
};
});

const urlsDatas = [...productsData, ...collectionsData, ...pagesData];

return `
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
>
${urlsDatas.map((url) => renderUrlTag(url!)).join("")}
</urlset>`;
}

function renderUrlTag({
url,
lastMod,
changeFreq,
image,
}: {
url: string;
lastMod?: string;
changeFreq?: string;
image?: {
url: string;
title?: string;
caption?: string;
};
}) {
return `
<url>
<loc>${url}</loc>
<lastmod>${lastMod}</lastmod>
<changefreq>${changeFreq}</changefreq>
${
image
? `
<image:image>
<image:loc>${image.url}</image:loc>
<image:title>${image.title ?? ""}</image:title>
<image:caption>${image.caption ?? ""}</image:caption>
</image:image>`
: ""
}
</url>
`;
}

0 comments on commit 7a28e7b

Please sign in to comment.