<?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: Tina</title>
    <description>The latest articles on DEV Community by Tina (@tinacms).</description>
    <link>https://dev.to/tinacms</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%2Forganization%2Fprofile_image%2F1323%2Faa4174a7-d373-4853-ac04-bc523118ea34.png</url>
      <title>DEV Community: Tina</title>
      <link>https://dev.to/tinacms</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tinacms"/>
    <language>en</language>
    <item>
      <title>Markdown Bot - An AI friend who improves your content</title>
      <dc:creator>scottgallant</dc:creator>
      <pubDate>Fri, 21 Jul 2023 17:19:30 +0000</pubDate>
      <link>https://dev.to/tinacms/markdown-bot-an-ai-friend-who-improves-your-content-1dpd</link>
      <guid>https://dev.to/tinacms/markdown-bot-an-ai-friend-who-improves-your-content-1dpd</guid>
      <description>&lt;p&gt;With &lt;a href="https://tina.io" rel="noopener noreferrer"&gt;TinaCMS&lt;/a&gt;, all your content changes are committed directly to Git. This enables your team to create a variety of workflows for reviewing and merging content updates. By leaning on GitHub, you can integrate CI/CD into your content workflow.&lt;/p&gt;

&lt;p&gt;To illustrate the potential of this combination, we're excited to introduce &lt;strong&gt;Markdown Bot&lt;/strong&gt;, an AI friend who improves your content by making suggestions to your Pull Requests.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/3SkumYmH8nc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want to skip the reading and jump straight to the code? &lt;a href="https://github.com/tinacms/markdown-bot" rel="noopener noreferrer"&gt;Check out the open source repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Useful Aid, Not a Replacement
&lt;/h2&gt;

&lt;p&gt;AI can be a valuable tool for assisting with writing and editing content. We've designed this bot not to replace content editors, but rather to augment their capabilities. The bot offers content suggestions directly in your pull requests. If you find the suggestions helpful, you can commit them with a click. If not, they're just as easily dismissed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fimage%2Fupload%2Fv1689957451%2Fblog-media%2Fmarkdown-bot%2FScreenshot_2023-07-21_at_12.36.48_PM_ztpdes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fimage%2Fupload%2Fv1689957451%2Fblog-media%2Fmarkdown-bot%2FScreenshot_2023-07-21_at_12.36.48_PM_ztpdes.png" title="AI Suggestion in Github" alt="AI Suggestion in Github"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown Bot Works On Your PRs
&lt;/h2&gt;

&lt;p&gt;There are many AI writing tools out there but if you use them with markdown content it often involved copying and pasting from AI outputs. We wanted something that could interact with our Content in GitHub. That's why we developed a GitHub bot that allows us to receive these suggestions right within a GitHub pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with the GitHub Bot
&lt;/h2&gt;

&lt;p&gt;After you've integrated the bot into your repository, you can command it to make suggestions by commenting &lt;code&gt;ai fix: &amp;lt;path to file&amp;gt;&lt;/code&gt;. A custom prompt can be added by using &lt;code&gt;prompt: &amp;lt;Custom Prompt&amp;gt;&lt;/code&gt; underneath. The bot will then offer commit suggestions in the form of a pull request review.&lt;/p&gt;

&lt;p&gt;To get started &lt;a href="https://github.com/tinacms/markdown-bot" rel="noopener noreferrer"&gt;check out the open source repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Ahead: AI and Git-based Content
&lt;/h2&gt;

&lt;p&gt;Our GitHub bot works hand in hand with TinaCMS to enhance the content creation process. No longer do you need to manually copy and paste suggestions. The bot brings suggestions right to your pull requests for a smooth, efficient experience.&lt;/p&gt;

&lt;p&gt;We can envision some impressive custom workflows being built with AI and Git-based content. For instance, you could build off of this bot to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger the AI bot with custom events, such as opening a PR.&lt;/li&gt;
&lt;li&gt;Utilize analytics to suggest recommendations based on your top/bottom performing pages.&lt;/li&gt;
&lt;li&gt;Integrate this bot with your feedback widget, to open PRs based on user feedback.&lt;/li&gt;
&lt;li&gt;Catch insensitive, inconsiderate writing with tools like &lt;a href="https://github.com/get-alex/alex" rel="noopener noreferrer"&gt;alex&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are just a few of the many possibilities we see for integrating AI with Git-based content. We're excited about the potential here and look forward to seeing the creative workflows that the community will build.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A/B testing with Next.js Middleware and TinaCMS</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Fri, 03 Jun 2022 12:56:16 +0000</pubDate>
      <link>https://dev.to/tinacms/ab-testing-with-nextjs-middleware-and-tinacms-cem</link>
      <guid>https://dev.to/tinacms/ab-testing-with-nextjs-middleware-and-tinacms-cem</guid>
      <description>&lt;p&gt;A/B testing can be an incredibly useful tool on any site. It allows you to increase user engagement, reduce bounce rates, increase conversion rate and effectively create content.&lt;/p&gt;

&lt;p&gt;Tina opens up the ability to A/B test, allowing marketing teams to test content without the need for the development team once it has been implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We are going to break this tutorial into two sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting up A/B Tests with NextJS's middleware.&lt;/li&gt;
&lt;li&gt;Configuring our A/B Tests with Tina, so that our editors can spin up dynamic A/B Tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Creating our Tina application
&lt;/h2&gt;

&lt;p&gt;This blog post is going to use the Tailwind Starter. Using the &lt;code&gt;create-tina-app&lt;/code&gt; command, choose "Tailwind Starter" as the starter template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create our Tina application

npx create-tina-app@latest a-b-testing

✔ Which package manager would you like to use? › Yarn
✔ What starter code would you like to use? › Tailwind Starter
Downloading files from repo tinacms/tina-cloud-starter. This might take a moment.
Installing packages. This might take a couple of minutes.

## Move into the directory and make sure everything is updated.

cd a-b-testing
yarn upgrade
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With your site running, you should be able to access it at &lt;code&gt;[http://localhost:3000](http://localhost:3000)&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning our tests
&lt;/h2&gt;

&lt;p&gt;The home page of the starter should look like this:&lt;/p&gt;

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

&lt;p&gt;This page is already setup nicely for an A/B test, its page layout (&lt;code&gt;[slug].tsx&lt;/code&gt;) renders dynamic pages by accepting a variable &lt;code&gt;slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's start by creating an alternate version of the homepage called &lt;code&gt;home-b&lt;/code&gt;. You can do so in Tina &lt;a href="http://localhost:3000/admin#/collections/page/new" rel="noopener noreferrer"&gt;at http://localhost:3000/admin#/collections/page/new&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Once that's done, go to: &lt;code&gt;http://localhost:3000/home-b&lt;/code&gt; to confirm that your new &lt;code&gt;/home-b&lt;/code&gt; page has been created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our A/B tests
&lt;/h2&gt;

&lt;p&gt;Utlimately, we want our site to dynamically swap out certain pages for alternate page-variants, but we will first need a place to reference these active A/b tests.&lt;/p&gt;

&lt;p&gt;Let's create the following file at &lt;code&gt;content/ab-test/index.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "tests": [
    {
      "testId": "home",
      "href": "/",
      "variants": [
        {
          "testId": "b",
          "href": "/home-b"
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use this file later to tell our site that we have an A/B test on our homepage, using &lt;code&gt;/home-b&lt;/code&gt; as a variant.&lt;/p&gt;

&lt;p&gt;In the next step, we'll setup some NextJS middleware to dynamically use this page variant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivering dynamic pages with NextJS middleware
&lt;/h2&gt;

&lt;p&gt;NextJS's middleware allows you to run code before the request is completed. We will leverage NextJS's middleware to conditionally swap out a page for its page-variant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can learn more about NextJS's middleware &lt;a href="https://nextjs.org/docs/advanced-features/middleware" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start by creating the &lt;code&gt;pages/_middleware.ts&lt;/code&gt; file, with the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//pages/_middleware.ts

import { NextRequest, NextResponse } from 'next/server'
import abTestDB from '../content/ab-test/index.json'
import { getBucket } from '../utils/getBucket'

// Check for AB tests on a given page
export function middleware(req: NextRequest) {
  // find the experiment that matches the request's url
  const matchingExperiment = abTestDB.tests.find(
    test =&amp;gt; test.href == req.nextUrl.pathname
  )

  if (!matchingExperiment) {
    // no matching A/B experiment found, so use the original page slug
    return NextResponse.next()
  }

  const COOKIE_NAME = `bucket-${matchingExperiment.testId}`
  const bucket = getBucket(matchingExperiment, req.cookies[COOKIE_NAME])

  const updatedUrl = req.nextUrl.clone()
  updatedUrl.pathname = bucket.url

  // Update the request URL to our bucket URL (if its changed)
  const res =
    req.nextUrl.pathname == bucket.url
      ? NextResponse.next()
      : NextResponse.rewrite(updatedUrl)

  // Add the bucket to cookies if it's not already there
  if (!req.cookies[COOKIE_NAME]) {
    res.cookie(COOKIE_NAME, bucket.id)
  }

  return res
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a little bit going on in the above snippet, but basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We check if there's an active experiment for our request's URL&lt;/li&gt;
&lt;li&gt;If there is, we call &lt;code&gt;getBucket&lt;/code&gt; to see which version of the page we should deliver&lt;/li&gt;
&lt;li&gt;We update the user's cookies so that they consistently get delivered the same page for their given bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll notice that the above code references a &lt;code&gt;getBucket&lt;/code&gt; function. We will need to create that, which will conditionally put us in a bucket for each page's A/B test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// /utils/getBucket.ts

export const getBucket = (matchingABTest: any, bucketCookie?: string) =&amp;gt; {
  // if we already have been assigned a bucket, use that
  // otherwise, call getAssignedBucketId to put us in a bucket
  const bucketId =
    bucketCookie ||
    getAssignedBucketId([
      matchingABTest.testId,
      ...matchingABTest.variants.map(t =&amp;gt; t.testId),
    ])

  // check if our bucket matches a variant
  const matchingVariant = matchingABTest.variants.find(
    t =&amp;gt; t.testId == bucketId
  )

  if (matchingVariant) {
    // we matched a page variant
    return {
      url: matchingVariant.href,
      id: bucketId,
    }
  } else {
    //invalid bucket, or we're matched with the default AB test
    return {
      url: matchingABTest.href,
      id: matchingABTest.testId,
    }
  }
}

function getAssignedBucketId(buckets: readonly string[]) {
  // Get a random number between 0 and 1
  let n = cryptoRandom() * 100
  // Get the percentage of each bucket
  const percentage = 100 / buckets.length
  // Loop through the buckets and see if the random number falls
  // within the range of the bucket
  return (
    buckets.find(() =&amp;gt; {
      n -= percentage
      return n &amp;lt;= 0
    }) ?? buckets[0]
  )
}

function cryptoRandom() {
  return crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code snippet does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks if we're already in a bucket, and if not, it calls &lt;code&gt;getAssignedBucketId&lt;/code&gt; to randomly put us in a bucket.&lt;/li&gt;
&lt;li&gt;Returns the matching A/B test info for our given bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That should be all for our middleware! Now, you should be able to visit the homepage at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, and you will have a 50-50 chance of being served the contents of &lt;code&gt;home.md&lt;/code&gt;, or &lt;code&gt;home-b.md&lt;/code&gt;. You can reset your bucket by clearing your browser's cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Tina to create A/B Tests
&lt;/h2&gt;

&lt;p&gt;At this point, our editors can edit the contents of both &lt;code&gt;home.md&lt;/code&gt; &amp;amp; &lt;code&gt;home-b.md&lt;/code&gt;, however we'd like our editors to be empowered to setup new A/B tests.&lt;/p&gt;

&lt;p&gt;Let's make our &lt;code&gt;content/ab-test/index.json&lt;/code&gt; file from earlier editable, by creating a Tina collection for it.&lt;/p&gt;

&lt;p&gt;Open up your &lt;code&gt;schema.ts&lt;/code&gt; and underneath the &lt;code&gt;Pages&lt;/code&gt; collection, create a new collection like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// ...,
    {
      label: "AB Test",
      name: "abtest",
      path: "content/ab-test",
      format: "json",
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to add the fields we want our content team to be able to edit, so that would be the ID, the page to run the test against, and the variants we want to run. We also want to be able to run tests on any number of pages so we will be using a list of objects for the &lt;code&gt;tests&lt;/code&gt; field.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to learn more about all the different field types and how to use them, check them out in our &lt;a href="https://tina.io/docs/schema/" rel="noopener noreferrer"&gt;Content Modeling documentation.&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
      label: "AB Test",
      name: "abtest",
      path: "content/ab-test",
      format: "json",
      fields: [
        {
          type: "object",
          label: "tests",
          name: "tests",
          list: true,
          ui: {
            itemProps: (item) =&amp;gt; {
              return { label: item.testId || "New A/B Test" };
            },
          },
          fields: [
            { type: "string", label: "Id", name: "testId" },
            {
              type: "string",
              label: "Page",
              name: "href",
              description:
                "This is the root page that will be conditionally swapped out",
            },
            {
              type: "object",
              name: "variants",
              label: "Variants",
              list: true,
              ui: {
                itemProps: (item) =&amp;gt; {
                  return { label: item.testId || "New variant" };
                },
              },
              fields: [
                { type: "string", label: "Id", name: "testId" },
                {
                  type: "string",
                  label: "Page",
                  name: "href",
                  description:
                    "This is the variant page that will be conditionally used instead of the original",
                },
              ],
            },
          ],
        },
      ],
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You may notice the &lt;code&gt;ui&lt;/code&gt; prop. We are using this to give a more descriptive label to the list items. You can read about this in our &lt;a href="https://tina.io/docs/extending-tina/customize-list-ui/" rel="noopener noreferrer"&gt;extending Tina documentation.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also need to update our &lt;code&gt;RouteMappingPlugin&lt;/code&gt; in &lt;code&gt;.tina/schema.ts&lt;/code&gt; to ensure that our collection is only editable with the basic editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      const RouteMapping = new RouteMappingPlugin((collection, document) =&amp;gt; {
        if (collection.name == 'abtest') {
          return undefined
        }
        // ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, restart your dev server, and go to: &lt;code&gt;[http://localhost:3000/admin#/collections/abtest/index](http://localhost:3000/admin#/collections/abtest/index)&lt;/code&gt;. Your editors should be able to wire up their own A/B tests!&lt;/p&gt;

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

&lt;p&gt;The process for editors to create new A/B tests would be as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Editor creates a new page in the CMS&lt;/li&gt;
&lt;li&gt;Editor wires up the page as a page-variant in the "A/B Tests" collection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it! We hope this empowers your team to start testing out different page variants to start optimizing your content!&lt;/p&gt;

&lt;h2&gt;
  
  
  How to keep up to date with Tina?
&lt;/h2&gt;

&lt;p&gt;The best way to keep up with Tina is to subscribe to our newsletter. We send out updates every two weeks. Updates include new features, what we have been working on, blog posts you may have missed, and more!&lt;/p&gt;

&lt;p&gt;You can subscribe by following this link and entering your email: &lt;a href="https://tina.io/community/" rel="noopener noreferrer"&gt;https://tina.io/community/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tina Community Discord
&lt;/h3&gt;

&lt;p&gt;Tina has a community &lt;a href="https://discord.com/invite/zumN63Ybpf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; full of Jamstack lovers and Tina enthusiasts. When you join, you will find a place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get help with issues&lt;/li&gt;
&lt;li&gt;Find the latest Tina news and sneak previews&lt;/li&gt;
&lt;li&gt;Share your project with the Tina community, and talk about your experience&lt;/li&gt;
&lt;li&gt;Chat about the Jamstack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tina Twitter
&lt;/h3&gt;

&lt;p&gt;Our Twitter account (&lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt;) announces the latest features, improvements, and sneak peeks to Tina. We would also be psyched if you tagged us in projects you have built.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>cms</category>
      <category>middleware</category>
    </item>
    <item>
      <title>Automating Pull Requests</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Thu, 14 Apr 2022 14:33:19 +0000</pubDate>
      <link>https://dev.to/tinacms/automating-pull-requests-21el</link>
      <guid>https://dev.to/tinacms/automating-pull-requests-21el</guid>
      <description>&lt;p&gt;When working on a git-backed site, you don’t normally want to work directly on your main branch. Ideally, you want to be able to finish your post or site update, check if it looks good, and schedule the release. In this post, we are going to cover two different options to automate your PRs to make scheduling content easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR Scheduler
&lt;/h2&gt;

&lt;p&gt;PR Scheduler is a GitHub integration that can be installed directly within your GitHub repositories. It was built by &lt;a href="https://tomkadwill.com/" rel="noopener noreferrer"&gt;Tom Kadwill&lt;/a&gt; with the goal to make it easy to schedule pull requests. PR Scheduler lets developers schedule PRs to be merged at a specific time. Instead of having to write your own GitHub action, you can write a comment in your pull request and the application will take care of it for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to install
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://github.com/apps/pr-scheduler" rel="noopener noreferrer"&gt;PR Scheduler's GitHub App page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Click the 'Install' button&lt;/li&gt;
&lt;li&gt;Select whether to install PR Scheduler on all repositories or only specific repositories. Then click 'Install'.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to schedule a pull request
&lt;/h3&gt;

&lt;p&gt;Now, the PR Scheduler can now be used to schedule any of your pull requests. To initiate,do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the pull request that you want to schedule.&lt;/li&gt;
&lt;li&gt;Add a new comment with DD/MM/YYYTHH:MM for example &lt;code&gt;@prscheduler 05/04/2022T14:00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PR Scheduler will respond back telling you it's ready.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;That's it! Now when that time comes, your PR will be merged. If you make a mistake with the time or date, just run the same command and it will reschedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Github Actions
&lt;/h2&gt;

&lt;p&gt;Github Actions are a powerful and flexible way to allow you to run all sorts of DevOps workflows without needing separate tooling. Github Actions uses YAML to define workflows. This makes a great option for scheduling your pull requests where you want to have maximum control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating your GitHub Action
&lt;/h3&gt;

&lt;p&gt;Create a file in your project called &lt;code&gt;.github/workflows/scheduler.yml&lt;/code&gt;. We will use this to create our action.&lt;/p&gt;

&lt;p&gt;There are quite a few options for Github Actions. I have used &lt;strong&gt;merge-schedule-action&lt;/strong&gt; in multiple personal projects, it has create customization and is easy to use. This action takes a few different arguments and uses the date to schedule your PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Merge Schedule
on:
  pull_request:
    types:
      - opened
      - edited
      - synchronize
  schedule:
    # Check every hour.
    - cron: 0 * * * *

jobs:
  merge_schedule:
    runs-on: ubuntu-latest
    steps:
      - uses: gr2m/merge-schedule-action@v1
        with:
          merge_method: merge
          #  Time zone to use. Default is UTC.
          time_zone: "America/New_York"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let us break down what is happening. We have given this job a name of Merge Schedule. It will only trigger on pull requests that are opened, edited, or synchronized. Every hour we run a job called &lt;code&gt;merge_schedule&lt;/code&gt;, thanks to the cronjob &lt;code&gt;0 * * * *&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The steps inside of the jobs section are the most important, it tells GitHub what to do when the schedule runs. First, the job needs to use &lt;code&gt;gr2m/merge-schedule-action@v1&lt;/code&gt; and tell it the merge method to use. I have set it to merge but you could use squash if you prefer. The &lt;code&gt;time_zone&lt;/code&gt; is t set to UTC by default, but can be any time zone you need.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; doesn’t need to be set, since GitHub will retrieve a &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; to use for the account.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to read more about triggering GitHub actions check out there documentation &lt;a href="https://docs.github.com/en/github-ae@latest/actions/using-workflows/events-that-trigger-workflows" rel="noopener noreferrer"&gt;https://docs.github.com/en/github-ae@latest/actions/using-workflows/events-that-trigger-workflows&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How to run the action
&lt;/h3&gt;

&lt;p&gt;Now that we have created the action, when you create a pull request, you need to add &lt;code&gt;/schedule YYYY-MM-DD&lt;/code&gt; to your pull request description. This Github Action will run on the schedule defined in the cron statement and check for PRs where the date matches and then deploy the code. If you need precise deployments you can use &lt;code&gt;/schedule 2019-12-31T00:00:00.000Z.&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to keep up to date with Tina?
&lt;/h2&gt;

&lt;p&gt;The best way to keep up with Tina is to subscribe to our newsletter, we send out updates every two weeks. Updates include new features, what we have been working on, blog posts you may have missed, and so much more!&lt;/p&gt;

&lt;p&gt;You can subscribe by following this link and entering your email: &lt;a href="https://tina.io/community/" rel="noopener noreferrer"&gt;https://tina.io/community/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tina Community Discord
&lt;/h3&gt;

&lt;p&gt;Tina has a community &lt;a href="https://discord.com/invite/zumN63Ybpf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; that is full of Jamstack lovers and Tina enthusiasts. When you join you will find a place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get help with issues&lt;/li&gt;
&lt;li&gt;Find the latest Tina news and sneak previews&lt;/li&gt;
&lt;li&gt;Share your project with Tina community, and talk about your experience&lt;/li&gt;
&lt;li&gt;Chat about the Jamstack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tina Twitter
&lt;/h3&gt;

&lt;p&gt;Our Twitter account (&lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt;) announces the latest features, improvements, and sneak peeks to Tina. We would also be psyched if you tagged us in projects you have built.&lt;/p&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Read-only tokens - Query Requests anytime</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Tue, 05 Apr 2022 13:56:09 +0000</pubDate>
      <link>https://dev.to/tinacms/read-only-tokens-query-requests-anytime-5571</link>
      <guid>https://dev.to/tinacms/read-only-tokens-query-requests-anytime-5571</guid>
      <description>&lt;p&gt;Read-only tokens allow you to query data from your project at any point in your application, whether that is on the server or on the client. Prior to Read-only tokens everything Tina did was through &lt;code&gt;getStaticProps&lt;/code&gt; or &lt;code&gt;getStaticPaths&lt;/code&gt;. This, for the most part, would handle most cases when using a headless CMS with an SSG. However as we move towards the 1.0 release of TinaCMS we want to be able to support more frameworks including React, Remix, and Gatsby. &lt;/p&gt;

&lt;h2&gt;
  
  
  Some use cases for Read-only tokens
&lt;/h2&gt;

&lt;p&gt;Below are some use cases for Read-only tokens&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server Side Rendering&lt;/li&gt;
&lt;li&gt;Client Side fetching&lt;/li&gt;
&lt;li&gt;Runtime server logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to use Read-only tokens?
&lt;/h2&gt;

&lt;p&gt;Before you start with Read-only tokens you will need to make sure the repository you are using has the data layer enabled. This is required for the read-only tokens to work and also be performant. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a token from the dashboard
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;a href="https://app.tina.io" rel="noopener noreferrer"&gt;Tina Cloud&lt;/a&gt; and click on the project you wish to add a token to, click on the "tokens" tab&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/graphql-docs/token-tab.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/graphql-docs/token-tab.png" alt="Tina cloud token tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, click "New Token" and fill out the fields required. The token name is how you can identify the token and "Git branches" is the list of branches separated by commas that the token has access to.&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/graphql-docs/create-new-token.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/graphql-docs/create-new-token.png" alt="Creating a new token in Tina Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, click "Create Token".&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/graphql-docs/final-token-page.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/graphql-docs/final-token-page.png" alt="Successful creation of a token in Tina Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ready for requests
&lt;/h3&gt;

&lt;p&gt;At this point you are now ready to make requests using the Read-only token. I have put together some examples of different use cases, they include SSR, CSR, SSG with fallback which should satisfy most use cases with Tina.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSR - Server Side Rendering content
&lt;/h3&gt;

&lt;p&gt;In most cases your content will be statically generated at build time, but on occasion you might need to use SSR in your Tina-powered app. It could be a page that isn’t powered by Tina but you are using our graphQL layer to power your whole application.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    getPostDocument(example.md) {
      data {
        title
        body
      }
  }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getServerSideProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://content.tinajs.io/content/&amp;lt;CLIENT_ID&amp;gt;/github/&amp;lt;BRANCH&amp;gt;&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// will be passed to the page component as props&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;Every time a user returns to this page, they will receive a freshly served page with the latest content from Tina.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSR - Client Side Rendering
&lt;/h3&gt;

&lt;p&gt;Client side rendering can be a great way to keep content on the page up to date, every-time someone visits a page. Tina content can be retrieved using your favorite http client such as fetch or axios.&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;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;useTina&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;tinacms/dist/edit-state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// This is a query you want.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
query ContentQuery($relativePath: String!) {
  get&amp;lt;CollectionName&amp;gt;Document(relativePath: $relativePath) {
    data {
      body
      title
    }
  }
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Variables used in the GraphQL query;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HelloWorld.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;function&lt;/span&gt; &lt;span class="nf"&gt;BlogPostPage&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;initalData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://content.tinajs.io/content/&amp;lt;ClientId&amp;gt;/github/&amp;lt;Branch&amp;gt;&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;ReadOnlyToken&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nx"&gt;query&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;variables&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="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTina&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initalData&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;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;data&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;BlogPostPage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see for this example we are using useEffect to fetch the data from Tina using our read-only token. The URL you see is powered by your &lt;code&gt;clientId&lt;/code&gt; and GitHub branch of choice. We then set the data to use &lt;code&gt;useTina&lt;/code&gt; and present the data through the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSG with Fallback
&lt;/h3&gt;

&lt;p&gt;Up until now most Tina users use  &lt;code&gt;fallback: blocking&lt;/code&gt; for creating new pages with Tina.&lt;/p&gt;

&lt;p&gt;This comes with issues: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You no longer have fallback pages by default (404 pages), any navigation will be served even if it’s a blank page.&lt;/li&gt;
&lt;li&gt;You need a way to handle when there is data, no data or no page. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Read-only tokens this has a lot less developer friction and a better user experience, we can break the getStaticsProps code into  three paths. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data is returned (this is the code you’ve had before)&lt;/li&gt;
&lt;li&gt;Data is not returned, so fetch it using read-only tokens. If it’s there, return it.&lt;/li&gt;
&lt;li&gt;Data is not returned, data is not returned using read-only tokens, so return a fallback page.
&lt;/li&gt;
&lt;/ol&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;staticRequest&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="s1"&gt;tinacms&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`query getPost($relativePath: String!) {
    getPostDocument(relativePath: $relativePath) {
      data {
        title
        body
      }
    }
  }
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStaticProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.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;let&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use the local client at build time&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;staticRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// swallow errors related to document creation&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// if there isn't data set the error flag&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use read-only tokens to get live data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://content.tinajs.io/content/&amp;lt;CLIENT_ID&amp;gt;/github/&amp;lt;BRANCH&amp;gt;&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&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;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// if there is no data set the notFound true (This returns 404&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above does a lot of different things, so let us break it down into the sections stated previously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The original request which produces data, will return data, query and variables. &lt;/li&gt;
&lt;li&gt;If there is no data returned, we set the error flag to true. If the error flag is true, we attempt to use read-only tokens to retrieve your data and return it to be displayed to the user, or to the content editor. &lt;/li&gt;
&lt;li&gt;If there is no data returned and the read-only token returns no data, we return &lt;code&gt;notFound: true&lt;/code&gt; (this is a special flag for Next.js). This flag will return your 404 error page as well as &lt;code&gt;404&lt;/code&gt; in the status code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to keep up to date with Tina?
&lt;/h2&gt;

&lt;p&gt;The best way to keep up with Tina is to subscribe to our newsletter, we send out updates every two weeks. Updates include new features, what we have been working on, blog posts you may have missed, and so much more!&lt;/p&gt;

&lt;p&gt;You can subscribe by following this link and entering your email: &lt;a href="https://tina.io/community/" rel="noopener noreferrer"&gt;https://tina.io/community/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tina Community Discord
&lt;/h3&gt;

&lt;p&gt;Tina has a community &lt;a href="https://discord.com/invite/zumN63Ybpf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; that is full of Jamstack lovers and Tina enthusiasts. When you join you will find a place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get help with issues&lt;/li&gt;
&lt;li&gt;Find the latest Tina news and sneak previews&lt;/li&gt;
&lt;li&gt;Share your project with Tina community, and talk about your experience&lt;/li&gt;
&lt;li&gt;Chat about the Jamstack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tina Twitter
&lt;/h3&gt;

&lt;p&gt;Our Twitter account (&lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt;) announces the latest features, improvements, and sneak peeks to Tina. We would also be psyched if you tagged us in projects you have built.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Tina Data Layer: Performant Editing</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Wed, 30 Mar 2022 13:19:54 +0000</pubDate>
      <link>https://dev.to/tinacms/tina-data-layer-performant-editing-3n8a</link>
      <guid>https://dev.to/tinacms/tina-data-layer-performant-editing-3n8a</guid>
      <description>&lt;p&gt;Tina has worked on the premise of direct interaction with Tina's GraphQL API and GitHub’s API. While this is a perfectly acceptable option, it does reduce performance slightly due to the nature of sending and retrieving data as “new” each time. &lt;/p&gt;

&lt;p&gt;The Tina team recently introduced a new optional data layer that sits between Tina and GitHub. In the future, this will be our default offering once it is out of the experimental stage. Our data layer buffers the requests between Tina and GitHub, increasing performance while editing your content. This blog post is going to explain how it works, what it does and what we have planned for the future!&lt;/p&gt;

&lt;h2&gt;
  
  
  How to enable the Data Layer on your project
&lt;/h2&gt;

&lt;p&gt;We made enabling and using the Data Layer with minimal development required. In fact you can enable the data layer by passing &lt;code&gt;--experimentalData&lt;/code&gt; as a command line flag. The easiest way to make sure this happens is by editing your &lt;code&gt;package.json&lt;/code&gt; script. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "dev": "yarn tinacms server:start -c \"next dev\"",
    "build": "yarn tinacms server:start -c \"next build\"",
    "start": "yarn tinacms server:start -c \"next start\" --experimentalData",
  },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once the flag is added and the CLI has been run, Tina will update the generated schema letting Tina know you want to use the Data Layer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once you have generated the Schema, you will need to commit the changes to GitHub for it to start working on the project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does the Data Layer do to increase Performance?
&lt;/h2&gt;

&lt;p&gt;Once the Data Layer is enabled on your project, we will automatically synchronize a copy of your repository with our secure cloud database. After Tina has run the initial indexing of your repository, Tina will automatically index new or updated content. This is done behind the scenes and you won’t notice that we are doing this, except the increase in performance when editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  When we might do a full re-index
&lt;/h3&gt;

&lt;p&gt;In some cases we might have to fully re-index your project. This usually happens when the schema of your project has changed. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Changes to &lt;code&gt;.tina\schema.ts&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Changes to the path to &lt;a href="https://tina.io/docs/tina-cloud/faq/#does-tina-cloud-work-with-monorepos" rel="noopener noreferrer"&gt;.tina&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some things to note
&lt;/h3&gt;

&lt;p&gt;This is still an experimental feature and the following things should be considered before enabling it on your project: &lt;/p&gt;

&lt;p&gt;1. The indexing process isn’t exposed to the end user, which means it is possible that queries made during the indexing process could return incomplete results.&lt;/p&gt;

&lt;p&gt;2. GitHub has a API request limitation of 5000 requests per repository per hour. If you have an extremely large project you could hit this rate limit. If you have a repository with more than 1500 items, please do not activate this feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The future
&lt;/h2&gt;

&lt;p&gt;The team at Tina has plans for the following highly requested features as the Data Layer matures out of its experimental phase. In fact we have already begun working on some of the features mentioned below, so stay tuned! &lt;/p&gt;

&lt;h3&gt;
  
  
  More complex and advanced queries
&lt;/h3&gt;

&lt;p&gt;The Data Layer opens up our GraphQL layer to be even more powerful, in the future Tina plans to offer: &lt;/p&gt;

&lt;p&gt;This means you will be able to reduce some of your calls if you don’t need a full data set. A good example of this would be where you only need 3 blog posts for a features section. In the current Tina integration, you need to retrieve all of the posts and filter them down after the fact. In the future you won’t need to do this. &lt;/p&gt;

&lt;h3&gt;
  
  
  Referential Integrity
&lt;/h3&gt;

&lt;p&gt;With the introduction of the Data Layer it allows us to now be able to offer referential integrity. This will stop a content writer from accidentally making changes to content that could break other content or an entire site. The biggest benefit to a content user will be the ability to rename files or even delete content without breaking any existing references.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>mdx</category>
    </item>
    <item>
      <title>From CMS To Contextual Editing</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Thu, 20 Jan 2022 16:38:09 +0000</pubDate>
      <link>https://dev.to/tinacms/new-year-new-cms-51ca</link>
      <guid>https://dev.to/tinacms/new-year-new-cms-51ca</guid>
      <description>&lt;p&gt;Tina allows you as a developer to create an amazing editing experience. By default the editing experience is a more traditional CMS, where you login to a specific URL and you edit your content without being able to see the content till after you finish your changes. But, if you are looking for a more transparent real-time editing experience, Tina has a superpower, called Contextual Editing. Just as it sounds, you get instant feedback on the page as well as being able to preview the changes before publishing live to your site. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fq_100%2Fv1647436971%2Fblog-media%2Fcms-to-contextual%2FTinaSuper.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fq_100%2Fv1647436971%2Fblog-media%2Fcms-to-contextual%2FTinaSuper.gif" alt="Example of Tina Context editing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting our project started
&lt;/h2&gt;

&lt;p&gt;For this blog post we are going to use Tina’s example which is located in the Next.js official examples and allows us to use &lt;code&gt;create-next-app&lt;/code&gt;.This example is based upon Next.js blog starter which is a markdown blog.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the source code in the Next.js examples directory, &lt;a href="https://github.com/vercel/next.js/tree/canary/examples/cms-tina" rel="noopener noreferrer"&gt;https://github.com/vercel/next.js/tree/canary/examples/cms-tina&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;## Create our new Tina site&lt;/span&gt;

npx create-next-app &lt;span class="nt"&gt;--example&lt;/span&gt; cms-tina tina-contextual-editing

&lt;span class="c"&gt;## Move into our app.&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;tina-contextual-editing

&lt;span class="c"&gt;## Open with your favorite editor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you want to learn how this example was created check out the blog post that covers &lt;a href="https://tina.io/blog/tina-cms-get-started/" rel="noopener noreferrer"&gt;getting started with Tina&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does the Tina code do?
&lt;/h2&gt;

&lt;p&gt;Before you add any code to this example, I want to cover how Tina is integrated and how it works. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.tina&lt;/code&gt; folder
&lt;/h3&gt;

&lt;p&gt;You will find a &lt;code&gt;.tina&lt;/code&gt; folder in the root of the project, this is the heart and brain of Tina in any project. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;schema.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The schema file contains two important pieces of code &lt;code&gt;defineSchema&lt;/code&gt; and &lt;code&gt;defineConfig&lt;/code&gt;. The &lt;code&gt;defineSchema&lt;/code&gt; allows you to define the shape of your content. If you have used a more traditional CMS before you may have done this via a GUI. However, given how Tina is tightly coupled with Git and we treat the filesystem as the “source of truth”, we take the approach of “content-modeling as code”.&lt;/p&gt;

&lt;p&gt;If you look at the current defined schema, you will see that each one of the fields is related to the front matter contained within any of the posts in the &lt;code&gt;_posts&lt;/code&gt; folder. &lt;/p&gt;

&lt;p&gt;Moving on to the &lt;code&gt;defineConfig&lt;/code&gt;, the &lt;code&gt;defineConfig&lt;/code&gt; tells your project where the content is requested from, what branch to use, and any configuration to TinaCMS itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;components&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;components&lt;/code&gt; folder inside the &lt;code&gt;.tina&lt;/code&gt; holds both our &lt;code&gt;TinaProvider&lt;/code&gt; and &lt;code&gt;DynamicTinaProvider&lt;/code&gt;; these two components wrap your application with the power of Tina. We will be heading back here later for some updates. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;_generated__&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This folder holds all the auto generated files from Tina, if you open this up you will see files that contain queries, fragments and types. You won’t need to make changes here but it’s good to know what you might find in there. &lt;/p&gt;

&lt;p&gt;Before we add contextual editing, go ahead and launch the application using &lt;code&gt;yarn tina-dev&lt;/code&gt; and navigate to &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:3000/&lt;/code&gt;&lt;/a&gt;. You will notice that it is a static blog. Feel free to navigate around and get a feel for the blog. &lt;/p&gt;

&lt;p&gt;Now if you navigate to &lt;a href="http://localhost:3000/admin" rel="noopener noreferrer"&gt;http://localhost:3000/admin&lt;/a&gt; you will be presented with a screen to login, if you login you will land on the CMS dashboard. Selecting a collection on the left will bring you to a screen with all the current files in that space. Then selecting a file will allow you to edit it as you see fit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fc_scale%2Cw_1174%2Fv1646412458%2Fblog-media%2Fgetting-started-tina-admin%2Fexample.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fc_scale%2Cw_1174%2Fv1646412458%2Fblog-media%2Fgetting-started-tina-admin%2Fexample.gif" alt="Tina CMS Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Contextual Editing.
&lt;/h2&gt;

&lt;p&gt;Now you have an understanding of both how the CMS works, and how the code behind is laid out we can start working on adding contextual editing to our project. What do we need to do to make contextual editing work? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Update &lt;code&gt;getStaticPaths&lt;/code&gt; and &lt;code&gt;getStaticProps&lt;/code&gt; to use Tina’s graphql layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the page to use &lt;code&gt;useTina&lt;/code&gt; and power the project props&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the TinaCMS configuration&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating the getStaticPaths query
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;getStaticPaths&lt;/code&gt; query is going to need to know where all of our markdown files are located. With our current schema you have the option to use &lt;code&gt;getPostsList&lt;/code&gt;, which will provide a list of all posts in our &lt;code&gt;_posts&lt;/code&gt; folder. Make sure your local server is running and navigate to &lt;a href="http://localhost:4001/altair" rel="noopener noreferrer"&gt;http://localhost:4001/altair&lt;/a&gt; and select the Docs button. The Docs button gives you the ability to see all the queries possible and the variables returned:&lt;/p&gt;

&lt;p&gt;So based upon the &lt;code&gt;getPostsList&lt;/code&gt; we will want to query the &lt;code&gt;sys&lt;/code&gt; which is the filesystem and retrieve the &lt;code&gt;filename&lt;/code&gt;, which will return all the filenames without the extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;query &lt;span class="o"&gt;{&lt;/span&gt;
  getPostsList &lt;span class="o"&gt;{&lt;/span&gt;
    edges &lt;span class="o"&gt;{&lt;/span&gt;
      node &lt;span class="o"&gt;{&lt;/span&gt;
        sys &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="nb"&gt;basename&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this query in the GraphQL client you will see the following returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"data"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"getPostsList"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"edges"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"node"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sys"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
              &lt;span class="s2"&gt;"filename"&lt;/span&gt;: &lt;span class="s2"&gt;"dynamic-routing"&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"node"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sys"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
              &lt;span class="s2"&gt;"filename"&lt;/span&gt;: &lt;span class="s2"&gt;"hello-world"&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"node"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sys"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
              &lt;span class="s2"&gt;"filename"&lt;/span&gt;: &lt;span class="s2"&gt;"preview"&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding this query to our Blog.&lt;/p&gt;

&lt;p&gt;The NextJS starter blog is served on the dynamic route &lt;code&gt;/pages/posts/[slug].js&lt;/code&gt;. When you open the file you will see a function called &lt;code&gt;getStaticPaths&lt;/code&gt; at the bottom of the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;async &lt;span class="k"&gt;function &lt;/span&gt;getStaticPaths&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Remove all the code inside of this function and we can update it to use our own code. The first step is to add an import to the top of the file to be able interact with our graphql, and remove the &lt;code&gt;getPostBySlug&lt;/code&gt; and &lt;code&gt;getAllPosts&lt;/code&gt; imports we won’t be using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;//other imports
.....
&lt;span class="gd"&gt;- import { getPostBySlug, getAllPosts } from '../../lib/api'
&lt;/span&gt;&lt;span class="gi"&gt;+ import { staticRequest } from "tinacms";
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside of the &lt;code&gt;getStaticPaths&lt;/code&gt; function we can construct our request to our content-api. When making a request we expect a &lt;code&gt;query&lt;/code&gt; or &lt;code&gt;mutation&lt;/code&gt; and then &lt;code&gt;variables&lt;/code&gt; if needed to be passed to the query, here is an example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;staticRequest({&lt;br&gt;
  query: '...', // our query&lt;br&gt;
}),&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;What does&lt;/em&gt; &lt;code&gt;staticRequest&lt;/code&gt; do?”&lt;/p&gt;

&lt;p&gt;It’s just a helper function which supplies a query to your locally-running GraphQL server, which is started on port &lt;code&gt;4001&lt;/code&gt;. You can just as easily use &lt;code&gt;fetch&lt;/code&gt; or an http client of your choice.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;getPostsList&lt;/code&gt; query from earlier to build our dynamic routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;async &lt;span class="k"&gt;function &lt;/span&gt;getStaticPaths&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const postsListData &lt;span class="o"&gt;=&lt;/span&gt; await staticRequest&lt;span class="o"&gt;({&lt;/span&gt;
    query: &lt;span class="sb"&gt;`&lt;/span&gt;
      query &lt;span class="o"&gt;{&lt;/span&gt;
        getPostsList &lt;span class="o"&gt;{&lt;/span&gt;
          edges &lt;span class="o"&gt;{&lt;/span&gt;
            node &lt;span class="o"&gt;{&lt;/span&gt;
            sys &lt;span class="o"&gt;{&lt;/span&gt;
              filename
              &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="sb"&gt;`&lt;/span&gt;,
  &lt;span class="o"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    paths: postsListData.getPostsList.edges.map&lt;span class="o"&gt;(&lt;/span&gt;edge &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;({&lt;/span&gt;
      params: &lt;span class="o"&gt;{&lt;/span&gt; slug: edge.node.sys.filename &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;}))&lt;/span&gt;,
    fallback: &lt;span class="nb"&gt;false&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quick break down of &lt;code&gt;getStaticPaths&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getStaticPaths&lt;/code&gt; code takes the graphql query we created, because it does not require any &lt;code&gt;variables&lt;/code&gt; we can send down an empty object. In the return functionality we map through each item in the &lt;code&gt;postsListData.getPostsList&lt;/code&gt; and create a slug for each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating getStaticProps query
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating the query
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;getStaticProps&lt;/code&gt; query is going to deliver all the content to the blog, which is how it works currently with the code provided by Next.js. When we use the GraphQL API we will both deliver the content and give the content team the ability to edit it right in the browser.&lt;/p&gt;

&lt;p&gt;We need to query the following things from our content api:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Excerpt&lt;/li&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Cover Image&lt;/li&gt;
&lt;li&gt;OG Image data&lt;/li&gt;
&lt;li&gt;Author Data&lt;/li&gt;
&lt;li&gt;Body content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Creating our Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using our local graphql client we can query the &lt;code&gt;getPostDocument&lt;/code&gt; using the path to the blog post in question, below is the skeleton of what we need to fill out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;query BlogPostQuery&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;: String!&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  getPostsDocument&lt;span class="o"&gt;(&lt;/span&gt;relativePath: &lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# data from our posts.&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now fill in the relevant fields we need to query, take special note of both &lt;code&gt;author&lt;/code&gt; and &lt;code&gt;ogImage&lt;/code&gt; which are grouped so they get queried as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;author &lt;span class="o"&gt;{&lt;/span&gt;
  name
  picture
&lt;span class="o"&gt;}&lt;/span&gt;
ogImage &lt;span class="o"&gt;{&lt;/span&gt;
  url
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have filled in all the fields you should have a query that looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;query BlogPostQuery&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;: String!&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  getPostsDocument&lt;span class="o"&gt;(&lt;/span&gt;relativePath: &lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    data &lt;span class="o"&gt;{&lt;/span&gt;
      title
      excerpt
      &lt;span class="nb"&gt;date
      &lt;/span&gt;coverImage
      author &lt;span class="o"&gt;{&lt;/span&gt;
        name
        picture
      &lt;span class="o"&gt;}&lt;/span&gt;
      ogImage &lt;span class="o"&gt;{&lt;/span&gt;
        url
      &lt;span class="o"&gt;}&lt;/span&gt;
      body
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would like to test this out, you can add the following to the variables section at the bottom &lt;code&gt;{"relativePath": "hello-world.md"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding our query to our blog&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remove all the code inside of the &lt;code&gt;getStaticProps&lt;/code&gt; function and we can update it to use our own code. Since these pages are dynamic, we’ll want to use the values we returned from &lt;code&gt;getStaticPaths&lt;/code&gt; in our query. We’ll destructure &lt;code&gt;params&lt;/code&gt; to obtain the &lt;code&gt;slug&lt;/code&gt;, using it as a &lt;code&gt;relativePath&lt;/code&gt;. As you’ll recall the “Blog Posts” collection stores files in a folder called &lt;code&gt;_posts&lt;/code&gt;, so we want to make a request for the relative path of our content. Meaning for the file located at &lt;code&gt;_posts/hello-world.md&lt;/code&gt;, we only need to supply the relative portion of &lt;code&gt;hello-world.md&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;const getStaticProps &lt;span class="o"&gt;=&lt;/span&gt; async &lt;span class="o"&gt;({&lt;/span&gt; params &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; slug &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; params
  // Ex. &lt;span class="sb"&gt;`&lt;/span&gt;slug&lt;span class="sb"&gt;`&lt;/span&gt; is &lt;span class="sb"&gt;`&lt;/span&gt;hello-world&lt;span class="sb"&gt;`&lt;/span&gt;
  const variables &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; relativePath: &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;slug&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.md&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  // ...
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also want to call &lt;code&gt;staticRequest&lt;/code&gt; to load our data for our specific page. You’ll also notice that we will return the query &amp;amp; variables from getStaticProps. We’ll be using these values within the TinaCMS frontend.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;import { staticRequest } from 'tinacms'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So the full query should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;const getStaticProps &lt;span class="o"&gt;=&lt;/span&gt; async &lt;span class="o"&gt;({&lt;/span&gt; params &lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  const &lt;span class="o"&gt;{&lt;/span&gt; slug &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; params
  const variables &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; relativePath: &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;slug&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.md&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  const data &lt;span class="o"&gt;=&lt;/span&gt; await staticRequest&lt;span class="o"&gt;({&lt;/span&gt;
    query: query,
    variables: variables,
  &lt;span class="o"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    props: &lt;span class="o"&gt;{&lt;/span&gt;
      data,
      variables,
    &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have noticed that our query isn’t there but we are referencing it. This is because we want to be able to reuse the query for our &lt;code&gt;useTina&lt;/code&gt; hook. At the top of our file, after the imports you can add the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`query BlogPostQuery($relativePath: String!) {
  getPostsDocument(relativePath: $relativePath) {
    data {
      title
      excerpt
      date
      coverImage
      author {
        name
        picture
      }
      ogImage {
        url
      }
      body
    }
  }
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding &lt;code&gt;useTina&lt;/code&gt; hook
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;useTina&lt;/code&gt; hook is used to make a piece of Tina content editable. It is code-split so in production, this hook will pass through the data value. When you are in edit mode, it registers an editable form in the sidebar. &lt;/p&gt;

&lt;h3&gt;
  
  
  Adding imports
&lt;/h3&gt;

&lt;p&gt;The first thing that needs to be done is import useTina, useEffect and useState. We can also remove a few imports that we will no longer be using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import &lt;span class="o"&gt;{&lt;/span&gt;useState, useEffect&lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'react'&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt;useTina&lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'tinacms/dist/edit-state'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Changing our props and adding UseTina
&lt;/h3&gt;

&lt;p&gt;We are going to update our props from &lt;code&gt;({ post, morePosts, preview })&lt;/code&gt; to &lt;code&gt;(props)&lt;/code&gt;. We are going to pass the props into our &lt;code&gt;useTina&lt;/code&gt; hook. Now that has been updated with the props coming in we can use &lt;code&gt;useTina&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- export default function Post({ post, morePosts, preview }) {
&lt;/span&gt;&lt;span class="gi"&gt;+ export default function Post( props ) {
&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="gi"&gt;+  const { data } = useTina({
+    query,
+    variables: props.variables,
+    data: props.data,
+  })
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we are reusing our query from before and passing the variables, and data to &lt;code&gt;useTina&lt;/code&gt;.  This now means we can power our site using contextual editing. Congratulations your site now has superpowers! &lt;/p&gt;

&lt;h3&gt;
  
  
  Update our elements to use Tina data
&lt;/h3&gt;

&lt;p&gt;Now we have access to our Tina powered data we can go through and update all of our elements to use Tina. You can replace each of the &lt;code&gt;post.&lt;/code&gt; with &lt;code&gt;data.getPostsDocument.data.&lt;/code&gt; and then replace the &lt;code&gt;!post?.slug&lt;/code&gt; with &lt;code&gt;!props.variables.relativePath&lt;/code&gt; when you are finished your return code should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;const router = useRouter()
&lt;/span&gt;&lt;span class="gd"&gt;-  if (!router.isFallback &amp;amp;&amp;amp; !post?.slug) {
&lt;/span&gt;&lt;span class="gi"&gt;+  if (!router.isFallback &amp;amp;&amp;amp; !props.variables.relativePath) {
&lt;/span&gt;    return &amp;lt;ErrorPage statusCode={404} /&amp;gt;
  }
  return (
&lt;span class="gd"&gt;-   &amp;lt;Layout preview={preview}&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+   &amp;lt;Layout preview={false}&amp;gt;
&lt;/span&gt;      &amp;lt;Container&amp;gt;
        &amp;lt;Header /&amp;gt;
        {router.isFallback ? (
          &amp;lt;PostTitle&amp;gt;Loading…&amp;lt;/PostTitle&amp;gt;
        ) : (
          &amp;lt;&amp;gt;
            &amp;lt;article className="mb-32"&amp;gt;
              &amp;lt;Head&amp;gt;
                &amp;lt;title&amp;gt;
&lt;span class="gd"&gt;-                  {post.title} | Next.js Blog Example with {CMS_NAME}
&lt;/span&gt;&lt;span class="gi"&gt;+                  {data.getPostsDocument.data.title} | Next.js Blog Example with {CMS_NAME}
&lt;/span&gt;                &amp;lt;/title&amp;gt;
&lt;span class="gd"&gt;-                &amp;lt;meta property="og:image" content={post.ogImage.url} /&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+                &amp;lt;meta property="og:image" content={data.getPostsDocument.data.ogImage.url} /&amp;gt;
&lt;/span&gt;              &amp;lt;/Head&amp;gt;
              &amp;lt;PostHeader
&lt;span class="gd"&gt;-                title={post.title}
-                coverImage={post.coverImage}
-                date={post.date}
-                author={post.author}
&lt;/span&gt;&lt;span class="gi"&gt;+                title={data.getPostsDocument.data.title}
+                coverImage={data.getPostsDocument.data.coverImage}
+                date={data.getPostsDocument.data.date}
+                author={data.getPostsDocument.data.author}
&lt;/span&gt;              /&amp;gt;
&lt;span class="gd"&gt;-             &amp;lt;PostBody content={post.content} /&amp;gt;              
&lt;/span&gt;&lt;span class="gi"&gt;+             &amp;lt;PostBody content={data.getPostsDocument.data.body} /&amp;gt;
&lt;/span&gt;            &amp;lt;/article&amp;gt;
          &amp;lt;/&amp;gt;
        )}
      &amp;lt;/Container&amp;gt;
    &amp;lt;/Layout&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The final piece of Contextual Editing.
&lt;/h3&gt;

&lt;p&gt;One piece of code that the original was doing was taking the markdown and turning it into HTML, inside of the &lt;code&gt;getStaticPriops&lt;/code&gt;. We currently aren’t doing that with our body and just returning the string. Due to the nature of contextual editing, we need to make sure that we are always passing the latest content through the &lt;code&gt;markdownToHtml&lt;/code&gt; function provided by the team at Next.js. This is where &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; come in, first create a content variable that is track by a state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note you could use another markdown to html package that would not require this, but in this example we want to reuse as much of the original code as possible to show how you could integrate with minimal code replacement.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in useEffect we are going to parse our body to the markdownToHtml code provided by the Next.js team and set it to content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markdownToHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPostsDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="nf"&gt;parseMarkdown&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPostsDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all we need to do is update our post body content from &lt;code&gt;data.getPostsDocument.data.body&lt;/code&gt; to &lt;code&gt;content&lt;/code&gt; . If you go ahead and test your application now, you can now edit on the page! &lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Now that you have contextual editing here are a few things you can explore with Tina&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tina.io/docs/media-cloudinary/" rel="noopener noreferrer"&gt;Media management through Cloudinary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tina.io/docs/tinacms-context/#the-routemappingplugin" rel="noopener noreferrer"&gt;Route Mapping (connect the CMS to contextual editing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tina.io/docs/tina-cloud/data-layer/" rel="noopener noreferrer"&gt;Data layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tina.io/docs/graphql/read-only-tokens/" rel="noopener noreferrer"&gt;Read-only tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to keep up to date with Tina?
&lt;/h2&gt;

&lt;p&gt;The best way to keep up with Tina is to subscribe to our newsletter, we send out updates every two weeks. Updates include new features, what we have been working on, blog posts you may of missed and so much more! &lt;/p&gt;

&lt;p&gt;You can subscribe by following this link and entering your email: &lt;a href="https://tina.io/community/" rel="noopener noreferrer"&gt;https://tina.io/community/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tina Community Discord
&lt;/h3&gt;

&lt;p&gt;Tina has a community &lt;a href="https://discord.com/invite/zumN63Ybpf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; that is full of Jamstack lovers and Tina enthusiasts. When you join you will find a place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get help with issues&lt;/li&gt;
&lt;li&gt;Find the latest Tina news and sneak previews&lt;/li&gt;
&lt;li&gt;Share your project with Tina community, and talk about your experience&lt;/li&gt;
&lt;li&gt;Chat about the Jamstack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tina Twitter
&lt;/h3&gt;

&lt;p&gt;Our Twitter account (&lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt;) announces the latest features, improvements, and sneak peeks to Tina. We would also be psyched if you tagged us in projects you have built.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>mdx</category>
    </item>
    <item>
      <title>New Year, New CMS?</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Thu, 06 Jan 2022 15:29:21 +0000</pubDate>
      <link>https://dev.to/tinacms/new-year-new-cms-38k0</link>
      <guid>https://dev.to/tinacms/new-year-new-cms-38k0</guid>
      <description>&lt;p&gt;Here we are, the new year is upon us, and if you are like us, you have set some New Year’s resolutions for yourself. We are big fans of the fresh start and self-improvement that comes along with this time of year. Have you set any resolutions, maybe building or rebuilding your own personal site, starting a new business, or giving your portfolio a new lick of pain?. That means that you will need a CMS to handle all the content that drives your site and we think Tina can fill the role in some unique ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tina?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Git Backed
&lt;/h3&gt;

&lt;p&gt;Traditionally when you sign up for a CMS, your content is locked into the vendor. This means if you decide that the CMS is not for you, you have to find a way to export that data and import it into your new CMS. That is not what Tina is about, we don’t want to hold you or your content hostage. In fact, Tina doesn’t store any of your data, it is stored in a GitHub repository that you own. Yep, that is right, you own and control it all. In addition to feeling secure that you own your own content, there are many practical advantages to this approach such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to track when and what changed in your project&lt;/li&gt;
&lt;li&gt;CI / CD support&lt;/li&gt;
&lt;li&gt;No vendor lock in&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contextual Editing
&lt;/h3&gt;

&lt;p&gt;Tina is different from a traditional headless CMS, where you enter your data into a form with no context of how it will behave or look on your site. You then have to kick off a build and navigate to your site to see the changes. &lt;br&gt;
When you use Tina, the content is edited using a sidebar on your site, you get to see the changes in real time as you make them. This allows you to see exactly what you are editing or creating and how it will look and behave. No more saves, previews, refreshes after every few edits. Once you are happy with the changes, you can hit save and Tina will commit it directly to your GitHub repository and the rebuild process will begin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native MDX Support
&lt;/h3&gt;

&lt;p&gt;Tina can support MDX out of the box, this means you can create reusable components and Tina can provide an easy way for anyone editing or creating content to add them to the page, no matter how experienced they are.&lt;/p&gt;

&lt;p&gt;Other headless CMSs require the users who are creating the content to remember the Component names, use the correct syntax when using them, as well as transform the data using mdx-remote or something similar. This is fine for an experienced developer but if you want to bring on guests who have zero experience it is a steep learning curve. Seriously, ask your editor friends about this, see the rage and/or fear in their eyes.&lt;/p&gt;

&lt;p&gt;We include a button that will allow anyone to click, select the user-friendly named component, and add it to the page. They can then dynamically edit that component with the correct text, images, or styles that you defined.&lt;br&gt;
Tina also doesn’t require you to transform the MDX or hydrate your components, making it easier to integrate then traditional CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to get started in under five minutes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tina Quickstart
&lt;/h3&gt;

&lt;p&gt;Our &lt;a href="https://app.tina.com/quickstart" rel="noopener noreferrer"&gt;Tina QuickStart&lt;/a&gt; flow is a web based way to get started with Tina, it allows you to choose from one of our starters (Tina Barebones, Tina Documentation Starter, Tina Cloud Starter) and deploy directly to Vercel.&lt;/p&gt;

&lt;p&gt;This approach allows you to see how Tina works in a production deployment almost immediately . This is great for getting to know what Tina can do, how Tina works,and show it to others such as your content team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390729%2Fblog-media%2Fnew-year-new-cms%2Ftina-quickstart.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390729%2Fblog-media%2Fnew-year-new-cms%2Ftina-quickstart.gif" alt="Tina Quickstart example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npx create-tina-app&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;create-tina-app&lt;/code&gt; allows you to work locally with one of our starters, this allows you to see how all the code behind the scenes is working before you decide that Tina is right for you.&lt;/p&gt;

&lt;p&gt;To use the &lt;code&gt;create-tina-app&lt;/code&gt; you will need &lt;code&gt;Node 14+&lt;/code&gt; .This doesn't require you to already have an application and will create a new project and directory and allow you to start developing locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390724%2Fblog-media%2Fnew-year-new-cms%2Fcreate-tina-app.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390724%2Fblog-media%2Fnew-year-new-cms%2Fcreate-tina-app.gif" alt="Create Tina App Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tina CLI
&lt;/h3&gt;

&lt;p&gt;Tina CLI (&lt;code&gt;npx @tincms/cli@latest init&lt;/code&gt;) allows you to add Tina to an existing Next.js application. When using the CLI we will take care of the important pieces including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding all the Tina dependencies&lt;/li&gt;
&lt;li&gt;Setting up a .tina folder with a basic schema&lt;/li&gt;
&lt;li&gt;Creating a demo directory with an example Tina powered page&lt;/li&gt;
&lt;li&gt;Creating an admin route&lt;/li&gt;
&lt;li&gt;Ensuring Tina best practices are used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the Tina CLI allows you to add Tina and selectively integrate it into your existing Next.js application. This allows you to keep your established site and slowly bring the power of Tina to your editors and content team. Though once you use it, we are not sure how slowly you will want to move. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390724%2Fblog-media%2Fnew-year-new-cms%2Ftina-cli.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fvideo%2Fupload%2Fv1641390724%2Fblog-media%2Fnew-year-new-cms%2Ftina-cli.gif" alt="Tina CLI Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can you keep up to date with Tina?
&lt;/h2&gt;

&lt;p&gt;You know that you will want to be part of this creative, innovative, supportive community of developers (and even some editors and designers) who are experimenting and implementing Tina daily. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tina Community Discord
&lt;/h3&gt;

&lt;p&gt;Tina has a community &lt;a href="https://discord.com/invite/zumN63Ybpf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; that is full of Jamstack lovers and Tina enthusiasts. When you join you will find a place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To get help with issues&lt;/li&gt;
&lt;li&gt;Find the latest Tina news and sneak previews&lt;/li&gt;
&lt;li&gt;Share your project with Tina community, and talk about your experience&lt;/li&gt;
&lt;li&gt;Chat about the Jamstack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tina Twitter
&lt;/h3&gt;

&lt;p&gt;Our Twitter account (&lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt;) announces the latest features, improvements, and sneak peeks to Tina. We would also be psyched if you tagged us in projects you have built.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cms</category>
      <category>mdx</category>
    </item>
    <item>
      <title>Tina now Supports MDX</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Tue, 26 Oct 2021 17:34:02 +0000</pubDate>
      <link>https://dev.to/tinacms/tina-now-supports-mdx-2jki</link>
      <guid>https://dev.to/tinacms/tina-now-supports-mdx-2jki</guid>
      <description>&lt;p&gt;The team at Tina is dedicated to revolutionizing the CMS space. We were the first to offer contextual editing in real-time which enabled teams to be more productive. Now we are introducing the world’s first UI editor for MDX. This empowers content teams to add components to a page with a single click. &lt;/p&gt;

&lt;p&gt;You can see our starter repository on our &lt;a href="https://github.com/tinacms/tina-docs-starter" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or select &lt;code&gt;Documentation Starter&lt;/code&gt; when going through our &lt;a href="https://app.tina.io/quickstart?utm_source=blog&amp;amp;utm_medium=link&amp;amp;utm_campaign=mdx_announcement" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MDX?
&lt;/h2&gt;

&lt;p&gt;MDX provides the ability to write JSX into Markdown files, which gives developers the ability to create content that is dynamic, interactive, and customizable. The problem with MDX, by nature, is you need to have some technical understanding to be able to both use and create content using MDX. This is where we have empowered non-technical members of your team to leverage reusable components with a single click. This means that content teams can move quickly and developers can focus on other projects. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to start using MDX?
&lt;/h2&gt;

&lt;p&gt;We have made the developer experience a breeze, you can check out our &lt;a href="https://tina.io/docs/mdx/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; or check out the steps to start using MDX :&lt;/p&gt;

&lt;h3&gt;
  
  
  Update to the latest version Tina
&lt;/h3&gt;

&lt;p&gt;You will need to update &lt;code&gt;tinacms&lt;/code&gt; and &lt;code&gt;@tinacms/cli&lt;/code&gt; to the latest versions to use the MDX features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create your components you want your content team to use.
&lt;/h3&gt;

&lt;p&gt;Create a component as you normally would and use props for any part you would like to be editable, below is an example of a callout component:&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="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Callout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CalloutWrapper&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CalloutLabel&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CalloutLabel&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CalloutText&lt;/span&gt; &lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CalloutText&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CalloutWrapper&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the fields for your components to your &lt;code&gt;schema.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rich-text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;templates&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Callout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Callout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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="p"&gt;],&lt;/span&gt;
          &lt;span class="nx"&gt;isBody&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the components to your Tina powered pages.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//imports&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;TinaMarkdown&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;tinacms/dist/rich-text&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="nx"&gt;Callout&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;../../blocks/callout-block&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;components&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Callout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Callout&lt;/span&gt; &lt;span class="nx"&gt;callout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&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;// Code removed for simplification&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TinaMarkdown&lt;/span&gt; &lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDocsDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TinaMarkdown&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ready to get started?
&lt;/h3&gt;

&lt;p&gt;You can see our starter repository on our &lt;a href="https://github.com/tinacms/tina-docs-starter" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or select &lt;code&gt;Documentation Starter&lt;/code&gt; when going through our &lt;a href="https://app.tina.io/quickstart?utm_source=blog&amp;amp;utm_medium=link&amp;amp;utm_campaign=mdx_announcement" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; flow.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>jamstack</category>
      <category>cms</category>
      <category>mdx</category>
    </item>
    <item>
      <title>Tina Cloud is Now in Public Beta</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Wed, 18 Aug 2021 15:19:35 +0000</pubDate>
      <link>https://dev.to/tinacms/tina-cloud-is-now-in-public-beta-2ao4</link>
      <guid>https://dev.to/tinacms/tina-cloud-is-now-in-public-beta-2ao4</guid>
      <description>&lt;p&gt;We are pleased to announce that Tina Cloud is in beta, and all of the core functionality is in place for your team to have a great content editing experience on Next.js sites! If you are excited as we are you can get started by &lt;a href="https://app.tina.io/register" rel="noopener noreferrer"&gt;signing up&lt;/a&gt;. But first, I'd love for you to stick around and hear about the team's vision, lessons learned, and what we have added. If you are new to Tina, &lt;a href="https://res.cloudinary.com/forestry-demo/video/upload/v1629294438/tina-io/Beta_Launch_Demo.mp4" rel="noopener noreferrer"&gt;check this quick demo&lt;/a&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Tina Cloud public Beta is a big milestone! All the core functionality is in place for teams to edit content on their Next.js sites. We’re just scratching the surface of what’s possible when you add an amazing content editing experience to content stored in your Git repository. Our goal is to fasten 10 times the experience for both developers and content editors — Scott Gallant, Founder and CEO&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;We learned a lot during the alpha stage, which allowed us to drive the product forward.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It took users a lot longer than we expected to make their first commit using Tina. At the beginning on average, it was over 8 hours. This signaled to us that even our starter took too long to set up.&lt;/li&gt;
&lt;li&gt;You had a lot of questions after using our starter, mostly surrounding content modeling, and our documentation didn't answer them. &lt;/li&gt;
&lt;li&gt;Tina could be used in Production on large sites, and both developers and content writers loved it.&lt;/li&gt;
&lt;li&gt;Creating a &lt;a href="https://discord.gg/njvZZYHj2Q" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; allowed us to give and receive real-time feedback, which allowed us to add features, fix bugs, and get people unstuck quickly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;p&gt;We believe that our product combines a fantastic developer and content creator experience into a single product. The update from alpha to beta is so large that I wanted to write a short paragraph about each part. Below is a list of each update, feel free to click on it to take you to the long-form update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better Documentation&lt;/li&gt;
&lt;li&gt;A new initialize command in the CLI&lt;/li&gt;
&lt;li&gt;Improving and adding guides&lt;/li&gt;
&lt;li&gt;Improving our Tina Starter&lt;/li&gt;
&lt;li&gt;
Media Manager &lt;/li&gt;
&lt;li&gt;Caching improvements&lt;/li&gt;
&lt;li&gt;Creating @tinacms/toolkit&lt;/li&gt;
&lt;li&gt;Vercel Integration&lt;/li&gt;
&lt;li&gt;Dashboard Overhaul&lt;/li&gt;
&lt;li&gt;Changes to content modeling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftina.io%2Fimg%2Ftina-laptop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftina.io%2Fimg%2Ftina-laptop.png" alt="" width="224" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get a website running with Tina Cloud in no time! &lt;a href="https://tina.io/guides/tina-cloud/starter/overview/" rel="noopener noreferrer"&gt;Quick Start Guide&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Tina
&lt;/h2&gt;

&lt;p&gt;We wanted to speed up getting Tina up and running, whether this was a newly bootstrapped Next.js application or your Production application. We introduced several things to improve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better documentation&lt;/li&gt;
&lt;li&gt;A tina init command&lt;/li&gt;
&lt;li&gt;New and improved guides&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better Documentation
&lt;/h3&gt;

&lt;p&gt;The documentation at Tina has been something that we wanted to improve as much as possible, we found people were unsure of Tina's concepts because we did not clearly explain them in the documentation. We spent time crafting documentation that gives a developer of any experience a better understanding of each part of Tina, how they work together and how to accomplish specific tasks. &lt;/p&gt;

&lt;p&gt;We also moved and created new navigation menus to better convey the intent of a piece of documentation, for example, if you were looking for the Next.js APIs we have a section for that. &lt;/p&gt;

&lt;h3&gt;
  
  
  A New Initialize Command in the CLI
&lt;/h3&gt;

&lt;p&gt;Tina init is my favorite addition to the Tina experience. A single command can bootstrap Tina on a Next.js application and do all the heavy lifting for you. The team spent quite a bit of time working on this, and refining it, to get it just right. The command &lt;code&gt;npx @tinacms/cli init&lt;/code&gt; command currently does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install all dependencies to your application&lt;/li&gt;
&lt;li&gt;Add the Tina commands to your package.json (&lt;code&gt;tina-dev&lt;/code&gt;, &lt;code&gt;tina-build&lt;/code&gt;, &lt;code&gt;tina-start&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Wrap your &lt;code&gt;app.js&lt;/code&gt; / &lt;code&gt;app.tsx&lt;/code&gt; in our &lt;code&gt;TinaEditProvider&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create demo data that you can test Tina out with.&lt;/li&gt;
&lt;li&gt;Create an admin route to allow people to edit, and a way to exit.&lt;/li&gt;
&lt;li&gt;Create a schema file ready for you to shape your content&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This allows you to move quickly and experience Tina without having to write any code. Then when you are ready you can easily extend it to use parts of your existing site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving and Adding Guides
&lt;/h3&gt;

&lt;p&gt;When we introduced Tina, we had a single guide that got you up and running with our Tina Cloud Starter. This was a great way for users to experience Tina but we found that people were missing some key concepts of Tina.&lt;/p&gt;

&lt;p&gt;I went back to the drawing board and created a new guide that takes the &lt;a href="https://dev.to/guides/tina-cloud/add-tinacms-to-existing-site/overview/"&gt;Next.js Starter Blog and adds Tina and Tina Cloud&lt;/a&gt; to it while explaining each concept as we went. This feels like a perfect way to show off Tina, learn how to use Tina, with something that many users are familiar with.&lt;/p&gt;

&lt;p&gt;We also removed old guides that no longer promote Tina's best practices and moved some of our other guides into our experimental section. Experimental to us means that we can't guarantee that there won't be bugs or issues with the packages used. &lt;/p&gt;

&lt;h2&gt;
  
  
  Improving our Tina Starter
&lt;/h2&gt;

&lt;p&gt;The Tina Starter was built originally to show "the power of Tina" while it did that, we didn't feel that it showed a real-world example. So we went back to the drawing board and created our new &lt;a href="https://tina.io/guides/tina-cloud/starter/overview/" rel="noopener noreferrer"&gt;Tina Starter&lt;/a&gt;, which includes a landing page, blog, and about pages. You can edit and rearrange the content and we styled it with TailwindCSS to give it some extra shine! Below is an example of just some of the work you can do:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/edit-alongside-content.gif" class="article-body-image-wrapper"&gt;&lt;img src="/img/edit-alongside-content.gif" alt="Quickstart Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Media Manager
&lt;/h2&gt;

&lt;p&gt;Media manager was one of the most important features that we needed for Tina Cloud. Our &lt;a href="https://tina.io/docs/media-cloudinary/" rel="noopener noreferrer"&gt;Cloudinary Media manager&lt;/a&gt; allows you to change images, upload new images, and delete ones you no longer need without ever leaving the Tina editing experience. &lt;/p&gt;

&lt;p&gt;I wrote a &lt;a href="https://tina.io/blog/manage-your-media-with-cloudinary/" rel="noopener noreferrer"&gt;blog post announcing it&lt;/a&gt; and how to implement it into your application. &lt;/p&gt;

&lt;h2&gt;
  
  
  Caching Improvements
&lt;/h2&gt;

&lt;p&gt;Speed and performance have been something we have been actively working on. We introduced some improvements behind the scenes to improve the way we retrieve the data for your site. Tina is carefully built with performance in mind and is now faster! &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating &lt;code&gt;@tinacms/toolkit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;TinaCMS was built with small modular packages, this meant that we relied heavily on React context. The dependency mismatches from over-modularizing our toolkit,  led to many bugs related to missing context.&lt;/p&gt;

&lt;p&gt;Our open-source team created &lt;code&gt;@tinacms/toolkit&lt;/code&gt; which incorporates the essentials of Tina all in one place. This simplifies everything for you as a user and Tina as a product.&lt;/p&gt;

&lt;p&gt;You can read about all the updates and why decided to make the changes in our pinned &lt;a href="https://github.com/tinacms/tinacms/issues/1898" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel Integration
&lt;/h2&gt;

&lt;p&gt;We wanted to reduce the friction to almost zero when testing TinaCMS, so we worked on adding Vercel integration. This means if you sign up for an account, you can one-click and deploy in minutes and start playing around with TinaCMS and Tina Cloud, using our Starter. In the future we will be adding the ability to deploy any application in Tina Cloud to Vercel!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dashboard Overhaul
&lt;/h2&gt;

&lt;p&gt;When using Tina Cloud in alpha our dashboard UX wasn't a first-class experience and at times could be confusing. We completely overhauled the dashboard, making it easier and quicker to add an application to the Cloud, invite users, and find important information such as site URL(s) or Client ID.&lt;/p&gt;

&lt;p&gt;If you did use the alpha you will need to sign up again as we made large changes to the way we handle user signup in our backend. &lt;/p&gt;

&lt;h2&gt;
  
  
  Changes to Content Modeling
&lt;/h2&gt;

&lt;p&gt;Content Modeling is the core of how you interact with TinaCMS and also retrieve your content. We decided to make an important change and be more primitive based in nature. &lt;/p&gt;

&lt;p&gt;This allows for simplistic queries that don't require disambiguation, we believe this will allow you as a developer to craft queries with ease. &lt;/p&gt;

&lt;p&gt;If you used the alpha of Tina you might want to read this article the team put together to explain all of the &lt;a href="https://tina.io/docs/tina-cloud/migration-overview/" rel="noopener noreferrer"&gt;changes and how to migrate&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Give Us Feedback!
&lt;/h2&gt;

&lt;p&gt;The whole team is truly excited to enter the beta phase and hope you will check it out and give us honest feedback. We want to hear about your projects or, let us know how Tina Cloud can help your team make progress.&lt;/p&gt;

&lt;p&gt;To keep up to date with Tina goings-on make sure to follow &lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt; and &lt;a href="https://twitter.com/james_r_perkins" rel="noopener noreferrer"&gt;@james_r_perkins&lt;/a&gt; on Twitter. Want to chat with the team? Join the &lt;a href="https://discord.gg/njvZZYHj2Q" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for further improvements, features, community-built projects and more!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Tina Cloud is in public alpha</title>
      <dc:creator>James Perkins</dc:creator>
      <pubDate>Wed, 02 Jun 2021 16:03:55 +0000</pubDate>
      <link>https://dev.to/tinacms/tina-cloud-is-in-public-alpha-2ied</link>
      <guid>https://dev.to/tinacms/tina-cloud-is-in-public-alpha-2ied</guid>
      <description>&lt;p&gt;The team at Tina is pleased to announce that &lt;a href="https://tina.io/cloud/" rel="noopener noreferrer"&gt;Tina Cloud&lt;/a&gt; is officially in public Alpha. &lt;/p&gt;

&lt;p&gt;Everyone is encouraged to register a free account on our headless GitHub-backed CMS and start committing. We have been working incredibly hard behind the scenes to get our vision in the hands of developers and content teams.&lt;/p&gt;

&lt;p&gt;Tina Cloud brings the power of Tina's open-source content editor with &lt;a href="https://tina.io/blog/using-graphql-with-the-filesystem/" rel="noopener noreferrer"&gt;a GraphQL API that allows you to interact with your Markdown files stored in your repository&lt;/a&gt;. Also, with Tina Cloud, you can allow &lt;em&gt;any&lt;/em&gt; team member to edit content — even if they don’t have a GitHub account.&lt;/p&gt;

&lt;p&gt;When we &lt;a href="https://www.youtube.com/watch?v=iPDCmbaEF0Y" rel="noopener noreferrer"&gt;launched TinaCMS&lt;/a&gt;, it was mainly an open-source Javascript UI for editing your site, visually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fimage%2Fupload%2Fv1619023278%2Ftina-cms-visual-editing.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fforestry-demo%2Fimage%2Fupload%2Fv1619023278%2Ftina-cms-visual-editing.gif" title="Real-time editing of a Next.js + Tailwind CSS site with Tina’s sidebar." alt="Real-time editing of a Next.js + Tailwind CSS site with Tina’s sidebar."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At that time, TinaCMS was a 3-month-old, open-source project and we relied on developers to roll their own solution for user management, authentication, roles, content storage, and more. But we quickly learned that developers need more out-of-the-box to get their teams successful.  With Tina Cloud, we're staying true to our vision of &lt;strong&gt;Git-backed content management&lt;/strong&gt;, but with a batteries-included experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Expect with the Tina Cloud Alpha?
&lt;/h2&gt;

&lt;p&gt;Tina cloud is still being actively developed, and is in a place we believe you can have a great experience with it. You may run into issues we didn't encounter yet or be required to update to the latest version because we have improved our API.&lt;/p&gt;

&lt;p&gt;Tina cloud isn't complete yet, some key features  are currently being shaped up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Media Management Solution&lt;/li&gt;
&lt;li&gt;Multi-branch workflows&lt;/li&gt;
&lt;li&gt;Read-only tokens for our GraphQL API. That means it's only used when you are editing content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What tech stack should be used with Tina Cloud
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt;: &lt;a href="https://tina.io/blog/tina-cloud-and-nextjs-the-perfect-match/" rel="noopener noreferrer"&gt;Next.js is a perfect match for Tina&lt;/a&gt; and is the default choice for our team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;:  GitHub is required as it is the only Git provider we support on Tina Cloud currently. Let us know if you want us to support other Git providers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static, file-based builds&lt;/strong&gt;: When you go to build our Tina Cloud product collects your filesystem content, in the future, you will be able to fetch data from our Cloud API during build times.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can I get started?
&lt;/h2&gt;

&lt;p&gt;The first thing to do is to &lt;a href="https://auth.tina.io/register" rel="noopener noreferrer"&gt;signup for Tina Cloud&lt;/a&gt;, once you're in, we have a few ways for you to get started and get up and running in minutes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/tinacms/tina-cloud-starter" rel="noopener noreferrer"&gt;Tina Cloud Starter&lt;/a&gt;: A basic implementation of Tina Cloud aimed at getting your up and running in a few minutes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tina.io/guides/tina-cloud/existing-site/overview/" rel="noopener noreferrer"&gt;Tina Cloud Next.js blog starter&lt;/a&gt; - A guide to add Tina Cloud on top of the default Next.js blog starter and work directly through our CMS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also &lt;a href="https://www.youtube.com/watch?v=Y-fG7qzoHKw" rel="noopener noreferrer"&gt;check out the video to get started with Tina Cloud in under 10 minutes&lt;/a&gt;, it gives you an overview of our Tina Cloud starter and gives you some tips on how to run Tina Cloud locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.youtube.com/watch?v=Y-fG7qzoHKw" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/http%3A%2F%2Fimg.youtube.com%2Fvi%2FY-fG7qzoHKw%2F0.jpg" alt="Getting Started with Tina Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can I give feedback or get help?
&lt;/h2&gt;

&lt;p&gt;We have a few channels open for you to reach out, provide feedback or get help with any challenges you may have.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dig into the new &lt;a href="https://tina.io/docs/tina-cloud/" rel="noopener noreferrer"&gt;Tina Cloud documentation&lt;/a&gt; that covers implementations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://discord.gg/6RrAXJws" rel="noopener noreferrer"&gt;Join our Discord&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="//mailto:support@tina.io"&gt;Email us&lt;/a&gt; if would like to schedule a call with our team and share more about your context.&lt;/li&gt;
&lt;li&gt;Get support through your Tina Cloud dashboard (there's a chat widget at the bottom of the screen on the left side).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What about pricing?
&lt;/h2&gt;

&lt;p&gt;We are still deciding out what we believe will be fair pricing for people who decided to use Tina Cloud. During the Alpha, &lt;strong&gt;Tina Cloud is at no cost for small teams&lt;/strong&gt; and we will contact you if we believe your use case may eventually fit within our post-beta paid plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Please reach out
&lt;/h2&gt;

&lt;p&gt;The whole team is truly excited to enter a public Alpha phase and hope you will check it out and give us honest feedback. We wanna hear about your projects, let us know how Tina Cloud can help your team make progress.&lt;/p&gt;

&lt;p&gt;To keep up to date with Tina goings-on make sure to follow &lt;a href="https://twitter.com/tina_cms" rel="noopener noreferrer"&gt;@tina_cms&lt;/a&gt; and &lt;a href="https://twitter.com/james_r_perkins" rel="noopener noreferrer"&gt;@james_r_perkins&lt;/a&gt; on Twitter.&lt;/p&gt;

&lt;p&gt;Stay tuned for further improvements, features, community-built projects and more!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>graphql</category>
      <category>github</category>
      <category>react</category>
    </item>
    <item>
      <title>Supercharging file-based content with GraphQL</title>
      <dc:creator>Jeff See</dc:creator>
      <pubDate>Thu, 29 Apr 2021 15:58:10 +0000</pubDate>
      <link>https://dev.to/tinacms/supercharging-file-based-content-with-graphql-544i</link>
      <guid>https://dev.to/tinacms/supercharging-file-based-content-with-graphql-544i</guid>
      <description>&lt;p&gt;Today we want to introduce you to the Tina GraphQL gateway that brings reliability to Git-based content management. It's an essential piece to provide a robust structured content, while your content remains fully portable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overcoming the limitations of the filesystem
&lt;/h2&gt;

&lt;p&gt;Using the filesystem for website content has been a mainstay of the web development ecosystem for years. The ability to ship your entire website in one fell swoop and roll anything back with thanks to Git has made this a popular and efficient way to get things done with confidence.&lt;/p&gt;

&lt;p&gt;On the other hand, the open nature of using files for content can lead to headaches. Content Management Systems (CMS) have always provided confidence in another way — knowing that your content's shape won't change out from underneath you. The scary (and powerful) thing about using the filesystem is that there's no layer to ensure that you're getting the content that you expect. It's a trade-off that has many valid use-cases, but just as many foot guns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's take an example
&lt;/h2&gt;

&lt;p&gt;We're going to use the &lt;a href="https://github.com/vercel/next.js/tree/canary/examples/blog-starter" rel="noopener noreferrer"&gt;Next.js blog starter&lt;/a&gt; to demonstrate some of the problems with file-based content and how we hope to solve them. If you'd like to follow along you can &lt;a href="https://github.com/tinacms/next-blog-starter-graphql" rel="noopener noreferrer"&gt;fork this repository&lt;/a&gt; and start with the branch called &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/tree/start" rel="noopener noreferrer"&gt;&lt;code&gt;start&lt;/code&gt;&lt;/a&gt;. To skip ahead to the final solution check out the &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/tree/add-tina-gql" rel="noopener noreferrer"&gt;&lt;code&gt;add-tina-gql&lt;/code&gt;&lt;/a&gt; branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our content structure
&lt;/h3&gt;

&lt;p&gt;This app sources its content from Markdown files in a folder called &lt;code&gt;_posts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- _posts
  - dynamic-routing.md
  - hello-world.md
  - preview.md
- pages
  - index.js # lists the blog posts
  - posts
    - [slug].js # dynamically shows the appropriate blog post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On the home page we get each post from the &lt;code&gt;_posts&lt;/code&gt; directory and sort them by date before showing them with our &lt;code&gt;getAllPosts&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAllPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&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;slugs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPostSlugs&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="nx"&gt;slugs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getPostBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// sort posts by date in descending order&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdeuzrsg3m%2Fimage%2Fupload%2Fv1619558511%2Ftina-blog-post%2Fnext-demo-home_kcnyv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdeuzrsg3m%2Fimage%2Fupload%2Fv1619558511%2Ftina-blog-post%2Fnext-demo-home_kcnyv5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Demo: ➡️ &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/tree/start" rel="noopener noreferrer"&gt;Start following along&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  File-based content is simple
&lt;/h4&gt;

&lt;p&gt;What we have so far is great, since our changes are stored in Git we know that if we made a mistake we will be able to easily roll it back to a previous version. But as the complexity of our content increases things become less straightforward.&lt;/p&gt;

&lt;p&gt;To demonstrate that, let's first look at how our content is structured. The "Dynamic Routing and Static Generation" blog post looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dynamic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Routing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Static&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Generation'&lt;/span&gt;
&lt;span class="na"&gt;excerpt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lorem&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;
&lt;span class="na"&gt;coverImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2020-03-16T05:35:07.322Z'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JJ Kasper&lt;/span&gt;
  &lt;span class="na"&gt;picture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/authors/jj.jpeg'&lt;/span&gt;
&lt;span class="na"&gt;ogImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Lorem ipsum dolor sit amet ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's expand on this structure by adding the ability to filter which blog posts show up on the home page. To do that we add a new &lt;code&gt;boolean&lt;/code&gt; value to each post called &lt;code&gt;featured&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dynamic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Routing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Static&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Generation'&lt;/span&gt;
&lt;span class="na"&gt;excerpt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lorem&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;
&lt;span class="na"&gt;coverImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2020-03-16T05:35:07.322Z'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JJ Kasper&lt;/span&gt;
  &lt;span class="na"&gt;picture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/authors/jj.jpeg'&lt;/span&gt;
&lt;span class="na"&gt;ogImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="na"&gt;featured&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Lorem ipsum dolor sit amet ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can update our &lt;code&gt;getAllPosts&lt;/code&gt; function accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;export function getAllPosts(fields = []) {
&lt;/span&gt;  const slugs = getPostSlugs();
  const posts = slugs
    .map((slug) =&amp;gt; getPostBySlug(slug, fields))
    // sort posts by date in descending order
   .sort((post1, post2) =&amp;gt; (post1.date &amp;gt; post2.date ? -1 : 1))
&lt;span class="gi"&gt;+  .filter((post) =&amp;gt; post.featured);
&lt;/span&gt;  return posts
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add a new post to test this out, this one &lt;em&gt;won't&lt;/em&gt; be featured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Why&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tina&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Great'&lt;/span&gt;
&lt;span class="na"&gt;excerpt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Lorem&lt;/span&gt;&lt;span class="nv"&gt;  &lt;/span&gt;&lt;span class="s"&gt;...'&lt;/span&gt;
&lt;span class="na"&gt;coverImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2021-04-25T05:35:07.322Z'&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JJ Kasper&lt;/span&gt;
  &lt;span class="na"&gt;picture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/authors/jj.jpeg'&lt;/span&gt;
&lt;span class="na"&gt;ogImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets/blog/dynamic-routing/cover.jpg'&lt;/span&gt;
&lt;span class="na"&gt;featured&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Lorem ipsum dolor sit amet ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Woops, look who's showing up on our home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdeuzrsg3m%2Fimage%2Fupload%2Fv1619560025%2Ftina-blog-post%2Fllama-woops_cchyel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdeuzrsg3m%2Fimage%2Fupload%2Fv1619560025%2Ftina-blog-post%2Fllama-woops_cchyel.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can you spot the issue? We accidentally set &lt;code&gt;featured&lt;/code&gt; to &lt;code&gt;"false"&lt;/code&gt; instead of &lt;code&gt;false&lt;/code&gt;! We made it a &lt;code&gt;string&lt;/code&gt;, not a &lt;code&gt;boolean&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Demo: 👀 &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/compare/start..featured-tag-mistake" rel="noopener noreferrer"&gt;Spot our mistakes&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we had been using a CMS this probably wouldn't have happened. Most of them require that the shape of your content is well-defined. While these kinds of issues are painful, there's a lot more that CMSs do for us that we don't get from the filesystem — you may have noticed something else about the shape of our content that doesn't feel quite right…&lt;/p&gt;

&lt;h3&gt;
  
  
  Relationships: it's complicated
&lt;/h3&gt;

&lt;p&gt;Let's look at the data from our new blog post again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: "Why Tina is Great"
excerpt: "Lorem  ..."
coverImage: "/assets/blog/dynamic-routing/cover.jpg"
date: "2021-04-25T05:35:07.322Z"
author:
  name: JJ Kasper
  picture: "/assets/blog/authors/jj.jpeg"
ogImage:
  url: "/assets/blog/dynamic-routing/cover.jpg"
featured: "false"
---

Lorem ipsum dolor sit amet…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;author&lt;/code&gt; content is the same over in the "Dynamic Routing and Static Generation" post. If JJ wanted to change his &lt;code&gt;picture&lt;/code&gt; he will need to update it on every post he's written. Sounds like something a CMS would solve with a content &lt;em&gt;relationship&lt;/em&gt;, JJ should ideally be an author who &lt;em&gt;has many&lt;/em&gt; posts. To solve this with our file-based content we could split the author data into its own file and place a reference to that author's filename in the &lt;code&gt;post&lt;/code&gt; structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;author: _authors/jj.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But now we have to update our data-fetching logic so that whenever it comes across the &lt;code&gt;author&lt;/code&gt; field in a post it knows to make an additional request for &lt;em&gt;that&lt;/em&gt; data. This is pretty cumbersome, and again — as complexity grows this type of logic quickly becomes untenable. With a CMS SDK or GraphQL API we'd be able to do this sort of thing easily, and we'd have confidence that a document can't be deleted if it's being referenced from another document.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Demo: &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/compare/featured-tag-mistake..split-author-data" rel="noopener noreferrer"&gt;Check out the diff&lt;/a&gt; to see how we're awkwardly making use of a separate &lt;code&gt;author&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Content Management Systems: Reliable? Yes. Portable? No.
&lt;/h3&gt;

&lt;p&gt;Headless CMSs are a great way to maintain full control over your frontend code while offloading issues like those mentioned above to a more robust content layer. But when you hand your content over to a CMS you lose the power of Git that comes built-in with file-based content.&lt;/p&gt;

&lt;p&gt;With a CMS, when you make a change to the shape of your content you also need to &lt;em&gt;coordinate&lt;/em&gt; that new shape with your code, and you need to make sure that all of your existing content has been updated accordingly.&lt;/p&gt;

&lt;p&gt;Most CMSs have come up with various ways to help with this: separate sandbox environments, preview APIs, and migration SDK scripts — all of which carry their own set of headaches. None of this is necessary with file-based content, &lt;em&gt;everything moves and changes together&lt;/em&gt;. So what if we could bring the robust features of a headless CMS to your local filesystem? What might that look like?&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet Tina Content API
&lt;/h2&gt;

&lt;p&gt;Today we're introducing a tool that marries the power of a headless CMS with the convenience and portability of file-based content. &lt;strong&gt;The Tina Content API is a GraphQL service that sources content from your local filesystem&lt;/strong&gt;. It will soon be available via &lt;a href="https://tina.io/cloud/" rel="noopener noreferrer"&gt;Tina Cloud&lt;/a&gt;, which connects to your GitHub repository to offer an identical, cloud-based, headless API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tina Cloud is currently open to a limited set of Next.js projects, &lt;a href="https://tina.io/early-access/" rel="noopener noreferrer"&gt;sign up&lt;/a&gt; for early access to get into the private beta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get a sense for how this works, let's make some tweaks to the blog demo.&lt;/p&gt;

&lt;p&gt;First let's install Tina CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add tina-graphql-gateway-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's add a schema so the API knows exactly what kind of shape to build for your content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; .tina &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;touch&lt;/span&gt; .tina/schema.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// `.tina/schema.ts`&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;defineSchema&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="s1"&gt;tina-graphql-gateway-cli&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineSchema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;collections&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="cm"&gt;/*
       * Indicates where to save this kind of content (eg. the "_posts" folder)
       */&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;templates&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Simple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simple_post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;fields&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Excerpt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;excerpt&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cover Image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coverImage&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&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="c1"&gt;// We indicate the author is a "reference"&lt;/span&gt;
              &lt;span class="c1"&gt;// to another document&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reference&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ogImage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open Graph Image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;fields&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&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="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Featured&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;featured&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="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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_authors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;templates&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;fields&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;picture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Picture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&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="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we're referencing the &lt;code&gt;authors&lt;/code&gt; section from the &lt;code&gt;post.author&lt;/code&gt; field&lt;/p&gt;

&lt;p&gt;Next we replace the &lt;code&gt;dev&lt;/code&gt; command to start the GraphQL server in tandem with our Next.js app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yarn tina-gql server:start -c &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;next dev&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Demo: &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/compare/featured-tag-mistake...add-tina-gql" rel="noopener noreferrer"&gt;Here's&lt;/a&gt; the changes we've made so far. Check out the &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/tree/add-tina-gql" rel="noopener noreferrer"&gt;&lt;code&gt;add-tina-graphql&lt;/code&gt;&lt;/a&gt; branch to pick up from this point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the &lt;code&gt;dev&lt;/code&gt; command, you can see that we now have a local GraphQL server listening on port 4001 along with some information about auto-generated configuration files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Started Filesystem GraphQL server on port: 4001
Generating Tina config
Tina config &lt;span class="o"&gt;======&amp;gt;&lt;/span&gt; /.tina/__generated__/config
Typescript types &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /.tina/__generated__/types.ts
GraphQL types &lt;span class="o"&gt;====&amp;gt;&lt;/span&gt; /.tina/__generated__/schema.gql
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it out:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡Tip: if you have a GraphQL client like &lt;a href="https://altair.sirmuel.design/" rel="noopener noreferrer"&gt;Altair&lt;/a&gt; you can explore the API by pointing it to &lt;a href="http://localhost:4001/graphql" rel="noopener noreferrer"&gt;http://localhost:4001/graphql&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="c"&gt;# Point your request to http://localhost:4001/graphql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;getPostsList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SimplePost_Doc_Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unexpected value of type string for boolean value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"getPostsList"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error is coming from our old friend &lt;code&gt;featured: "false"&lt;/code&gt;. This is exactly the kind of assurance you'd get from a CMS, but without any of the overhead. After fixing the issue, we get what we expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"getPostsList"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dynamic Routing and Static Generation"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;truncated&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use GraphQL to replace all of our bespoke filesystem data-fetching logic and rest assured that the data we get back will be exactly what we expect it to be.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BlogPostQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;getPostsDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$relativePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SimplePost_Doc_Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;coverImage&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Author_Doc_Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="n"&gt;picture&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;ogImage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;featured&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;_body&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Demo: &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/compare/split-author-data..add-tina-gql" rel="noopener noreferrer"&gt;View the changes&lt;/a&gt; we made to add Tina GraphQL&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  To be continued
&lt;/h2&gt;

&lt;p&gt;Being able to work locally with GraphQL is a first step to help us bring the capabilities of a full-fledged CMS to the filesystem. Tina Cloud will offer the same great experience through a hosted headless API. In the coming weeks we'll continue sharing more about how this API works with TinaCMS to bring visual content management to your website with minimal overhead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take a look at the &lt;a href="https://github.com/tinacms/next-blog-starter-graphql/tree/add-tina-gql" rel="noopener noreferrer"&gt;demo&lt;/a&gt; we just went through, see if you can expand on it and share your progress with us!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>tinacms</category>
      <category>nextjs</category>
      <category>graphql</category>
    </item>
    <item>
      <title>2021 Q1 TinaCMS Roundup</title>
      <dc:creator>Frank Taillandier</dc:creator>
      <pubDate>Thu, 15 Apr 2021 12:13:13 +0000</pubDate>
      <link>https://dev.to/tinacms/2021-q1-tinacms-updates-1g2</link>
      <guid>https://dev.to/tinacms/2021-q1-tinacms-updates-1g2</guid>
      <description>&lt;p&gt;We are regularly improving Tina to provide an effective developer experience &lt;em&gt;and&lt;/em&gt; a unique visual content editing experience. Latest additions are the result of listening to the feedback of the community as well as scratching our own itch.&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at some of the most impactful recent changes in Tina core library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Radio Group Field 🆕
&lt;/h2&gt;

&lt;p&gt;Editors are now able to pick from a list of options, the &lt;a href="https://tina.io/docs/plugins/fields/radio-group/"&gt;radio group field&lt;/a&gt; comes in two flavors: &lt;code&gt;radio&lt;/code&gt; and &lt;code&gt;button&lt;/code&gt;, they are displayed horizontally by default, you can opt-in for the vertical variant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WdOzWeDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/fields/radio-group-field-horizontal-radio.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WdOzWeDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/fields/radio-group-field-horizontal-radio.gif" alt="Radio Group field in horizontal direction (default)"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3JkR2fwy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/fields/radio-group-field-horizontal-button.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3JkR2fwy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/fields/radio-group-field-horizontal-button.gif" alt="Radio Group field Button Variant (horizontal)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Search and filter blocks 🆕
&lt;/h2&gt;

&lt;p&gt;When your page template offers more than 10 components to pick from, editors can type and &lt;a href="https://github.com/tinacms/tinacms/pull/1772"&gt;filter from the blocks menu list&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Duplicate blocks 🆕
&lt;/h2&gt;

&lt;p&gt;A small, yet big, win for inline editing, you can now duplicate a block with a single click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xfovWkVs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/blog/duplicate-block-tinacms.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xfovWkVs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://tina.io/img/blog/duplicate-block-tinacms.gif" alt="When using TIna Inline Block, click on the duplicate icon to insert the same block below."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethinking Inline Editing 🤔
&lt;/h2&gt;

&lt;p&gt;We started reevaluating our approach to how Tina does inline editing. We refactored the code to take advantage of &lt;a href="https://github.com/tinacms/tinacms/pull/1749"&gt;events and references&lt;/a&gt; to provide a &lt;a href="https://github.com/tinacms/tinacms/blob/master/packages/react-tinacms-inline/README.md#usefieldref-ref-based-inline-editing"&gt;new API&lt;/a&gt;. This work is far from done and is currently only available through a feature flag, as this will be subject to more changes.&lt;br&gt;&lt;br&gt;
We’ll share our views in an upcoming post on what the future of inline editing will look like in Tina from a developer perspective.&lt;/p&gt;

&lt;p&gt;Related to this change, we decided to remove drag-and-drop of inline blocks for now. There were some cases where it was creating issues but we might revisit this feature later when we have better inline editing. Now, you can move blocks around a page with up and down block controls, which shouldn't require too many clicks in most cases.&lt;/p&gt;

&lt;p&gt;You'll soon be able to &lt;a href="https://github.com/tinacms/tinacms/pull/1795#issue-615395547"&gt;resize the sidebar&lt;/a&gt; as you see fit. &lt;br&gt;
This makes the writing experience much more enjoyable when you  write long format in the WYSIWYG editor. ↔️ &lt;/p&gt;

&lt;h2&gt;
  
  
  Next.js and Tailwind Demo 👀
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://tina-demo-two.vercel.app/"&gt;default demo&lt;/a&gt; is based on Next.js and Tailwind CSS. It currently showcases how you can set up Tina to provide: theme colors, component variants, dark mode, responsive blocks, etc. In the near future, we’ll dedicate a post on how we built this demo, so  you will be able to dig in further and learn from it. For now, feel free to play around and &lt;a href="https://github.com/tinacms/tina-tailwind-inline-demo/issues"&gt;let us know if you have any feedback&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contentful plugin 🔌
&lt;/h2&gt;

&lt;p&gt;Our homepage makes it very clear that Tina can potentially work with any backend, we already released &lt;a href="https://dev.to/guides/nextjs/tina-with-strapi/overview/"&gt;a guide to work with Strapi&lt;/a&gt;. We did also experiment with Contentful and published &lt;a href="https://github.com/tinalabs/tinacms-contentful"&gt;a plugin&lt;/a&gt; for Tina to work with content fetched from their Content API. If you’re working with Contentful, feel free to test the plugin and &lt;a href="https://github.com/tinalabs/tinacms-contentful/issues"&gt;leave us feedback&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community 💜
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New Core Team Members
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jeffsee55"&gt;Jeff&lt;/a&gt;, &lt;a href="https://github.com/Enigmatical"&gt;Chris&lt;/a&gt;, and &lt;a href="https://github.com/jbevis"&gt;Jack&lt;/a&gt; joined the core team. We’re very happy to welcome them to help us unleash the potential Tina has to offer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some stats 📈
&lt;/h3&gt;

&lt;p&gt;Our community activities are now tracked in &lt;a href="https://orbit.love/"&gt;Orbit&lt;/a&gt; 💜, in order to help us better build better relationships with you. Warm thanks to &lt;a href="https://github.com/phacks/"&gt;Nicolas&lt;/a&gt; from the Orbit team for helping out setting things up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HXhCpVH2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://tina.io/img/blog/orbit-members-2021-q1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HXhCpVH2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://tina.io/img/blog/orbit-members-2021-q1.png" alt="Member Activity in the Tina Community for 2021 Q1 in Orbit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Orbit makes it easy to reflect on the community’s activity, you can easily see when you got on the front page of Hacker News 😊.   &lt;/p&gt;

&lt;p&gt;Shout out to our most active community members for 2021 Q1:  &lt;a href="https://github.com/joeinnes"&gt;Joe Innes&lt;/a&gt;, &lt;a href="https://github.com/zenflow"&gt;Matthew Francis Brunetti&lt;/a&gt;, &lt;a href="https://github.com/austincondiff"&gt;Austin Condiff&lt;/a&gt;, &lt;a href="https://github.com/hirvin-faria"&gt;Hirvin Faria&lt;/a&gt; and &lt;a href="https://github.com/Chizzah"&gt;Chadd Poggenpoel&lt;/a&gt;. 👏👏👏👏👏&lt;/p&gt;

&lt;p&gt;Our open source project has reached some new milestones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/tinacms/tinacms"&gt;Tina&lt;/a&gt; has been starred by more than &lt;strong&gt;6K&lt;/strong&gt; people on GitHub 🌟&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/tinacms"&gt;tinacms core package&lt;/a&gt; has been downloaded more than 250K times 📦&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🙏 Thanks for your interest, we’re just getting started!&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing... ⏳
&lt;/h2&gt;

&lt;p&gt;In order to enhance the Tina ecosystem, our team is currently working on a &lt;a href="https://tina.io/blog/using-graphql-with-the-filesystem/"&gt;new open source tool to ease the way you can do structured content management while keeping control of your content in your Git repository&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://tina.io/rss.xml"&gt;Subscribe to our blog feed&lt;/a&gt; or &lt;a href="https://gmail.us20.list-manage.com/subscribe?u=1fea337bee20e7270d025ea8a&amp;amp;id=c1062536a1"&gt;newsletter&lt;/a&gt; to be the first to know about all our new and exciting developments.&lt;/p&gt;

</description>
      <category>cms</category>
      <category>nextjs</category>
      <category>tailwindcss</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
