{"id":3860,"date":"2021-10-06T19:55:22","date_gmt":"2021-10-06T19:55:22","guid":{"rendered":"https:\/\/ionicframework.com\/blog\/?p=3860"},"modified":"2023-01-21T00:35:35","modified_gmt":"2023-01-21T05:35:35","slug":"dynamic-jamstack-with-stencil-and-supabase","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase","title":{"rendered":"Dynamic Jamstack with Stencil and Supabase"},"content":{"rendered":"<p>The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts. The <em>Jam<\/em> in Jamstack stands for JavaScript, API\u2019s, and markup. Worded differently, static markup is transformed by JavaScript and utilizes third-party API\u2019s at build time and run time. A comprehensive definition and history of Jamstack can be found <a href=\"https:\/\/jamstack.wtf\/\">here.<\/a><\/p>\n<p><!--more--><\/p>\n<p>Despite often being associated with static site generators, Jamstack sites do not need to stay <em>static.<\/em> In situations where prerendered markup alone does not suffice, dynamic content can be progressively loaded on the client side. An online storefront might statically generate listings for all the products in their catalog, and then update details like inventory or price after the page is first rendered. Web components are a great fit for this workflow, because they allow for adding zones of interactivity within otherwise static HTML.<\/p>\n<p>Let\u2019s add user-generated comments to a Jamstack site with a web component created with <a href=\"https:\/\/stenciljs.com\">Stencil<\/a> that utilizes <a href=\"https:\/\/supabase.io\/\">Supabase<\/a> to store dynamic data. Supabase is an open-source alternative to Firebase that provides an interface and API that makes database CRUD operations (creating, reading, updating and deleting) possible without writing backend code. I have been using it extensively in my personal projects, and I think combining it with Stencil&#8217;s web components is a wonderful way to create <a href=\"https:\/\/micro-frontends.org\/\">micro-frontends<\/a>: dynamic, independent, contained widgets that can be dropped anywhere in a larger project (whether that is a SPA or a static HTML file).<\/p>\n<p>Here is a preview of the final component we will be creating:<\/p>\n<p><img decoding=\"async\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/commentsScreenshot.png\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" class=\"lazyload\" style=\"--smush-placeholder-width: 856px; --smush-placeholder-aspect-ratio: 856\/862;\"><noscript><img decoding=\"async\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/commentsScreenshot.png\"><\/noscript><\/p>\n<p>The full source is also available on <a href=\"https:\/\/github.com\/willmartian\/my-comments\">GitHub.<\/a><\/p>\n<h2>Usage<\/h2>\n<p>When I am building a component, I like to start out with writing an example of what the end usage might look like. For our comments component, I want it to have a really low-config API: all that should be required to use the component is a unique ID, URL to our backend, and access token for Supabase.<\/p>\n<pre><code class=\"language-html\">&lt;my-comments\n  id=&quot;...&quot;\n  supabase-url=&quot;...&quot;\n  supabase-key=&quot;...&quot;\n&gt;&lt;\/my-comments&gt;\n<\/code><\/pre>\n<h2>Stencil Implementation<\/h2>\n<p>Let&#8217;s scaffold out a Stencil component with  <code>npm run generate my-comments<\/code> in a new Stencil project. This will set us up with a boilerplate, to which we can add public properties with the <code>@Prop<\/code> decorator and internal state with the <code>@State<\/code> decorator. For a deeper introduction to props and state, check out the <a href=\"https:\/\/stenciljs.com\/docs\/properties\">Stencil documentation<\/a>.<\/p>\n<pre><code class=\"language-ts\">@Component({\n  tag: &#039;my-comments&#039;,\n  styleUrl: &#039;my-comments.css&#039;,\n  shadow: true,\n})\nexport class MyComments {\n  \/**\n   * We don&#039;t need a Prop for `id`, since it is a global HTML attribute.\n   * Instead, we can grab it from the HTML element with the @Element decorator.\n   *\/\n  @Element() element;\n\n  \/**\n   * Public URL to the Supabase backend.\n   *\/\n  @Prop() supabaseUrl: string;\n\n  \/**\n   * Public access token to the Supabase backend.\n   *\/\n  @Prop() supabaseKey: string;\n\n  \/**\n   * Comments associated with this block&#039;s `id`.\n   *\/\n  @State() comments: MyComment[] = [];\n\n  \/**\n   * Value of the new comment text input.\n   *\/\n  @State() newCommentValue: string;\n\n  \/**\n   * Supabase client to be initialized with `supabaseUrl` and `supabaseKey`.\n   * We will import this type when we install Supabase.\n   *\/\n  private supabase: SupabaseClient;\n\n  render() {\n    \/\/...\n  }\n}\n<\/code><\/pre>\n<p>The <code>this.comments<\/code> array we defined above will hold the comments data loaded from the Supabase backend. Let&#8217;s define a <code>MyComment<\/code> type so that we can utilize Stencil&#8217;s built in TypeScript support.<\/p>\n<pre><code class=\"language-ts\">type MyComment = {\n  \/*\n   * The id of the author who wrote the comment.\n   *\/\n  author_id: string;\n  \/*\n   * The body of the comment.\n   *\/\n  content: string;\n  \/*\n   * The datetime when the comment was first posted.\n   *\/\n  created_at: string;\n  \/*\n   * The `id` of the component that created this comment.\n   * For example,  would create and retrieve comments with\n   * a `location_id` of &quot;blog-post-1&quot;.\n   *\/\n  location_id: string;\n  \/*\n   * A unique id for the comment, generated by Supabase.\n   *\/\n  id: string;\n}\n<\/code><\/pre>\n<h3>Rendering a template<\/h3>\n<p>Now we are ready to think about what we need to render to the user:<br \/>\n&#8211; A list of current comments.<br \/>\n&#8211; An input field for adding a new comment.<\/p>\n<p>When building Stencil components with multiple moving parts, I find it helpful to separate out semantically-different templates into multiple render functions or functional components.<\/p>\n<p>The <code>renderComment<\/code> helper function will be used to render the markup for an individual comment. To make sure the comment is accessible, let&#8217;s write it as an <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/article\">HTML article element<\/a> with the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Accessibility\/ARIA\/Roles\/Comment_role\">ARIA <em>comment<\/em> role<\/a>.<\/p>\n<pre><code class=\"language-jsx\">private renderComment(comment: MyComment) {\n  return (\n    &lt;article role=&quot;comment&quot;&gt;\n    &lt;header&gt;\n        &lt;h1&gt;{comment.author_id}&lt;\/h1&gt;\n    &lt;\/header&gt;\n    &lt;p&gt;{comment.content}&lt;\/p&gt;\n    &lt;footer&gt;\n        &lt;small&gt;&lt;time dateTime={comment.created_at}&gt;{comment.created_at}&lt;\/time&gt;&lt;\/small&gt;\n    &lt;\/footer&gt;\n    &lt;\/article&gt;\n  );\n}\n<\/code><\/pre>\n<p>Now in our <code>render<\/code> function, we can call <code>renderComment<\/code> for each item in the <code>this.comments<\/code> state variable:<\/p>\n<pre><code class=\"language-jsx\">render() {\n  return (\n    &lt;Host&gt;\n      &lt;h1&gt;Comments&lt;\/h1&gt;\n      {this.comments.map(comment =&gt; this.renderComment(comment))}\n    &lt;\/Host&gt;\n  );\n}\n<\/code><\/pre>\n<p>The last thing we need to add to our template is a way for users to add new comments. Let&#8217;s add a small form with a submit button and a text input that updates the <code>newCommentValue<\/code>.<\/p>\n<pre><code class=\"language-jsx\">render() {\n  return (\n    &lt;Host&gt;\n      &lt;h2&gt;Comments&lt;\/h2&gt;\n      &lt;form onSubmit={(ev: Event) =&gt; this.handleSubmit(ev)}&gt;\n        &lt;textarea\n          rows={5}\n          placeholder=&quot;Add a comment...&quot;\n          value={this.newCommentValue}\n          onChange={(ev: Event) =&gt; this.handleChange(ev)}\n        &gt;&lt;\/textarea&gt;\n        &lt;input type=&quot;submit&quot; value=&quot;Submit&quot;\/&gt;\n      &lt;\/form&gt;\n      {this.comments.map(comment =&gt; renderComment(comment))}\n    &lt;\/Host&gt;\n  );\n}\n<\/code><\/pre>\n<h3>Handling Input<\/h3>\n<p>In the form template above, we referenced two methods to be implemented that handle user input. In the <code>handleChange<\/code> method we want to store the current value of the input element into component state. The <code>handleSubmit<\/code> method will be implemented in the next section.<\/p>\n<pre><code class=\"language-ts\">private handleChange(ev: Event) {\n  const target = ev.currentTarget as HTMLInputElement;\n  this.newCommentValue = target.value;\n}\nprivate handleSubmit(ev: Event) {\n  \/\/ TODO\n}\n<\/code><\/pre>\n<h2>Setting up Supabase<\/h2>\n<p>Now we have a shell set up to display our comments, and props and state set up to hold our data. Let&#8217;s configure Supabase to hold our comment data and wire it up to the component\u2019s lifecycle events.<\/p>\n<p>After registering an account and creating a new project, configuring Supabase to work with our frontend only takes a couple of steps.<\/p>\n<h3>Creating a Table to Store Comments<\/h3>\n<p>In the Supabase sidebar, go to the &#8220;Tables&#8221; page in the sidebar and then select the <code>New<\/code> button. Let\u2019s create a table with fields that match our <code>MyComment<\/code> type above. Each field will be a column in the table, and each row will represent a comment.<\/p>\n<p><img decoding=\"async\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/supacommentsTable.png\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" class=\"lazyload\" style=\"--smush-placeholder-width: 1314px; --smush-placeholder-aspect-ratio: 1314\/1864;\"><noscript><img decoding=\"async\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/supacommentsTable.png\"><\/noscript><\/p>\n<h3>Get the Supabase URL and Key<\/h3>\n<p>In the Supabase sidebar, go to the &#8220;Settings&#8221; page in the sidebar and continue to the &#8220;API&#8221; sub-page. There, Supabase exposes two things we need:<br \/>\n&#8211; The &#8220;anon&#8221; key is the client side api key that allows our frontend to connect to Supabase. This key is safe to share publicly.<br \/>\n&#8211; The config URL is the public REST endpoint for our Supabase project.<br \/>\nThese values correspond to the <code>supabaseUrl<\/code> and <code>supabaseKey<\/code> props that were added to the component.<\/p>\n<pre><code class=\"language-html\">&lt;my-comments\n  id=&quot;...&quot;\n  supabase-url=&quot;[supabaseURL]&quot;\n  supabase-key=&quot;[supabaseKey]&quot;\n&gt;&lt;\/my-comments&gt;\n<\/code><\/pre>\n<p><strong>This tutorial does not cover setting up authentication.<\/strong> Supabase has tutorials for doing so <a href=\"https:\/\/supabase.io\/docs\/guides\/auth\">here.<\/a> Setting up proper authentication is necessary to prevent unrestricted CRUD access to the database.<\/p>\n<h2>Connecting Supabase to Stencil<\/h2>\n<p>We need the component to interact with Supabase in three ways:<br \/>\n&#8211; Read comments from the database when first loaded<br \/>\n&#8211; Load new comments whenever the database is updated<br \/>\n&#8211; Submit a new comment when the user presses the submit button<br \/>\nSimilar to how we added multiple render helper functions in the previous section, when I am creating a component, I like to separate logical tasks like these into their own helper functions as well.<\/p>\n<h3>Initialize Supabase<\/h3>\n<p>When our component first loads, we need to also load the Supabase client and connect to it.<\/p>\n<p>Let&#8217;s add the Supabase client as a dependency to our project with <code>npm i @supabase\/supabase-js<\/code>. After installing, we can get the references we need with the following import statement at the top of the component file.<\/p>\n<pre><code class=\"language-ts\">import { createClient, SupabaseClient } from &#039;@supabase\/supabase-js&#039;;\n<\/code><\/pre>\n<p>Now, in the <code>componentWillLoad<\/code> lifecycle function, we can initialize Supabase with the appropriate props that were provided to the component.<\/p>\n<pre><code class=\"language-ts\"> componentWillLoad() {\n   this.supabase = createClient(this.supabaseUrl, this.supabaseKey);\n }\n<\/code><\/pre>\n<h3>Read comments from the database<\/h3>\n<p>Supabase allows for chaining function calls to grab data from a table, filter out irrelevant data, and sorting the returned entries.<\/p>\n<pre><code class=\"language-ts\">private async getComments() {\n  const { data } = await this.supabase\n    \/\/ Grab data from the &#039;comments&#039; table.\n    .from(&#039;comments&#039;)\n    .select()\n    \/\/ Only grab data that is associated with this component&#039;s `id`.\n    .eq(&#039;location_id&#039;, this.element.id)\n    \/\/ Order the data such that new comments are at the top of the list.\n    .order(&#039;created_at&#039;, { ascending: false });\n  \/\/ Store the response in the `comments` state variable.\n  this.comments = data;\n}\n<\/code><\/pre>\n<p>We want to load the state when the component is first loaded, so we can call <code>getComments<\/code> in the <code>componentWillLoad<\/code> lifecycle function.<\/p>\n<pre><code class=\"language-ts\">componentWillLoad() {\n  this.getComments();\n}\n<\/code><\/pre>\n<h3>Real-time updates from the database<\/h3>\n<p>It would be nice if new comments were automatically shown in our comment list. Supabase provides a way to do this as well.<\/p>\n<pre><code class=\"language-ts\">private async watchComments() {\n  await this.supabase\n    \/\/ Only watch updates that match our component&#039;s id\n    .from(`comments:location_id=eq.${this.element.id}`)\n    \/\/ When a comment is inserted into the table, update the component state.\n    .on(&#039;INSERT&#039;, payload =&amp;gt; {\n    this.comments = [payload.new, ...this.comments];\n    })\n    .subscribe()\n}\n<\/code><\/pre>\n<p>This can also be added to the <code>componentWillLoad<\/code> lifecycle function.<\/p>\n<pre><code class=\"language-ts\">componentWillLoad() {\n  this.getComments();\n  this.watchComments();\n}\n<\/code><\/pre>\n<h3>Add new comment to the database<\/h3>\n<p>The last piece of functionality we need is the ability to add new comments.<\/p>\n<pre><code class=\"language-ts\">private async addComment() {\n  const { data, error } = await this.supabase\n    .from(&#039;comments&#039;)\n    .insert([\n    {\n        content: this.newCommentValue,\n        author: &#039;Author Name&#039;, \/\/ Dummy value. (To be implemented when auth is added.)\n        location_id: this.element.id\n        \/\/ Supabase will automatically generate `id` and `created_at`\n    }\n    ]);\n  return data;\n}\n<\/code><\/pre>\n<p>Now, we need to call <code>addComment<\/code> whenever the form in our component&#8217;s template is submitted. Let&#8217;s add it to the <code>handleSubmit<\/code> function we created earlier.<\/p>\n<pre><code class=\"language-ts\">private handleSubmit(ev: Event) {\n  \/\/ Prevent the default event behavior to keep the page from refreshing.\n  ev.preventDefault();\n  this.addComment();\n}\n<\/code><\/pre>\n<h2>Adding Styles<\/h2>\n<p>Lastly, let\u2019s make the component a little nicer to look at by adding some styles. I have added the following CSS to <code>my-comments.css<\/code>, which is referenced in the <code>@Component<\/code> decorator.<\/p>\n<pre><code class=\"language-css\">:host {\n  display: block;\n  font-family: sans-serif;\n}\n\narticle[role=&quot;comment&quot;] {\n  margin-top: 1rem;\n  padding: 1rem;\n  border: 2px solid black;\n  border-radius: .5rem;\n  box-shadow: black 5px 5px;\n}\n\nform {\n  display: flex;\n  flex-direction: column;\n}\n\nform textarea {\n  width: 100%;\n  border-radius: 5px;\n  border: 2px solid lightgray;\n  resize: vertical;\n  padding: .5rem;\n  box-sizing: border-box;\n}\n\nform input[type=&quot;submit&quot;] {\n  background-color: black;\n  color: white;\n  border: none;\n  border-radius: 5px;\n  text-align: start;\n  max-width: min-content;\n  margin-top: .5rem;\n  padding: .5rem;\n}\n<\/code><\/pre>\n<h2>Wrapping up<\/h2>\n<p>There are a couple of things not covered in this tutorial that are necessary before the component is ready for public use, such as adding authentication and some content moderation capabilities. However, when we are ready and we <a href=\"https:\/\/stenciljs.com\/docs\/publishing\">publish the component to npm<\/a>, we will have a powerful micro-frontend, wrapped in a Stencil web component, and ready to be dropped into any Jamstack website with zero additional configuration.<\/p>\n<p>That is one of the natural synergies that web-standard Stencil web components have with the Jamstack. All that is required is a script tag and your components are ready to be consumed. Additionally, this component can be built to Angular, React, or Vue components using Stencil\u2019s built in <a href=\"https:\/\/stenciljs.com\/docs\/overview\">framework integrations.<\/a> Personally, I can\u2019t wait to expand this starter and add it to <a href=\"https:\/\/willmartian.com\">my Jamstack blog<\/a> built with <a href=\"https:\/\/www.11ty.dev\/\">11ty<\/a>. If you enjoyed this tutorial, leave a comment below if you would like to see more!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts. The Jam in Jamstack stands for JavaScript, API\u2019s, and markup. Worded differently, static markup is transformed by JavaScript and utilizes third-party API\u2019s at build [&hellip;]<\/p>\n","protected":false},"author":88,"featured_media":3869,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"0","publish_post_category":"21","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"534112","discourse_permalink":"https:\/\/forum.ionicframework.com\/t\/dynamic-jamstack-with-stencil-and-supabase\/215831","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[223,124],"tags":[140,76,82],"class_list":["post-3860","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stencil","category-tutorials","tag-design-systems","tag-stencil","tag-web-components"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Dynamic Jamstack with Stencil and Supabase - Ionic Blog<\/title>\n<meta name=\"description\" content=\"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Dynamic Jamstack with Stencil and Supabase\" \/>\n<meta property=\"og:description\" content=\"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-10-06T19:55:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-01-21T05:35:35+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1600\" \/>\n\t<meta property=\"og:image:height\" content=\"880\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Will Martin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Will Martin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\"},\"author\":{\"name\":\"Will Martin\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/f76dca2fef900caa2611f7c83ee7386a\"},\"headline\":\"Dynamic Jamstack with Stencil and Supabase\",\"datePublished\":\"2021-10-06T19:55:22+00:00\",\"dateModified\":\"2023-01-21T05:35:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\"},\"wordCount\":1304,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png\",\"keywords\":[\"Design Systems\",\"stencil\",\"web components\"],\"articleSection\":[\"Stencil\",\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\",\"url\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\",\"name\":\"Dynamic Jamstack with Stencil and Supabase - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png\",\"datePublished\":\"2021-10-06T19:55:22+00:00\",\"dateModified\":\"2023-01-21T05:35:35+00:00\",\"description\":\"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png\",\"width\":1600,\"height\":880},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Dynamic Jamstack with Stencil and Supabase\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/f76dca2fef900caa2611f7c83ee7386a\",\"name\":\"Will Martin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/3df979896a905f48e2940585e4d8e02c00e654a6fcd9445db8c27c294db0ce46?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/3df979896a905f48e2940585e4d8e02c00e654a6fcd9445db8c27c294db0ce46?s=96&d=mm&r=g\",\"caption\":\"Will Martin\"},\"description\":\"Software Engineer working on Ionic Framework\",\"sameAs\":[\"http:\/\/willmartian.com\"],\"url\":\"https:\/\/ionic.io\/blog\/author\/ionicwill\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Dynamic Jamstack with Stencil and Supabase - Ionic Blog","description":"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase","og_locale":"en_US","og_type":"article","og_title":"Dynamic Jamstack with Stencil and Supabase","og_description":"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.","og_url":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase","og_site_name":"Ionic Blog","article_published_time":"2021-10-06T19:55:22+00:00","article_modified_time":"2023-01-21T05:35:35+00:00","og_image":[{"width":1600,"height":880,"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","type":"image\/png"}],"author":"Will Martin","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Will Martin","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase"},"author":{"name":"Will Martin","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/f76dca2fef900caa2611f7c83ee7386a"},"headline":"Dynamic Jamstack with Stencil and Supabase","datePublished":"2021-10-06T19:55:22+00:00","dateModified":"2023-01-21T05:35:35+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase"},"wordCount":1304,"commentCount":0,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","keywords":["Design Systems","stencil","web components"],"articleSection":["Stencil","Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase","url":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase","name":"Dynamic Jamstack with Stencil and Supabase - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","datePublished":"2021-10-06T19:55:22+00:00","dateModified":"2023-01-21T05:35:35+00:00","description":"The Jamstack is a modern architecture for creating websites that focus on performance and developer experience. It can be used for anything from personal static blogs to large, enterprise, dynamic storefronts.","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","width":1600,"height":880},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/dynamic-jamstack-with-stencil-and-supabase#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Dynamic Jamstack with Stencil and Supabase"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/f76dca2fef900caa2611f7c83ee7386a","name":"Will Martin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/3df979896a905f48e2940585e4d8e02c00e654a6fcd9445db8c27c294db0ce46?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/3df979896a905f48e2940585e4d8e02c00e654a6fcd9445db8c27c294db0ce46?s=96&d=mm&r=g","caption":"Will Martin"},"description":"Software Engineer working on Ionic Framework","sameAs":["http:\/\/willmartian.com"],"url":"https:\/\/ionic.io\/blog\/author\/ionicwill"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2021\/10\/stencil-supabase-feature-image.png","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3860","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/88"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=3860"}],"version-history":[{"count":1,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3860\/revisions"}],"predecessor-version":[{"id":4727,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3860\/revisions\/4727"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media\/3869"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=3860"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=3860"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=3860"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}