<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts tagged #createnode</title><description>All posts tagged with createnode on queen.raae.codes</description><link>https://queen.raae.codes/tag/createnode/</link><item><title>🗂 🏴‍☠️ ~ Source data (avatars) into the Gatsby Data Layer from a Supabase table</title><link>https://queen.raae.codes/2022-08-26-source-supabase/</link><guid isPermaLink="true">https://queen.raae.codes/2022-08-26-source-supabase/</guid><description>On yesterday&apos;s unauthorized and rum-fueled treasure hunt in the sharky waters around the Gatsby islands, we refactored the little avatar section on Prune your…</description><pubDate>Fri, 26 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On yesterday&apos;s &lt;a href=&quot;https://youtu.be/EK74ACNxM7M&quot;&gt;unauthorized and rum-fueled treasure hunt&lt;/a&gt; in the sharky waters around the Gatsby islands, we refactored the little avatar section on &lt;a href=&quot;https://prune.raae.tech&quot;&gt;Prune your follows&lt;/a&gt; from client-side to server-side rendering!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/EK74ACNxM7M&quot;&gt;&lt;img src=&quot;./stream-screendump.jpg&quot; alt=&quot;Stream Screendump&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PR: If you did (or do) watch the show, you&apos;ll see that I have sneakily done some cleaning up and solved our little environment variable problem by making sure I load them in the gatsby-config 🤦‍♀️ 🤪&lt;/p&gt;
&lt;h2&gt;The What?&lt;/h2&gt;
&lt;p&gt;Source data from the avatars table on build, so that the little avatars are present as part of the statically generated version of the marketing page.&lt;/p&gt;
&lt;h2&gt;The Why?&lt;/h2&gt;
&lt;p&gt;It gets rid of the tiny flicker of no content, and why call Supabase every time someone lands on the marketing page for &lt;a href=&quot;https://prune.raae.tech&quot;&gt;Prune your follows&lt;/a&gt; when there is no need? You can also use your service key when sourcing, allowing you to even information from otherwise locked down table rows.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://prune.raae.tech&quot;&gt;&lt;img src=&quot;./prune-avatars.jpg&quot; alt=&quot;Screendump of avatars on Prune your follows&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The How&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Fetch data on avatars using the Supabase SKD this time&lt;/li&gt;
&lt;li&gt;Create a Gatsby node for each avatar using &lt;code&gt;createNode&lt;/code&gt;, &lt;code&gt;createNodeId&lt;/code&gt; and &lt;code&gt;createContentDigest&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Code&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// File: gatsby-node.js
// Note: Error handling omitted

const { createClient } = require(&amp;quot;@supabase/supabase-js&amp;quot;);

const supabaseUrl = process.env.GATSBY_PUBLIC_SUPABASE_URL;
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY;

const serviceSupabase = createClient(supabaseUrl, supabaseServiceKey);

exports.sourceNodes = async (gatsbyUtils) =&amp;gt; {
  const { actions, createNodeId, createContentDigest, reporter } = gatsbyUtils;
  const { createNode } = actions;

  const { data } = await serviceSupabase
    .from(&amp;quot;avatars&amp;quot;)
    .select(&amp;quot;username, avatar_url&amp;quot;)
    .limit(20);

  data.forEach((item) =&amp;gt; {
    createNode({
      id: createNodeId(item.username),
      avatarUrl: item.avatar_url,
      username: item.username,
      internal: {
        type: &amp;quot;UserAvatar&amp;quot;,
        contentDigest: createContentDigest(item),
      },
    });
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voila, we have avatar nodes in the data layer we may use in the &lt;code&gt;Avatars&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// File: Avatars.js
// Note: Styling omitted

import React from &amp;quot;react&amp;quot;;
import { useStaticQuery, graphql } from &amp;quot;gatsby&amp;quot;;

export function Avatars() {
  const data = useStaticQuery(graphql`
    query {
      allUserAvatar {
        nodes {
          username
          avatarUrl
        }
      }
    }
  `);

  return (
    &amp;lt;div&amp;gt;
      {data.allUserAvatar.nodes.map((user) =&amp;gt; {
        return (
          &amp;lt;img key={user.username} src={user.avatarUrl} alt={user.username} /&amp;gt;
        );
      })}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see the entire app code, check out its &lt;a href=&quot;https://github.com/queen-raae/prune-your-follows&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
&lt;br/&gt;&lt;ul&gt;&lt;li&gt;Contrary to popular belief, Queen Raae and family have never worked for Gatsby.&lt;/li&gt;&lt;/ul&gt;</content:encoded></item><item><title>📥 🏜 ~ createNode does NOT return anything useful</title><link>https://queen.raae.codes/2022-06-21-create-node/</link><guid isPermaLink="true">https://queen.raae.codes/2022-06-21-create-node/</guid><description>createNode does not return anything useful: js // gatsby-node.js // Should be sourced or derived, only for example purposes const DATA = { id: &quot;unique&quot;, hello:…</description><pubDate>Tue, 21 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;createNode&lt;/code&gt; does not return anything useful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js

// Should be sourced or derived, only for example purposes
const DATA = { id: &amp;quot;unique&amp;quot;, hello: &amp;quot;world&amp;quot; };

exports.sourceNodes = async (gatsbyUtils) =&amp;gt; {
  const { actions, createNodeId, createContentDigest } = gatsbyUtils;
  const { createNode } = actions;

  const awaitedNode = await createNode({
    ...DATA,
    id: createNodeId(`example-node-${DATA.id}`),
    internal: {
      type: `BadExample`,
      contentDigest: createContentDigest(DATA),
    },
  });
  console.log(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt; awaitedNode:&amp;quot;, awaitedNode); // 👈
  // Console:
  // &amp;gt;&amp;gt;&amp;gt; awaitedNode: []
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore to create a child-parent link in &lt;code&gt;onCreateNode&lt;/code&gt; for instance, you need to keep a reference to the id yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js

// Should be sourced or derived, only for example purposes
const DATA = { id: &amp;quot;unique&amp;quot;, hello: &amp;quot;world&amp;quot; };

exports.onCreateNode = (gatsbyUtils) =&amp;gt; {
  const { node, actions, createNodeId, createContentDigest } = gatsbyUtils;
  const { createNode, createParentChildLink } = actions;

  if (node.internal.type !== &amp;quot;Parent&amp;quot;) return;

  const nodeId = createNodeId(`example-node-${DATA.id}`); // 👈

  createNode({
    ...DATA,
    id: nodeId, // 👈
    internal: {
      type: `Child`,
      contentDigest: createContentDigest(DATA),
    },
  });

  // 👇👇👇
  createParentChildLink({ parent: node, child: { id: nodeId } });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more common version of the code above that you see in examples are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js

// Should be sourced or derived, only for example purposes
const DATA = { id: &amp;quot;unique&amp;quot;, hello: &amp;quot;world&amp;quot; };

exports.onCreateNode = (gatsbyUtils) =&amp;gt; {
  const { node, actions, createNodeId, createContentDigest } = gatsbyUtils;
  const { createNode, createParentChildLink } = actions;

  if (node.internal.type !== &amp;quot;Parent&amp;quot;) return;

  const childNode = {
    ...DATA,
    id: createNodeId(`example-node-${DATA.id}`),
    internal: {
      type: `Child`,
      contentDigest: createContentDigest(DATA),
    },
  };

  // 👇👇👇
  createNode(childNode);
  createParentChildLink({ parent: node, child: childNode });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I&apos;m not too fond of indicating that the data object describing the node is already an &lt;em&gt;actual&lt;/em&gt; node in the data layer, so I prefer my initial solution as only the id is needed to create the link.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
</content:encoded></item><item><title>🗂 🐝 ~ Source content from anywhere with ScrapingBee</title><link>https://queen.raae.codes/2022-05-06-scrapingbee/</link><guid isPermaLink="true">https://queen.raae.codes/2022-05-06-scrapingbee/</guid><description>Pierre de Wulf, co-founder of ScrapingBee joined us on yesterday&apos;s unauthorized and rum-fueled treasure hunt in the sharky waters around the Gatsby islands.…</description><pubDate>Fri, 06 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://twitter.com/PierreDeWulf&quot;&gt;Pierre de Wulf&lt;/a&gt;, co-founder of &lt;a href=&quot;https://www.scrapingbee.com/&quot;&gt;ScrapingBee&lt;/a&gt; joined us on yesterday&apos;s &lt;a href=&quot;https://youtu.be/MjcYzjYIFuI&quot;&gt;unauthorized and rum-fueled treasure hunt&lt;/a&gt; in the sharky waters around the Gatsby islands.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/MjcYzjYIFuI&quot;&gt;&lt;img src=&quot;./youtube-screengrab.jpg&quot; alt=&quot;YouTube Screengrab&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The What?&lt;/h2&gt;
&lt;p&gt;Source Crowdcast webinars into the Gatsby Data Layer using ScrapingBee — an API that simplifies web scraping.&lt;/p&gt;
&lt;h2&gt;The Why?&lt;/h2&gt;
&lt;p&gt;There is no official Crowdcast API and keeping the data in sync using copy/past is a pain (or at least boring 🤪). To scrape from Crowdcast, we need to load the page in a headless browser such as Puppeteer. Doing so is not possible as part of the Gatsby build process, so we outsource it to ScrapingBee.&lt;/p&gt;
&lt;h2&gt;The How&lt;/h2&gt;
&lt;p&gt;We used the &lt;a href=&quot;https://www.scrapingbee.com/features/data-extraction/&quot;&gt;Data Extraction&lt;/a&gt;-feature from ScrapingBee. It lets us select data on a page using CSS-selector. It felt kinda similar to &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;cheerio&lt;/a&gt; if you have ever used that.&lt;/p&gt;
&lt;p&gt;As always, I started with copy/pasting the &lt;a href=&quot;https://www.scrapingbee.com/documentation/data-extraction/&quot;&gt;example snippets&lt;/a&gt;. It worked almost out of the box, but we had to make use of the &lt;code&gt;wait_for&lt;/code&gt; option as the Crowdcast page takes a while to load:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&apos;s sometimes necessary to wait for a particular element to appear in the DOM before ScrapingBee returns the HTML content.
&amp;lt;cite&amp;gt;&lt;a href=&quot;https://www.scrapingbee.com/documentation/#wait_for&quot;&gt;ScrapingBee Docs&lt;/a&gt;&amp;lt;/cite&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The Code&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const axios = require(&amp;quot;axios&amp;quot;);

const scrapeCrowdcast = async () =&amp;gt; {
  const { data } = await axios.get(&amp;quot;https://app.scrapingbee.com/api/v1&amp;quot;, {
    params: {
      api_key: process.env.SCRAPING_BEE_API_KEY,
      url: &amp;quot;https://www.crowdcast.io/raae&amp;quot;,
      // Wait for there to be at least one
      // non-empty .event-tile element
      wait_for: &amp;quot;.event-tile&amp;quot;,
      extract_rules: {
        webinars: {
          // Lets create a list with data
          // extracted from the .event-tile element
          selector: &amp;quot;.event-tile&amp;quot;,
          type: &amp;quot;list&amp;quot;,
          // Each object in the list should
          output: {
            // have a title lifted from
            // the .event-tile__title element
            title: &amp;quot;.event-tile__title&amp;quot;,
            // and a path lifted from
            // the href attribute of the first link element
            path: {
              selector: &amp;quot;a&amp;quot;,
              output: &amp;quot;@href&amp;quot;,
            },
          },
        },
      },
    },
  });

  return data;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting data object:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
  webinars: [
    {
      title: &amp;quot;5 Gatsby Gotchas to look out for as a React developer&amp;quot;,
      path: &amp;quot;/e/gatsby-gotchas-react?utm_source=profile&amp;amp;utm_medium=profile_web&amp;amp;utm_campaign=profile&amp;quot;,
    },
    {
      title: &amp;quot;Testing your Gatsby Serverless Functions&amp;quot;,
      path: &amp;quot;/e/testing-your-functions?utm_source=profile&amp;amp;utm_medium=profile_web&amp;amp;utm_campaign=profile&amp;quot;,
    },
    // and more
  ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then loop through the webinars on the data object creating content nodes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js
exports.sourceNodes = async (gatsbyUtils) =&amp;gt; {
  const { actions, createNodeId, createContentDigest } = gatsbyUtils;
  const { createNode } = actions;

  const data = await scrapeCrowdcast();

  for (const webinar of data.webinars) {
    createNode({
      id: createNodeId(webinar.path),
      title: webinar.title,
      url: &amp;quot;https://www.crowdcast.io&amp;quot; + webinar.path,
      rawScrape: webinar,
      internal: {
        type: `CrowdcastWebinar`,
        mediaType: `text/json`,
        content: JSON.stringify(webinar),
        contentDigest: createContentDigest(webinar),
      },
    });
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voila, we have webinar nodes in our data layer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-graphql&quot;&gt;query MyQuery {
  allCrowdcastWebinar {
    nodes {
      title
      url
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see the entire demo, check out its &lt;a href=&quot;https://github.com/queen-raae/gatsby-demo-web-scraping/blob/main/gatsby-node.js&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
Should we make this a full-featured plugin? Extracting the cover art and descriptions from the individual webinar pages? Let me know!&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
&lt;strong&gt;PS:&lt;/strong&gt; ScrapingBee is a paid service, but we are as always neither sponsored nor an affiliate.&lt;br&gt;
&lt;strong&gt;PPS:&lt;/strong&gt; If you want to learn more about web-scraping without ScrapingBee check out their article &lt;a href=&quot;https://www.scrapingbee.com/blog/web-scraping-javascript/&quot;&gt;Web Scraping with Javascript and NodeJS&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>🏷 ✨ ~ How to create unique Gatsby node ids</title><link>https://queen.raae.codes/2022-05-04-create-node-id/</link><guid isPermaLink="true">https://queen.raae.codes/2022-05-04-create-node-id/</guid><description>All Gatsby nodes need a unique id. Luckily for you, a Gatsby Source Plugin developer, you are not responsible for creating unique ids. You are only responsible…</description><pubDate>Wed, 04 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;All Gatsby nodes need a unique id. Luckily for you, a Gatsby Source Plugin developer, you are not responsible for creating unique ids. You are only responsible for supplying a unique-within-your-universe-id to &lt;code&gt;createNodeId&lt;/code&gt;. Gatsby is responsible for returning a unique node id.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;exports.sourceNodes = async ({ actions, createNodeId }) =&amp;gt; {
  const { createNode } = actions;

  createNode({
    id: createNodeId(&amp;quot;unique-within-your-universe-id&amp;quot;),

    // More fields
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;a href=&quot;https://github.com/queen-raae/gatsby-source-youtube-oembed/blob/812494ccc6d1daf74bf9de4e04ee9aa87e887f90/plugin/gatsby-node.js#L57&quot;&gt;Gatsby Source YouTube oEmbed&lt;/a&gt; we use the YouTube id. It is unique across the YouTube universe.&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
&lt;strong&gt;PS:&lt;/strong&gt; &lt;code&gt;createNodeId&lt;/code&gt; &lt;a href=&quot;/2022-03-30-deterministic/&quot;&gt;is deterministic&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>📄 🏷 ~ No need to create the content digest yourself</title><link>https://queen.raae.codes/2022-05-03-content-digest/</link><guid isPermaLink="true">https://queen.raae.codes/2022-05-03-content-digest/</guid><description>We see a lot of this type of code when looking at source plugins: js exports.sourceNodes = async ({ actions }) = { const { createNode } = actions; const data =…</description><pubDate>Tue, 03 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We see a lot of this type of code when looking at source plugins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;exports.sourceNodes = async ({ actions }) =&amp;gt; {
  const { createNode } = actions;
  const data = { hello: &amp;quot;world&amp;quot; };

  createNode({
    // More fields
    internal: {
      // More fields
      contentDigest: crypto
        .createHash(`md5`)
        .update(JSON.stringify(data))
        .digest(`hex`),
    },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is not technically wrong, but I prefer using &lt;code&gt;createContentDigest&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;exports.sourceNodes = async ({ actions, createContentDigest }) =&amp;gt; {
  const { createNode } = actions;
  const data = { hello: &amp;quot;world&amp;quot; };

  createNode({
    // More fields
    internal: {
      // More fields
      contentDigest: createContentDigest(data), // 👈👈👈
    },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;internal.contentDigest&lt;/code&gt; is mandatory and thus part of a &lt;a href=&quot;/2022-01-20-minimal-viable-content-node/&quot;&gt;Minimal Viable Content Node&lt;/a&gt;. If this value has not changed, Gatsby uses the cached node instead of creating a new node, and for instance, &lt;code&gt;onCreateNode&lt;/code&gt; is not called.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;contentDigest:&lt;/strong&gt; the digest for the content of this node. Helps Gatsby avoid doing extra work on data that hasn’t changed.
&amp;lt;cite&amp;gt;&lt;a href=&quot;https://www.gatsbyjs.com/docs/reference/config-files/actions/#createNode&quot;&gt;Gatsby Docs&lt;/a&gt;&amp;lt;/cite&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
</content:encoded></item><item><title>📥 👩‍👧 ~ Always define a parent when creating nodes in onCreateNode</title><link>https://queen.raae.codes/2022-04-01-parent-id/</link><guid isPermaLink="true">https://queen.raae.codes/2022-04-01-parent-id/</guid><description>TLDR: When using the action createNode inside onCreateNode it is super duper important to give the node a parent! On yesterday&apos;s unauthorized and rum-fueled…</description><pubDate>Fri, 01 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; When using the action &lt;code&gt;createNode&lt;/code&gt; inside &lt;code&gt;onCreateNode&lt;/code&gt; it is super duper important to give the node a parent!&lt;/p&gt;
&lt;p&gt;On yesterday&apos;s unauthorized and rum-fueled &lt;a href=&quot;https://youtu.be/SWumzHLEpYA&quot;&gt;treasure hunt&lt;/a&gt; in the sharky waters around the Gatsby islands, we worked through the code from the last two emails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2022-03-30-deterministic/&quot;&gt;Gatsby&apos;s createNodeId is deterministic 📥 💡&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/2022-03-31-skip-cache/&quot;&gt;Should we skip the Gatsby Cache altogether? 🛑 🔄&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://youtu.be/SWumzHLEpYA&quot;&gt;&lt;img src=&quot;./youtube-screengrab.jpg&quot; alt=&quot;Screengrab of stream&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But we also briefly touched on a bug I had to fix from last week&apos;s ImageCDN stream. The YouTubeThumbnail nodes were &amp;quot;lost&amp;quot; between builds... 😱&lt;/p&gt;
&lt;p&gt;Gatsby caches nodes between builds. If you call &lt;code&gt;createNode&lt;/code&gt; with the same nodeId and content digest as a cached node, Gatsby will not go through the node creation process again. It will use the cached node, skipping &lt;code&gt;onCreateNode&lt;/code&gt; for that node.&lt;/p&gt;
&lt;p&gt;Since caches can get out of hand, Gatsby throws out all nodes that are not &amp;quot;touched&amp;quot; during the node sourcing phase. And this is where our problem arises.&lt;/p&gt;
&lt;p&gt;A node is considered touched either by the use of &lt;code&gt;createNode&lt;/code&gt; or &lt;code&gt;touchNode&lt;/code&gt;. However, the code &amp;quot;touching&amp;quot; our YouTubeThumbnail nodes is skipped when its YouTube node is cached.&lt;/p&gt;
&lt;p&gt;Luckily a node is also considered &amp;quot;touched&amp;quot; if its parent is &amp;quot;touched&amp;quot;: problem solved 👩‍👧&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;createNode({
  id: youTubeThumbnailNodeId,
  parent: node.id, // 👈 👈 👈
  youTubeId: node.youTubeId,
  url: node.oEmbed.thumbnail_url,
  mimeType: &amp;quot;image/jpeg&amp;quot;,
  filename: node.youTubeId + &amp;quot;.jpg&amp;quot;,
  height: node.oEmbed.thumbnail_height,
  width: node.oEmbed.thumbnail_width,
  internal: {
    type: YOUTUBE_THUMBNAIL_TYPE,
    contentDigest: node.internal.contentDigest,
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
</content:encoded></item><item><title>🔴 🏴‍☠️ ~ Source YouTube information using oEmbed, no YouTube API Key needed</title><link>https://queen.raae.codes/2022-01-21-yt-oembed/</link><guid isPermaLink="true">https://queen.raae.codes/2022-01-21-yt-oembed/</guid><description>In yesterday&apos;s unauthorized and rum-fueled treasure hunts in the sharky waters around the Gatsby islands, we looked closely at sourcing content nodes with data…</description><pubDate>Fri, 21 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In yesterday&apos;s unauthorized and rum-fueled treasure hunts in the sharky waters around the Gatsby islands, we looked closely at &lt;a href=&quot;https://youtu.be/VhrOe0X_oA8&quot;&gt;sourcing content nodes&lt;/a&gt; with data from the YouTube oEmbed endpoint.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://oembed.com/&quot;&gt;oEmbed&lt;/a&gt; is likely one of the most underappreciated concepts of the open web. I first encountered it when WordPress added support back in my uni days; let&apos;s just say it been awhile 👵&lt;/p&gt;
&lt;p&gt;A simple yet powerful concept that I think would benefit from more awareness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have you heard about or used oEmbed before?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;oEmbed Examples&lt;/h2&gt;
&lt;p&gt;A provider: YouTube, SoundCloud, Issuu, Flickr, or your blog provides an oEmbed endpoint that accepts at a minimum an URL and returns embeddable content in return.&lt;/p&gt;
&lt;p&gt;A GET request to &lt;code&gt;https://www.flickr.com/services/oembed/?url=http://www.flickr.com/photos/bees/2341623661/&lt;/code&gt; returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;photo&amp;quot;,
  &amp;quot;width&amp;quot;: 240,
  &amp;quot;height&amp;quot;: 160,
  &amp;quot;title&amp;quot;: &amp;quot;ZB8T0193&amp;quot;,
  &amp;quot;url&amp;quot;: &amp;quot;http://farm4.static.flickr.com/3123/2341623661_7c99f48bbf_m.jpg&amp;quot;,
  &amp;quot;author_name&amp;quot;: &amp;quot;Bees&amp;quot;,
  &amp;quot;author_url&amp;quot;: &amp;quot;http://www.flickr.com/photos/bees/&amp;quot;,
  &amp;quot;provider_name&amp;quot;: &amp;quot;Flickr&amp;quot;,
  &amp;quot;provider_url&amp;quot;: &amp;quot;http://www.flickr.com/&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or more interestingly in our case, a GET Request to &lt;code&gt;https://www.youtube.com/oembed/?url=https://youtu.be/Bk1jonYPFD4&lt;/code&gt; returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;title&amp;quot;: &amp;quot;Celebrating POW! (usepow.app)  \u00b7  A day in my life as a developer, founder and mom  \u00b7  March 2021&amp;quot;,
  &amp;quot;author_name&amp;quot;: &amp;quot;Queen Raae&amp;quot;,
  &amp;quot;author_url&amp;quot;: &amp;quot;https://www.youtube.com/c/QueenRaae&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;video&amp;quot;,
  &amp;quot;height&amp;quot;: 113,
  &amp;quot;width&amp;quot;: 200,
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,
  &amp;quot;provider_name&amp;quot;: &amp;quot;YouTube&amp;quot;,
  &amp;quot;provider_url&amp;quot;: &amp;quot;https://www.youtube.com/&amp;quot;,
  &amp;quot;thumbnail_height&amp;quot;: 360,
  &amp;quot;thumbnail_width&amp;quot;: 480,
  &amp;quot;thumbnail_url&amp;quot;: &amp;quot;https://i.ytimg.com/vi/Bk1jonYPFD4/hqdefault.jpg&amp;quot;,
  &amp;quot;html&amp;quot;: &amp;quot;\u003ciframe width=\u0022200\u0022 height=\u0022113\u0022 src=\u0022https://www.youtube.com/embed/Bk1jonYPFD4?feature=oembed\u0022 frameborder=\u00220\u0022 allow=\u0022accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\u0022 allowfullscreen\u003e\u003c/iframe\u003e&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take note of &lt;code&gt;html&lt;/code&gt; on the YouTube response; it&apos;s all you need to embed a YouTube player to your site.&lt;/p&gt;
&lt;h2&gt;Sourcing nodes with oEmbed data&lt;/h2&gt;
&lt;p&gt;In Gatsby land, we can use oEmbed to ensure we have the correct embed snippet and/or the current title for our YouTube videos.&lt;/p&gt;
&lt;p&gt;We do so by using the &lt;code&gt;gatsby-node.js&lt;/code&gt; hook &lt;code&gt;sourceNodes&lt;/code&gt; where we create one content node per YouTube video and add the information from the oEmbed endpoint onto the node.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js
// Full code without error handling

const axios = require(&amp;quot;axios&amp;quot;);

const YOUTUBE_IDS = [&amp;quot;Bk1jonYPFD4&amp;quot;, &amp;quot;TzJfepDjpzM&amp;quot;];

const fetchEmbed = async (id) =&amp;gt; {
  const ytUrl = `https://youtu.be/${id}`;
  const { data } = await axios.get(&amp;quot;https://www.youtube.com/oembed&amp;quot;, {
    params: {
      url: ytUrl,
    },
  });
  return data;
};

const prepYouTubeNode = async (
  id,
  { actions: { createNode }, createNodeId, createContentDigest }
) =&amp;gt; {
  const embedData = await fetchEmbed(id);

  createNode({
    id: createNodeId(`you-tube-${id}`),
    oEmbed: embedData,
    internal: {
      type: `YouTube`,
      contentDigest: createContentDigest(embedData),
    },
  });
};

exports.sourceNodes = async (params) =&amp;gt; {
  await Promise.all(YOUTUBE_IDS.map((id) =&amp;gt; prepYouTubeNode(id, params)));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Would you like to see this code refactored into a plugin?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
&lt;strong&gt;PS:&lt;/strong&gt; The Pirate Princess and I woke up in this &lt;a href=&quot;https://twitter.com/OlaHolstVea/status/1483819055584878593&quot;&gt;magical tree top hut&lt;/a&gt; today...&lt;/p&gt;
</content:encoded></item><item><title>🥗 🌮 ~ Minimal Viable Content Node</title><link>https://queen.raae.codes/2022-01-20-minimal-viable-content-node/</link><guid isPermaLink="true">https://queen.raae.codes/2022-01-20-minimal-viable-content-node/</guid><description>Are you the type who preps all your ingredients in advance of cooking? No? Sourcing content into the Gatsby Data Layer as content nodes is a little like…</description><pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Are you the type who preps all your ingredients in advance of cooking? No?&lt;/p&gt;
&lt;p&gt;Sourcing content into the Gatsby Data Layer as content nodes is a little like shopping for ingredients! Chopping up and prepping those ingredients is similar to transforming those sourced content nodes to be ready to go.&lt;/p&gt;
&lt;p&gt;If you are the prep type, you might have taken quickly to the Gatsby Data Layer; if you are not, maybe you feel it&apos;s an unnecessary step keeping you from making it up while you go and moving quickly?&lt;/p&gt;
&lt;p&gt;Either way, if you are going to get the most out of Gatsby, you should probably become intimately familiar with it.&lt;/p&gt;
&lt;p&gt;On that note, Ola and I had an interesting conversation last week:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How minimal can a content node be?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It turns out it can be very minimal!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// gatsby-node.js

exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) =&amp;gt; {
  const { createNode } = actions;

  const ID = `unique`;

  createNode({
    id: createNodeId(`minimal-content-node-${ID}`),
    internal: {
      type: `Minimal`,
      contentDigest: createContentDigest(ID),
    },
  });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A content node of type &lt;code&gt;Minimal&lt;/code&gt; is now available in the Gatsby Data Layer and can be queried for like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;query MinimalQuery {
  minimal {
    internal {
      type
    }
    id
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resulting in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;data&amp;quot;: {
    &amp;quot;minimal&amp;quot;: {
      &amp;quot;internal&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;Minimal&amp;quot;
      },
      &amp;quot;id&amp;quot;: &amp;quot;40c03aa4-b334-5ec8-a148-8f42e7bafbb4&amp;quot;
    }
  },
  &amp;quot;extensions&amp;quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously not usable for anything, but in tonight&apos;s &lt;a href=&quot;https://www.youtube.com/watch?v=VhrOe0X_oA8&quot;&gt;unauthorized and rum-fueled treasure hunt&lt;/a&gt; Deep Dive Edition, we&apos;ll build from here and create &lt;code&gt;YouTube&lt;/code&gt; content nodes containing the embed code and thumbnail fetched from YouTube.&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
Have you ever created your own content nodes?&lt;br&gt;
Let me know!&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
All the best,&lt;br&gt;
Queen Raae&lt;/p&gt;
&lt;p&gt; &lt;br&gt;
&lt;strong&gt;PS:&lt;/strong&gt; You might feel you have seen this &lt;a href=&quot;https://youtu.be/tJHV96jVlKg&quot;&gt;particualar Deep Dive before&lt;/a&gt;, but this time we&apos;ll move slower, and we&apos;ll not need a YouTube API Key 🤯&lt;/p&gt;
</content:encoded></item></channel></rss>