<?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: Wilson Staley</title>
    <description>The latest articles on DEV Community by Wilson Staley (@wilstaley).</description>
    <link>https://dev.to/wilstaley</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%2F227912%2F04adc011-9c77-4e7c-9bb0-93fef590b707.jpeg</url>
      <title>DEV Community: Wilson Staley</title>
      <link>https://dev.to/wilstaley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wilstaley"/>
    <language>en</language>
    <item>
      <title>Community Drawing 🎨</title>
      <dc:creator>Wilson Staley</dc:creator>
      <pubDate>Mon, 23 Dec 2024 01:46:31 +0000</pubDate>
      <link>https://dev.to/wilstaley/community-drawing-1fm9</link>
      <guid>https://dev.to/wilstaley/community-drawing-1fm9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/devcycle"&gt;DevCycle Feature Flag Challenge&lt;/a&gt;: Feature Flag Funhouse&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For the DevCycle Feature Flag Challenge, I made a unique daily drawing app.  This app challenges users to draw a daily prompt.  However there’s a twist!  The pen tool settings are controlled by feature flags and will be the same for all active users!  When you update features like the stroke weight and color, they will change for &lt;em&gt;all users 👀.&lt;/em&gt;  All active users must work together to create something great.&lt;/p&gt;

&lt;p&gt;The first challenge in building this was configuring a web socket API.  I used an AWS API Gateway WebSocket API to broadcast pen strokes to all connected users.  This involved setting up a couple lambda functions to manage connections and send messages to all connected users.&lt;/p&gt;

&lt;p&gt;Once the API was setup, I created a Next.js app with a Canvas element. I attached some event listeners to the canvas to implement a basic drawing feature. One of the most difficult parts of building this app was figuring out how to emit a message to the WebSocket containing the pen stroke data and replicate the drawing on all connected devices. I ended up storing a stroke as an array of x-y-coordinate tuples.  When the user finishes drawing a stroke (mouse up event), I broadcast the message to all users.&lt;/p&gt;

&lt;p&gt;Then it was time to add some pen stroke options including a stroke weight and color.  This is where I implemented DevCycle variables.  I setup a feature in my DevCycle account for the pen stroke options. I added a “stroke-weight” number variable to this feature to manage the state of the stroke weight for all users.  I also added a “stroke-color” string variable to manage the hex code used by the color picker. The DevCycle React SDK made it easy to retrieve these values using the “useVariableValue” hook. Whenever the user changes the stroke weight or color, I invoke a server action which makes a request to the DevCycle API to update the primary variation of these variables which is served to all users.  I also debounced the color picker so as not to spam the API with too many requests.&lt;/p&gt;

&lt;p&gt;To finish the app, I setup a DynamoDB table to save the contents of the drawing whenever a new stroke is added to the canvas.  I also setup a cronjob that creates a new prompt every day.  This cronjob utilizes the OpenAI API to generate a new prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The app is live here: &lt;a href="https://community-drawing-app.vercel.app/" rel="noopener noreferrer"&gt;https://community-drawing-app.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is an example drawing:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.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%2Fm1lvaeg6jcj4b45bmey5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fm1lvaeg6jcj4b45bmey5.png" alt="Community Drawing app in action!" width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is a screenshot of chaotic drawing I made while testing the app:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.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%2Flzrcmnkxtetvfrbtn7xt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flzrcmnkxtetvfrbtn7xt.png" alt="Testing the app" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here is a video demonstrating realtime drawing updates for multiple users:&lt;/em&gt;&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EjV5K41ZOhE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wilstaley/community-drawing-app" rel="noopener noreferrer"&gt;https://github.com/wilstaley/community-drawing-app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My DevCycle Experience
&lt;/h2&gt;

&lt;p&gt;I leveraged DevCycle’s React client SDK to retrieve the values of my pen stroke variables.  I also utilized the DevCycle API to patch variations’ values as users update the settings. Overall, I had a very good experience with DevCycle. I really liked the structuring of features, variables, and variations.  This allowed me to cleanly model my data.  I had a “stroke-options” feature containing two variables for “stroke-weight” and “stroke-color”.  The only problem I had was with one aspect of the API. I wanted to update the value of a variation for a single variable in my feature. However, when I sent a patch request to the “update variation” endpoint, if I didn’t specify the values for both variables, the one I left out was overwritten.  Even though I wanted to only update the value of “stroke-weight”, I had to send a value for both “stroke-weight” and “stroke-value” in the request to ensure nothing was overwritten.  This might be expected behavior, but typically I think a PATCH request would only update provided values and not overwrite existing data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Prize Categories
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;OpenFeature Aficionado&lt;/li&gt;
&lt;li&gt;API All-Star&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>devcyclechallenge</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Using Notion as a CMS</title>
      <dc:creator>Wilson Staley</dc:creator>
      <pubDate>Sat, 18 Feb 2023 17:36:23 +0000</pubDate>
      <link>https://dev.to/wilstaley/using-notion-as-a-cms-2p45</link>
      <guid>https://dev.to/wilstaley/using-notion-as-a-cms-2p45</guid>
      <description>&lt;p&gt;Notion is a great tool for writing and managing documents. I love writing in Notion because it provides a focused view where I can get all my thoughts down with minimal distraction. Because I love the simple, organized style of Notion, I decided to model my &lt;a href="https://wilsonstaley.dev" rel="noopener noreferrer"&gt;new website&lt;/a&gt; off of the Notion interface. I also wanted to use Notion as a content management system, so I could write my blog posts directly in Notion, click a button to publish it when I'm ready, and have it immediately appear on the site. I love the seamless integration I've been able to achieve with Notion and want to show you how I did it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;First I want to tell you a little about the stack I've used to create wilsonstaley.dev. The site is a React application, scaffolded and built with &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;. I used &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; for styling mainly because I'm not too familiar with it and wanted to test it out on a new project (I'll have to share my thoughts on Tailwind in a future post 🙂).&lt;/p&gt;

&lt;p&gt;I'm hosting the site on &lt;a href="https://netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, and all the blog post data is client-side rendered. So when you land on the homepage, a call is made to retrieve all the blog posts from Notion and populate the page. While it would probably be more performant to use something like Nextjs to statically build all the blog pages, this does avoid an issue with Notion images where the links expire after a short period of time. I am however using &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt; to get some caching and preloading benefits.&lt;/p&gt;

&lt;p&gt;And of course, for this to work, you'll need a Notion account! Let's talk about how I've structured the data in Notion…&lt;/p&gt;

&lt;h2&gt;
  
  
  Notion Database
&lt;/h2&gt;

&lt;p&gt;To store all of my blog posts, I created a database in Notion. You can do this by clicking "New Page" and selecting "Database". In Notion, a database is a collection of related data that is organized into rows and columns. Think of it like a spreadsheet, but with more flexibility and functionality.&lt;/p&gt;

&lt;p&gt;In my case, I set up a database where each row corresponds to a blog post. I gave it properties including "Title", "Description", "Tags", "Date", "Published", and "Slug".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F29zf9up5n58tbq0h4dhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F29zf9up5n58tbq0h4dhb.png" alt="Notion Database" width="800" height="248"&gt;&lt;/a&gt;&lt;br&gt;
These properties are pretty self-explanatory.&lt;/p&gt;

&lt;p&gt;The "Published" checkbox is an easy way I can mark a post to be publicly visible on the site. I can write the post and see how it looks in dev, and when I'm good and ready, all I have to do is click the checkbox to make it go live. This is another perk of client-side rendering - each visitor will see the most up-to-date records from my Notion database.&lt;/p&gt;

&lt;p&gt;The "Slug" property is also useful. I am using this to determine what URL the user sees for each post. For example, the post titled "Git Workflows 101" would be accessible at the URL wilsonstaley.dev/posts/git-workflows-101.&lt;/p&gt;
&lt;h2&gt;
  
  
  Retrieving the Data
&lt;/h2&gt;

&lt;p&gt;Now we'll finish up our "backend" by creating a simple API with a single endpoint. This will just be an endpoint where we can retrieve either a list of blog posts or the data for an individual post.&lt;/p&gt;

&lt;p&gt;To create this lambda function, I used a &lt;a href="https://www.netlify.com/products/functions/" rel="noopener noreferrer"&gt;Netlify function&lt;/a&gt;. This is a service Netlify offers that lets you quickly deploy a serverless function in conjunction with your site. I also used Notion's SDK to easily query the database I set up.&lt;/p&gt;

&lt;p&gt;First, let's look at an example of what it would look like to query the database for a list of blog posts:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@notionhq/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NETLIFY_ENV&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;"&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;notion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_API_KEY&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;databaseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_DATABASE_ID&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;queryOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sorts&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="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;descending&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;//If in production, filter to only published posts&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Published&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;equals&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryOptions&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;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;posts&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;In this example, I am querying the Notion database and asking for the results to be sorted by date. I am also filtering the results to only return the published posts if I am in a production environment. In order for this to work, you must make sure you have &lt;a href="https://developers.notion.com/docs/create-a-notion-integration" rel="noopener noreferrer"&gt;created a Notion integration and shared the database with your integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let us look at what the code might look like for retrieving data for a specific blog post:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@notionhq/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NotionToMarkdown&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notion-to-md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;notion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryStringParameters&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;)&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postId&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;n2m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotionToMarkdown&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;notionClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;notion&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;mdblocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;n2m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pageToMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&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;mdString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n2m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMarkdownString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mdblocks&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;responseData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bannerImg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mdString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;plain_text&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;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseData&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;Here, I'm retrieving data for a specific page and using the &lt;a href="https://github.com/souvikinator/notion-to-md" rel="noopener noreferrer"&gt;notion-to-md&lt;/a&gt; package to generate Markdown for this blog post. Then, I package it into a custom response that I can use for individual blog post pages on my site.&lt;/p&gt;

&lt;p&gt;Netlify functions are available as a custom endpoint when you deploy your site. In my case, I created an endpoint &lt;code&gt;/.netlify/functions/posts&lt;/code&gt;. Hitting this endpoint returns the data for all the blog posts. Adding a query param &lt;code&gt;/.netlify/functions/posts?id=1d12aaeb324&lt;/code&gt; gives you the data for that individual post, including the Markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering a Blog Page
&lt;/h2&gt;

&lt;p&gt;Now that we've got our backend setup to retrieve the data from Notion, let's look at how we can render that data in a readable format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Layout/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;{bannerImg}&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"block w-full h-48 md:h-56 lg:h-64 xl:h-80 object-cover object-center"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;     
  &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"max-w-screen-sm m-auto px-4 pt-4 pb-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"my-6 text-4xl font-bold"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ReactMarkdown&lt;/span&gt; &lt;span class="na"&gt;components=&lt;/span&gt;&lt;span class="s"&gt;{markdownMapping}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {markdown}
    &lt;span class="nt"&gt;&amp;lt;/ReactMarkdown&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Layout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example React component, I have a page that can be used with any blog post. The page includes a banner image, a title, and the &lt;code&gt;&amp;lt;ReactMarkdown /&amp;gt;&lt;/code&gt; component that will take all of the markdown I generated on the backend and transform it into valid HTML. The “components” attribute on the markdown component lets me specify custom components to use for each markdown element. So if I were to have a link in my markdown, I could map it to my own custom link component. If you want to learn more about how that works, you can look into the &lt;a href="https://github.com/remarkjs/react-markdown" rel="noopener noreferrer"&gt;react-markdown&lt;/a&gt; library.&lt;/p&gt;




&lt;p&gt;And with that, we’ve setup a really simple, yet powerful content management system using Notion.  I love how easy it was to get this setup and how fast I can publish new content.  I hope this tutorial was helpful and gave you some idea of how you can use Notion in your next project!&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
  </channel>
</rss>
