<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cameron</title>
    <description>The latest articles on DEV Community by Cameron (@webby).</description>
    <link>https://dev.to/webby</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1093675%2Fb7256024-344e-42fd-88e0-aa4beec6e479.png</url>
      <title>DEV Community: Cameron</title>
      <link>https://dev.to/webby</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/webby"/>
    <language>en</language>
    <item>
      <title>Error handling in Get Static Props</title>
      <dc:creator>Cameron</dc:creator>
      <pubDate>Thu, 01 Aug 2024 10:51:25 +0000</pubDate>
      <link>https://dev.to/webby/error-handling-in-your-statically-generated-next-pages-site-1g05</link>
      <guid>https://dev.to/webby/error-handling-in-your-statically-generated-next-pages-site-1g05</guid>
      <description>&lt;p&gt;&lt;strong&gt;Am I in the right place&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a NextJS (pages) site&lt;/li&gt;
&lt;li&gt;using SSG with GetStaticProps (GSProps) with data from a CMS or similar source&lt;/li&gt;
&lt;li&gt;unsure how best to handle errors in your GSProps function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…then definitely keep reading.&lt;/p&gt;

&lt;p&gt;If people are interested in a similar article for SSG with the app directory please lmk.&lt;/p&gt;

&lt;p&gt;Let’s start with an example of what NOT to do (at least in 90% of cases):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cmsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCmsData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;cmsData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MY_REVALIDATE_TIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So what seems to be problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seems obvious when it is pointed out… it’s the “return notFound”.&lt;/p&gt;

&lt;p&gt;If any of your functions to fetch data or perform operations in the GSProps function throw, you’re going to have the following issues depending on when that error occurs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GSProps error occurs at build: Your build will succeed. Whichever paths threw, will return 404 when you try to visit them. Yikes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GSProps error occurs during a post-build revalidation (because you're using on-demand or time-driven revalidation): A page that previously generated successfully that throws on subsequent regeneration will change from a healthy page to a 404 page. Even bigger yikes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;fallback&lt;/code&gt; property defined against the corresponding getStaticPaths will determine whether this issue is confined to the already built pages (fallback = false), or if it extends also to pages that are statically generated after build time (fallback = blocking or true). &lt;/p&gt;

&lt;p&gt;Unless you’re checking whether the page still exists, most likely you don’t want to return the 404 page when there is an error caught in your GSProps function. &lt;/p&gt;

&lt;p&gt;So whats the fix? Throw! And to be polite to future codebase contributors who may not be privy to this information, make sure to leave a comment explaining why you’re throwing, and log to your monitoring solution of choice if you use one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cmsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCmsData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// If our data retrieval is successful, but no data is collected&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cmsData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cmsData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MY_REVALIDATE_TIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Do not try to recover from this error so that Next&lt;/span&gt;
    &lt;span class="c1"&gt;// can use the previously cached version of this page&lt;/span&gt;
    &lt;span class="c1"&gt;// or to flag this issue in build&lt;/span&gt;
    &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error fetching CMS data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach will now give you the following outcomes depending on your setup and the time of error:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GSProps error occurs at build: Your build will fail. The logs should explain why, allowing you to resolve the issue before it is shipped to production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GSProps error occurs during a post-build revalidation: GSProps will throw when the error happens, the page is not recreated which would replace the previously cached version of the static page. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put another way – You can throw and leave the error "uncaught" in your GSProps function to adopt Next's behaviour to use the last successfully generated version of the page if an error occurs during revalidation – a useful tool that can be used where many people would default to use the "notFound" approach.&lt;/p&gt;

&lt;p&gt;One thing you will notice here is that we’re still returning &lt;code&gt;notFound: true&lt;/code&gt; if our data is false-ish. This could be the case if your page data was deleted in a CMS or external data store causing the fetch to end successfully, but not return data. Because the page's data no longer exists, now we should return the 404. It's worth noting that this depends on your fetcher function – I've  assumed here that it ends successfully where the page no longer exists which it might not. Take care to perform proper analysis for each scenario whether you want either to return a 404, or throw to use the previously cached page.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ssg</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Crop it like it's hot: Cropping Images in Sanity v3</title>
      <dc:creator>Cameron</dc:creator>
      <pubDate>Fri, 02 Jun 2023 15:31:00 +0000</pubDate>
      <link>https://dev.to/webby/crop-it-like-its-hot-cropping-images-in-sanity-v3-2i1e</link>
      <guid>https://dev.to/webby/crop-it-like-its-hot-cropping-images-in-sanity-v3-2i1e</guid>
      <description>&lt;p&gt;If you're reading this, you probably couldn't find a solid guide on how to set up image cropping with Sanity either.&lt;/p&gt;

&lt;p&gt;I think this is partly because Sanity provides &lt;em&gt;a tonne&lt;/em&gt; of flexibility to manipulate your image using their CDN and image builder (more on this later). Which is both fantastic, and a touch overwhelming. &lt;/p&gt;

&lt;p&gt;In this guide, you'll learn the easiest and clearest way that I discovered on my path to set up Sanity cropping in my frontend application. This guide is not a comprehensive overview into everything you can do with Sanity's image manipulation tools. For that, I'd recommend the &lt;a href="https://www.sanity.io/docs/image-urls" rel="noopener noreferrer"&gt;Sanity documentation&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sanity project (v3) with your document queries prepared&lt;/li&gt;
&lt;li&gt;Frontend project with the following dependencies:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/sanity"&gt;@sanity&lt;/a&gt;/image-url&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/sanity"&gt;@sanity&lt;/a&gt;/asset-utils&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's jump in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating your Sanity schema
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  name: 'myImage',
  title: 'My Image',
  type: 'image',
  options: {
    hotspot: true
  },
  ...
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After nesting this options object into your image schema, you should see in your Studio instance the 'crop' aciton in the top right of the image form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2itdclfq38czuu2va8wg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2itdclfq38czuu2va8wg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating your frontend code
&lt;/h2&gt;

&lt;p&gt;We know that when you query your Sanity document(s), images are returned not with their source, but with a reference. And, when you add the hotspot in your schema as per section 1, it will also return the cropping/hotspot properties specified in Sanity studio. These are properties you can leverage in your frontend code however you like. Sanity does not have a direct pattern for how these should be used (albeit with good reason). That being said, we'll dive into one pattern you can use to easily apply cropping to images. Let's get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  The boilerplate
&lt;/h3&gt;

&lt;p&gt;I'll assume you've already set up your query(s) to retrieve the image(s) returned from Sanity. For more info on this I would recommend the &lt;a href="https://www.sanity.io/docs/how-queries-work" rel="noopener noreferrer"&gt;Sanity documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To generate image urls from an image reference, we will use Sanity's helper methods to build the src url:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import imageUrlBuilder from '@sanity/image-url'

const builder = imageUrlBuilder({
  dataset: process.env.MY_DATASET,
  projectId: process.env.MY_PROJECT_ID,
})

export const urlFor = (imageRef: string) =&amp;gt; builder.image(imageRef)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, the &lt;em&gt;urlFor&lt;/em&gt; function accepts the imageRef returned from the Sanity groq query (more on this later) and returns an "&lt;a href="https://www.sanity.io/docs/image-urls" rel="noopener noreferrer"&gt;ImageBuilder&lt;/a&gt;" object - an inbuilt Sanity type that lets you build the CDN src something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

urlFor(imageRef).width(1600).height(900).url()


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which generates a url like:&lt;br&gt;
&lt;code&gt;https://cdn.sanity.io/images/project_id/development/28e908-1680x1204.png?width=1600&amp;amp;height=900&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So how the crop do we use the crop/hotspot properties returned from Sanity in our image builder? Maybe I missed something, but this is where the Sanity documentation (as ace as they are), left me high and dry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cropping
&lt;/h3&gt;

&lt;p&gt;It's probably what you're here for, so here ya go:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { getImageDimensions } from '@sanity/asset-utils'
import { urlFor } from './imageUrlFor'

export const getCroppedImageSrc = (
  image: SanityImageQueryResult, // Details on this type in the appendix
) =&amp;gt; {

  const imageRef = image?.src?.asset?._ref
  const crop = image.src.crop

  // get the image's og dimensions
  const { width, height } = getImageDimensions(imageRef)

  if (Boolean(crop)) {
    // compute the cropped image's area
    const croppedWidth = Math.floor(width * (1 - (crop.right + crop.left)))

    const croppedHeight = Math.floor(height * (1 - (crop.top + crop.bottom)))

    // compute the cropped image's position
    const left = Math.floor(width * crop.left)
    const top = Math.floor(height * crop.top)

    // gather into a url
    return urlFor(imageRef)
            .rect(left, top, croppedWidth, croppedHeight)
            .url()
  }

  return urlFor(imageRef).url()
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For those interested, let's break it down.&lt;/p&gt;

&lt;p&gt;The width, height properties returned from getImageDimensions do &lt;em&gt;not&lt;/em&gt; return the cropped width and height, but the original image's width and height. &lt;/p&gt;

&lt;p&gt;This is why we use the crop settings to compute the cropped width and height as set in Sanity Studio. Crop settings return the cropped area's (percentage) distance from each edge. &lt;/p&gt;

&lt;p&gt;For example, {top: 0.2, bottom: 0.3, ...} means that the cropped area is 20% away from the top of the original area, and 30% away from the bottom of the original area; similar to absolute positioning in css - probably why Sanity chose this convention 🤔.&lt;/p&gt;

&lt;p&gt;This is why:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1 - (crop.right + crop.left)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can be thought of as &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1 - (percentage distance from horizontal edges)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can be thought as&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;percentage of image to retain/not crop&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Multiplying by this value the original width gives us the cropped width. The corollary is true for the cropping in the y/height axis.&lt;/p&gt;

&lt;p&gt;This gives us the size of the crop, but not the position. For that, we simply retrieve the cropped area's actual distance (not percentage distance) from the top and left of the original image's area.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    const left = Math.floor(width * crop.left)
    const top = Math.floor(height * crop.top)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Rembember that width and height here are the image's &lt;em&gt;original&lt;/em&gt; properties.&lt;/p&gt;

&lt;p&gt;Now we use the 'rect' method that accepts our computed values for the size and position:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

return urlFor(image)
  .rect(left, top, croppedWidth, croppedHeight)
  .url()


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which generates a url like:&lt;br&gt;
&lt;code&gt;https://cdn.sanity.io/images/project_id/development/28e903-1680x704.png?rect=10,40,1074,704&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For those curious, Math.floor is required because the Sanity CDN expects integer values. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voila&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To caveat, you still have a responsibility to correctly place  and position your image within your HTML or JSX. &lt;/p&gt;

&lt;p&gt;I hope you found this helpful! Please let me know if you are interested in image manipulation using the hotspot and the imageBuilder's focalpoint method. &lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix
&lt;/h3&gt;

&lt;p&gt;This is the shape of an image type returned from a Sanity query. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

export type SanityImageQueryResult = {
  src: {
    asset: { _ref: string }
    crop: {
      _type: 'sanity.imageCrop'
      bottom: number
      left: number
      right: number
      top: number
    }
    hotspot: {
      _type: 'sanity.imageHotspot'
      height: number
      width: number
      x: number
      y: number
    }
  }
  alt: string
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>sanity</category>
      <category>react</category>
      <category>webdev</category>
      <category>cms</category>
    </item>
  </channel>
</rss>
