import {Link, useLoaderData} from 'react-router'; import type {Route} from './+types/blogs.$blogHandle._index'; import {Image, getPaginationVariables} from '@shopify/hydrogen'; import type {ArticleItemFragment} from 'storefrontapi.generated'; import {PaginatedResourceSection} from '~/components/PaginatedResourceSection'; import {redirectIfHandleIsLocalized} from '~/lib/redirect'; export const meta: Route.MetaFunction = ({data}) => { return [{title: `Hydrogen | ${data?.blog.title ?? ''} blog`}]; }; export async function loader(args: Route.LoaderArgs) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** * Load data necessary for rendering content above the fold. This is the critical data * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. */ async function loadCriticalData({context, request, params}: Route.LoaderArgs) { const paginationVariables = getPaginationVariables(request, { pageBy: 4, }); if (!params.blogHandle) { throw new Response(`blog not found`, {status: 404}); } const [{blog}] = await Promise.all([ context.storefront.query(BLOGS_QUERY, { variables: { blogHandle: params.blogHandle, ...paginationVariables, }, }), // Add other queries here, so that they are loaded in parallel ]); if (!blog?.articles) { throw new Response('Not found', {status: 404}); } redirectIfHandleIsLocalized(request, {handle: params.blogHandle, data: blog}); return {blog}; } /** * Load data for rendering content below the fold. This data is deferred and will be * fetched after the initial page load. If it's unavailable, the page should still 200. * Make sure to not throw any errors here, as it will cause the page to 500. */ function loadDeferredData({context}: Route.LoaderArgs) { return {}; } export default function Blog() { const {blog} = useLoaderData(); const {articles} = blog; return (

{blog.title}

connection={articles}> {({node: article, index}) => ( )}
); } function ArticleItem({ article, loading, }: { article: ArticleItemFragment; loading?: HTMLImageElement['loading']; }) { const publishedAt = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }).format(new Date(article.publishedAt!)); return (
{article.image && (
{article.image.altText
)}

{article.title}

{publishedAt}
); } // NOTE: https://shopify.dev/docs/api/storefront/latest/objects/blog const BLOGS_QUERY = `#graphql query Blog( $language: LanguageCode $blogHandle: String! $first: Int $last: Int $startCursor: String $endCursor: String ) @inContext(language: $language) { blog(handle: $blogHandle) { title handle seo { title description } articles( first: $first, last: $last, before: $startCursor, after: $endCursor ) { nodes { ...ArticleItem } pageInfo { hasPreviousPage hasNextPage hasNextPage endCursor startCursor } } } } fragment ArticleItem on Article { author: authorV2 { name } contentHtml handle id image { id altText url width height } publishedAt title blog { handle } } ` as const;