<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Aidan Kinzett</title><description>My personal website.</description><link>https://aidankinzett.com/</link><item><title>How to use `astro-og-canvas` with Astro</title><link>https://aidankinzett.com/blog/astro-open-graph-image/</link><guid isPermaLink="true">https://aidankinzett.com/blog/astro-open-graph-image/</guid><description>Use `astro-og-canvas` to automatically generate Open Graph images for your blog posts.</description><pubDate>Tue, 07 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;What is an Open Graph Image?&lt;/h1&gt;
&lt;p&gt;Open Graph images are the images that are displayed when you share a link to your blog post on social media. Open Graph is a standard that lets you provide details about your website through &lt;code&gt;meta&lt;/code&gt; tags in your HTML. You can read more about the Open Graph Protocol &lt;a href=&quot;https://ogp.me/&quot;&gt;here&lt;/a&gt;. The main thing to know is that you set the image by adding a &lt;code&gt;meta&lt;/code&gt; tag to the &lt;code&gt;head&lt;/code&gt; of your page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta property=&quot;og:image&quot; content=&quot;https://picsum.photos/200&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you inspect the source code of this page, you will see that the &lt;code&gt;og:image&lt;/code&gt; tag is set to &lt;a href=&quot;https://aidankinzett.com/og/blog/astro-open-graph-image.png&quot;&gt;https://aidankinzett.com/og/blog/astro-open-graph-image.png&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;How to Generate Open Graph Images&lt;/h1&gt;
&lt;p&gt;We will be using the &lt;a href=&quot;https://www.npmjs.com/package/astro-og-canvas&quot;&gt;astro-og-canvas&lt;/a&gt; package to generate our images.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, install the package:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;npm install astro-og-canvas
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new file in your &lt;code&gt;src/pages&lt;/code&gt; directory. For example, &lt;code&gt;src/pages/og/[...route].ts&lt;/code&gt;. This will be the route that generates the Open Graph images.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following code to the file:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/og/[...route].ts
import { OGImageRoute } from &quot;astro-og-canvas&quot;;

const directory = &quot;src/content&quot;;

// Import all pages from the content directory
const rawPages = import.meta.glob(`${directory}/**/*.md`, { eager: true });

// Remove the /src/content prefix from the paths
const pages = Object.entries(rawPages).reduce(
  (acc, [path, page]) =&amp;gt; ({ ...acc, [path.replace(directory, &quot;&quot;)]: page }),
  {}
);

export const { getStaticPaths, get } = OGImageRoute({
  // Set the name of the dynamic route segment here it’s `route`,
  // because the file is named `[...route].ts`.
  param: &quot;route&quot;,

  // Provide our pages object here
  pages,

  // For each page, this callback will be used to
  // customize the OpenGraph image.
  getImageOptions: (path, page) =&amp;gt; ({
    title: page.frontmatter.title,
    description: page.frontmatter.description,
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set &lt;code&gt;directory&lt;/code&gt; to the path of your content directory. In this example, we are using &lt;code&gt;src/content&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are more options you can provide in the &lt;code&gt;getImageOptions&lt;/code&gt; function to customize the Open Graph image. You can read more about them in the &lt;a href=&quot;https://github.com/delucis/astro-og-canvas/tree/latest/packages/astro-og-canvas#image-options&quot;&gt;astro-og-canvas documentation&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a helper function to add the Open Graph image to the data of each page:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// src/lib/getBlogCollection.ts
import { getCollection } from &quot;astro:content&quot;;
import { SITE_URL } from &quot;../consts&quot;;

const collection = &quot;blog&quot;;

export default async () =&amp;gt; {
  const posts = await getCollection(collection);

  return posts.map((post) =&amp;gt; ({
    ...post,
    data: {
      ...post.data,
      ogImage: `${SITE_URL}/og/${collection}/${post.slug}.png`,
    },
  }));
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Call the helper function to get the blog posts in your &lt;code&gt;src/pages/blog/[...slug].ts&lt;/code&gt; file instead of using &lt;code&gt;getCollection&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// src/pages/blog/[...slug].ts
...
export async function getStaticPaths() {
  const posts = await getBlogCollection();
  return posts.map((post) =&amp;gt; ({
    params: { slug: post.slug },
    props: post,
  }));
}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Add the Open Graph image to the &lt;code&gt;BaseHead.astro&lt;/code&gt; component:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// src/components/BaseHead.astro
---
// Import the global.css file here so that it is included on
// all pages through the use of the &amp;lt;BaseHead /&amp;gt; component.
import &quot;../styles/global.css&quot;;

export interface Props {
  title: string;
  description: string;
  ogImage?: string;
}

const canonicalURL = new URL(Astro.url.pathname, Astro.site);

const { title, description, ogImage = &quot;/default-og.png&quot; } = Astro.props;
---

&amp;lt;!-- Global Metadata --&amp;gt;
&amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot; /&amp;gt;
&amp;lt;meta name=&quot;generator&quot; content={Astro.generator} /&amp;gt;

&amp;lt;!-- Canonical URL --&amp;gt;
&amp;lt;link rel=&quot;canonical&quot; href={canonicalURL} /&amp;gt;

&amp;lt;!-- Primary Meta Tags --&amp;gt;
&amp;lt;title&amp;gt;{title}&amp;lt;/title&amp;gt;
&amp;lt;meta name=&quot;title&quot; content={title} /&amp;gt;
&amp;lt;meta name=&quot;description&quot; content={description} /&amp;gt;

&amp;lt;!-- Open Graph Tags --&amp;gt;
&amp;lt;meta property=&quot;og:title&quot; content={title} /&amp;gt;
&amp;lt;meta property=&quot;og:image&quot; content={ogImage} /&amp;gt;
&amp;lt;meta property=&quot;og:type&quot; content=&quot;article&quot; /&amp;gt;
&amp;lt;meta property=&quot;og:url&quot; content={canonicalURL} /&amp;gt;
&amp;lt;meta property=&quot;og:description&quot; content={description} /&amp;gt;
&amp;lt;meta property=&quot;og:locale&quot; content=&quot;en_GB&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you share a link to your blog post on social media, it will display the Open Graph image that you generated. You can use &lt;a href=&quot;https://www.opengraph.xyz/&quot;&gt;https://www.opengraph.xyz/&lt;/a&gt; to test the Open Graph tags on your page.&lt;/p&gt;
&lt;p&gt;The source code for this post is available in my blog’s &lt;a href=&quot;https://github.com/aidankinzett/astro-blog&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Hello World</title><link>https://aidankinzett.com/blog/hello-world/</link><guid isPermaLink="true">https://aidankinzett.com/blog/hello-world/</guid><description>Hi, this is my first post...</description><pubDate>Sun, 05 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Hello&lt;/h1&gt;
&lt;p&gt;Hi, this is my first post.&lt;/p&gt;
&lt;p&gt;For an introduction see my &lt;a href=&quot;/about&quot;&gt;About page&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Upload a File with Supabase</title><link>https://aidankinzett.com/blog/upload-file-using-supabase-in-node/</link><guid isPermaLink="true">https://aidankinzett.com/blog/upload-file-using-supabase-in-node/</guid><description>How to upload a file using Supabase Storage in Node.js</description><pubDate>Sat, 24 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Supabase, the Firebase alternative, not only simplifies database management but also offers a robust solution for file storage. Supabase&apos;s SDK streamlines the process, ensuring your data is secure and easily accessible. This blog post will walk you through the steps of setting up the SDK and uploading files.&lt;/p&gt;
&lt;p&gt;To get started setup a free project at &lt;a href=&quot;https://supabase.com&quot;&gt;supabase.com&lt;/a&gt;. Then, create a storage bucket.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;Install using your preferred package manager. We will also be using the &lt;code&gt;nanoid&lt;/code&gt; package to give the file a unique identifier.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn install @supabase/supabase-js nanoid
pnpm install @supabase/supabase-js nanoid
npm install @supabase/supabase-js nanoid
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Create a Client&lt;/h2&gt;
&lt;p&gt;Copy the &lt;code&gt;SUPABASE_URL&lt;/code&gt; and &lt;code&gt;SUPABASE_PRIVATE_KEY&lt;/code&gt; from the Supabase dashboard.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;SUPABASE_PRIVATE_KEY&lt;/code&gt;, labelled as &lt;code&gt;service_role&lt;/code&gt; in the dashboard, allows your backend to avoid Supabase&apos;s Row Level Security (RLS). In contrast to the &lt;code&gt;anon public&lt;/code&gt; key it must never be shared publicly.&lt;/p&gt;
&lt;p&gt;Create a new file &lt;code&gt;supabaseAdmin.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createClient } from &quot;@supabase/supabase-js&quot;;

const SUPABASE_URL = process.env.SUPABASE_URL;
const SUPABASE_PRIVATE_KEY = process.env.SUPABASE_PRIVATE_KEY;

const supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_PRIVATE_KEY);

export default supabaseAdmin;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Upload File&lt;/h2&gt;
&lt;p&gt;First we get the data we want to upload from the provided URL and convert it to an array buffer. Next we upload the file to the storage bucket, then use the returned path to get the public URL for the file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import supabaseAdmin from &quot;./supabaseAdminClient&quot;;
import nanoid from &quot;nanoid&quot;;

const BUCKET_NAME = &quot;bucket-name&quot;;

/**
 * Uploads a file to Supabase storage.
 * @returns string The public URL to access the file
 */
const uploadFileFromURL = async (url: string) =&amp;gt; {
  // fetch data to upload
  const dataResponse = await fetch(url);
  const dataBuffer = await dataResponse.arrayBuffer();

  // upload to supabase
  const { data: uploadData, error } = await supabaseAdmin.storage
    .from(BUCKET_NAME)
    .upload(nanoid(), imageBuffer, {
      contentType: &quot;image/jpeg&quot;, // Adjust based on your files&apos;s content type
      upsert: false, // Set to true to overwrite existing files
    });

  // get public URL
  const { data: publicUrl } = supabaseAdmin.storage
    .from(&quot;book-covers&quot;)
    .getPublicUrl(uploadData?.path ?? &quot;&quot;);

  return publicUrl.publicUrl;
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>