<?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>Forem: Contentful Blog</title>
    <description>The latest articles on Forem by Contentful Blog (@contentful_blog).</description>
    <link>https://forem.com/contentful_blog</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F72128%2Fa962b5a3-25d8-48e0-881d-293f82b517d8.jpg</url>
      <title>Forem: Contentful Blog</title>
      <link>https://forem.com/contentful_blog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/contentful_blog"/>
    <language>en</language>
    <item>
      <title>How to build chatbots with Contentful and Azure functions</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Wed, 26 Aug 2020 09:00:00 +0000</pubDate>
      <link>https://forem.com/contentful/what-can-i-help-you-with-powering-chatbots-with-contentful-e14</link>
      <guid>https://forem.com/contentful/what-can-i-help-you-with-powering-chatbots-with-contentful-e14</guid>
      <description>&lt;p&gt;In 2019, I was boarding a plane for London when I realized that I forgot my shaving kit. I quickly opened the app for the hotel that I would be staying in. Thanks to its chatbot feature, I dropped them a request. They were able to immediately solve my shaving problem and, in addition, booked me a taxi from the airport.&lt;/p&gt;

&lt;p&gt;The benefits of chatbots are pretty evident: they make customers happy, they operate as a sales tool and allow you to maintain a conversational relationship. I didn’t get frustrated, I stayed connected with the hotel and now we’ve built a conversational history.&lt;/p&gt;

&lt;p&gt;Because conversational bots offer an important channel to reach customers, most brands are developing expert bots that provide specific solutions. Some chatbots provide FAQ solutions, others recommend specific products depending on the customer’s needs, and still others quickly reply to questions about billing and account information. Contentful can handle the content layer of your chatbot, and this post walks you through the architecture, implementation and results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specific context and tech stack for chatbot
&lt;/h2&gt;

&lt;p&gt;One of our enterprise customers wanted to build an FAQ chatbot with Contentful after they used the content platform to successfully power their web products. They were already using Microsoft cloud services, so we decided to build a quick proof-of-concept using Microsoft Azure functions and knowledge base. Contentful works with these tools, but can also be used with other cloud and bot services like Amazon Web Services, Google Cloud Platform, or IBM Watson thanks to its APIs. After some initial investigation, we developed the architecture presented here, which uses Contentful webhooks, Azure cloud functions and Microsoft’s knowledge base service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vVRVTEtM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1m7uZ9141mcgr2FPGpe0yy/c9df075a3c44bcf81528154f2dfd65ce/BLOG_PoweringChatbots_ArchitectureOverview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vVRVTEtM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1m7uZ9141mcgr2FPGpe0yy/c9df075a3c44bcf81528154f2dfd65ce/BLOG_PoweringChatbots_ArchitectureOverview.png" alt="The Contentful chatbot publishing flow "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Editors will publish the FAQ in Contentful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publishing of the FAQ will trigger a webhook in Contentful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The webhook will call our publish cloud function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The function will push the content to our knowledge base&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unpublishing flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Editors will unpublish the FAQ in Contentful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unpublishing of the FAQ will trigger a webhook in Contentful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The webhook will call our unpublish cloud function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The function will remove the FAQ from our knowledge base&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Contentful
&lt;/h3&gt;

&lt;p&gt;We used Contentful to store questions and answers for the chatbot. In the Contentful web app, editors can easily write, edit and publish that content. &lt;/p&gt;

&lt;h4&gt;
  
  
  Content Model
&lt;/h4&gt;

&lt;p&gt;We’re keeping the content model very simple for this demo — just a single content type holds our question and answer pairs, as well as an internal title for content editors to have as a reference. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JpmL-uyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/5biAKVZbj7JUiecCL4t2br/c665fe01e49c7a7ec43ab9bbc2070db5/Explaining_Content_Models_-_ChatBots.jpg%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JpmL-uyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/5biAKVZbj7JUiecCL4t2br/c665fe01e49c7a7ec43ab9bbc2070db5/Explaining_Content_Models_-_ChatBots.jpg%3Fq%3D1" alt="Contentful FAQ content model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Webhooks
&lt;/h4&gt;

&lt;p&gt;We use webhooks for publish and unpublish events, triggering our cloud functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1k0_GoIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/6RqTPRlpbRC7LxGD0O2yw5/0e4c36d9694a6c7e5f956fafb9a12e2a/Screenshot_2020-04-27_at_11.44.59.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1k0_GoIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/6RqTPRlpbRC7LxGD0O2yw5/0e4c36d9694a6c7e5f956fafb9a12e2a/Screenshot_2020-04-27_at_11.44.59.png%3Fq%3D1" alt="Using Contentful webhooks to build a chatbot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When creating the publish event webhook, make sure to set the trigger of the webhook for the entry publish event as shown below. For the URL, we will need to add the endpoint of our cloud function, as we have not configured any cloud function yet. We can use the &lt;a href="https://webhook.site/#!/a74d2708-dd74-49e2-8bf2-ee616bcf6fa0"&gt;DummyWebhook&lt;/a&gt; website or &lt;a href="https://ngrok.com/"&gt;ngrok&lt;/a&gt; to create dummy endpoints and add them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zh5tT0-I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/3R7YcQ9VX94jdDpUBkFs9A/4c54f9f1bf5740b77011dda8e8bf43fe/unnamed.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zh5tT0-I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/3R7YcQ9VX94jdDpUBkFs9A/4c54f9f1bf5740b77011dda8e8bf43fe/unnamed.png%3Fq%3D1" alt="Creating dummy end points and adding them"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When creating the unpublish event webhook, make sure to set the trigger of our webhook for the entry unpublish event as shown below. Just like last time, we’ll populate the URL with a dummy value from DummyWebhook or ngrok.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JsFJKzc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1ruUdOy3OGt2hQHTeHZa6U/b3fcd6e8658f1f55e86c7f67b9fab45e/Screenshot_2020-04-27_at_11.51.52.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JsFJKzc2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1ruUdOy3OGt2hQHTeHZa6U/b3fcd6e8658f1f55e86c7f67b9fab45e/Screenshot_2020-04-27_at_11.51.52.png%3Fq%3D1" alt="Creating the Contentful chatbot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once done, you can create an entry and test our workflow by publishing and unpublishing it and checking the webhook payload being sent. You can then view the payloads in our webhook logs.&lt;/p&gt;

&lt;p&gt;Publish entry webhook payload:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "sys": {
    "type": "Entry",
    "id": "1ssZF4Lqq1Wpi9KSHvq9jE",
    "space": {
      "sys": {
        "type": "Link",
        "linkType": "Space",
        "id": "bwn0phmhnub6"
      }
    },
    "environment": {
      "sys": {
        "id": "master",
        "type": "Link",
        "linkType": "Environment"
      }
    },
    "contentType": {
      "sys": {
        "type": "Link",
        "linkType": "ContentType",
        "id": "faqQuestion"
      }
    },
    "revision": 2,
    "createdAt": "2020-04-13T23:06:07.225Z",
    "updatedAt": "2020-04-13T23:07:05.635Z"
  },
  "fields": {
    "title": {
      "en-US": "First question"
    },
    "question": {
      "en-US": "What is contentful? \n\n"
    },
    "answer": {
      "en-US": {
        "data": {},
        "content": [
          {
            "data": {},
            "content": [
              {
                "data": {},
                "marks": [],
                "value": "Contentful is Headless CSM",
                "nodeType": "text"
              }
            ],
            "nodeType": "paragraph"
          }
        ],
        "nodeType": "document"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unpublish entry webhook payload:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "sys": {
    "type": "DeletedEntry",
    "id": "1ssZF4Lqq1Wpi9KSHvq9jE",
    "space": {
      "sys": {
        "type": "Link",
        "linkType": "Space",
        "id": "bwn0phmhnub6"
      }
    },
    "environment": {
      "sys": {
        "id": "master",
        "type": "Link",
        "linkType": "Environment"
      }
    },
    "contentType": {
      "sys": {
        "type": "Link",
        "linkType": "ContentType",
        "id": "faqQuestion"
      }
    },
    "revision": 2,
    "createdAt": "2020-04-13T23:27:30.971Z",
    "updatedAt": "2020-04-13T23:27:30.971Z",
    "deletedAt": "2020-04-13T23:27:30.971Z"
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For now, that’s all we have to do within Contentful. &lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the FAQ chatbot in Microsoft Knowledge Base
&lt;/h3&gt;

&lt;h4&gt;
  
  
  QnA service
&lt;/h4&gt;

&lt;p&gt;1. Go to &lt;a href="https://www.qnamaker.ai/"&gt;https://www.qnamaker.ai/&lt;/a&gt; and create a free account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L4XoF63n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/287CZO8MXz55uu1sUmRMXm/8abd914ace70d5a9e65412d5b15497d9/unnamed__1_.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L4XoF63n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/287CZO8MXz55uu1sUmRMXm/8abd914ace70d5a9e65412d5b15497d9/unnamed__1_.png%3Fq%3D1" alt="Creating an account at QnA"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2. Create a knowledge base.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DCho1F8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/Y767pq5Udo6VfIJ4297py/3c7b8257e9c0c7e944598e4fb6ed4aa7/unnamed__2_.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DCho1F8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/Y767pq5Udo6VfIJ4297py/3c7b8257e9c0c7e944598e4fb6ed4aa7/unnamed__2_.png%3Fq%3D1" alt="How to create a knowledgebase in QnAmaker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3. Create a QnA service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1YlPBkEO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/4YYH3nvtJMYq9FBwbHABCx/6198d86530070e5f5188f1206c088673/unnamed__3_.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1YlPBkEO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/4YYH3nvtJMYq9FBwbHABCx/6198d86530070e5f5188f1206c088673/unnamed__3_.png%3Fq%3D1" alt="Create a QnA service&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4. Go back to &lt;a href="https://www.qnamaker.ai/"&gt;https://www.qnamaker.ai/&lt;/a&gt; and refresh to see your subscription name and Azure QnA service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2EsisLkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1QyvFGK88dhGT2zHZfbbHT/ec809ba1475cae93640edc4f0c0d3b6a/Screenshot_2020-04-29_at_14.49.15.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2EsisLkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1QyvFGK88dhGT2zHZfbbHT/ec809ba1475cae93640edc4f0c0d3b6a/Screenshot_2020-04-29_at_14.49.15.png%3Fq%3D1" alt="Go back and refresh to see your subscription name and Azure QnA service.&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5. Complete the rest of the steps to create a knowledge base. Once done, you will have a knowledge base similar to the one shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z9TgX3sF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/7lnju7tC37f770QU4FRNDX/98830774c35a3704dcbe5582dfa805dd/unnamed__4_.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z9TgX3sF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/7lnju7tC37f770QU4FRNDX/98830774c35a3704dcbe5582dfa805dd/unnamed__4_.png%3Fq%3D1" alt="Complete the rest of the steps to create a knowledge base. Once done, you will have a knowledge base similar to the one shown below."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  APIs to integrate
&lt;/h4&gt;

&lt;p&gt;Now that we’re done with creating our knowledge base, we need to populate it with questions and answers coming from Contentful for the chatbot to use. Let’s explore which APIs we can use to add, update or delete information from our knowledge base.&lt;/p&gt;

&lt;p&gt;We will be using the &lt;a href="https://docs.microsoft.com/en-us/rest/api/cognitiveservices/qnamaker/knowledgebase/update"&gt;update endpoint&lt;/a&gt; to add and remove content. We will also use the &lt;a href="https://docs.microsoft.com/en-us/rest/api/cognitiveservices/qnamaker/knowledgebase/download"&gt;download endpoint&lt;/a&gt; to get all questions and answers and then filter them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure functions
&lt;/h4&gt;

&lt;p&gt;Azure functions are a powerful way to develop and deploy serverless applications. For this use case we chose Azure functions as we can quickly develop and test our proof of concept.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;This &lt;a href="https://www.youtube.com/watch?v=F0dJz8LLF4Q"&gt;video tutorial from Microsoft&lt;/a&gt; gives you a guide to set up an Azure function project using Visual Studio Code.&lt;/p&gt;

&lt;p&gt;We used the following settings for this chatbot project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Language&lt;/strong&gt;: you can select &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages"&gt;multiple languages&lt;/a&gt;, but we used JavaScript  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Function trigger&lt;/strong&gt;: http&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt;: the function we used &lt;strong&gt;publishWebhookHandler&lt;/strong&gt; for the first function and &lt;strong&gt;unpublishWebhookHandler&lt;/strong&gt; for the second function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access Rights&lt;/strong&gt;: set it to anonymous for now. For production, make sure to implement proper security mechanisms &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We created a new Azure function project with two functions: publishWebhookHandler and unpublishWebhookHandler.&lt;/p&gt;

&lt;h4&gt;
  
  
  publishWebhookHandler
&lt;/h4&gt;

&lt;p&gt;This function will be used to &lt;strong&gt;push content&lt;/strong&gt; into our &lt;strong&gt;knowledge&lt;/strong&gt; &lt;strong&gt;base&lt;/strong&gt; using the &lt;strong&gt;webhook&lt;/strong&gt; coming from Contentful whenever the &lt;strong&gt;content&lt;/strong&gt; is &lt;strong&gt;published&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Algorithm&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Retrieve the following from the webhook: entry ID, question and answer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make an &lt;a href="https://westus.dev.cognitive.microsoft.com/docs/services/5a93fcf85b4ccd136866eb37/operations/knowledgebases_download"&gt;API&lt;/a&gt; call to our knowledge base to get all the questions and the answers. Once you have all questions, filter the question using metadata and check for the entry ID. We’re doing this to check if we have duplicate questions in our &lt;strong&gt;knowledge base,&lt;/strong&gt; so we &lt;strong&gt;update&lt;/strong&gt; them instead of &lt;strong&gt;adding&lt;/strong&gt; a &lt;strong&gt;duplicate&lt;/strong&gt; entry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the question is &lt;strong&gt;present&lt;/strong&gt; we will &lt;strong&gt;update&lt;/strong&gt; that &lt;strong&gt;question&lt;/strong&gt; using the &lt;a href="https://westus.dev.cognitive.microsoft.com/docs/services/5a93fcf85b4ccd136866eb37/operations/5ac266295b4ccd1554da7600"&gt;update endpoint&lt;/a&gt;. Use the same endpoint to add new questions. The only difference is the body of the payload. You can see in the metadata that we are adding key &lt;strong&gt;contentfulid&lt;/strong&gt; and its &lt;strong&gt;value&lt;/strong&gt;. This helps us &lt;strong&gt;to map&lt;/strong&gt; entries in &lt;strong&gt;Contentful&lt;/strong&gt; with entries in the &lt;strong&gt;knowledge base&lt;/strong&gt; and will help us for &lt;strong&gt;deleting&lt;/strong&gt; and &lt;strong&gt;updating&lt;/strong&gt; &lt;strong&gt;entries&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Adding a new question:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "add": {
    "qnaList": [
      {
        "answer": "answer",
        "source": "source",
        "questions": [
          "question"
        ],
        "metadata": [{"name":"contentfulid","value":"1ssZF4Lqq1Wpi9KSHvq9jE"}]
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Updating a question:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "update": {
    "name": "QnA Maker FAQ Prompts Bot",
    "qnaList": [
      {
        "id":87,
        "answer": "answer 2",
        "source": "source",
        "questions": {
          "add":["questions 1"],
          "delete":[]
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sample function for adding content to our knowledge base:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = async function(context, req) {
   if (req.body) {
       const contentfulID = req.body.sys.id;
       const contentfulQuestion = req.body.fields.question['en-US'];
       const contentfulAnswer = req.body.fields.answer['en-US'].content[0].content[0].value;

       const questionFromKB = await getQuestionIdFromKB(contentfulID);
       const response = await  upsertQuestionInKB(context, questionFromKB, contentfulID, contentfulAnswer, contentfulQuestion);

       context.res = {
           status: 200,
           body: JSON.stringify(response),
       };
   } else {
       context.res = {
           status: 400,
           body: 'Please pass a name on the query string or in the request body',
       };
   }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;
  
  
  unpublishWebhookHandler
&lt;/h4&gt;

&lt;p&gt;This function &lt;strong&gt;removes content&lt;/strong&gt; from our &lt;strong&gt;knowledge&lt;/strong&gt; &lt;strong&gt;base&lt;/strong&gt; using the unpublish &lt;strong&gt;webhook&lt;/strong&gt;, whenever &lt;strong&gt;content&lt;/strong&gt; is &lt;strong&gt;unpublished&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = async function(context, req) {
 if (req.body) {
     const contentfulID = req.body.sys.id;
     const questionId = await getQuestionIdFromKB(contentfulID);
     const response = await removeQuestionFromKB(questionId);
     context.res = {
         body: JSON.stringify(response),
     };
 } else {
     context.res = {
         status: 400,
         body: 'Please pass a name on the query string or in the request body',
     };
 }
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Algorithm&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Retrieve the &lt;strong&gt;entry id&lt;/strong&gt; from the webhook&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make an &lt;a href="https://westus.dev.cognitive.microsoft.com/docs/services/5a93fcf85b4ccd136866eb37/operations/knowledgebases_download"&gt;API&lt;/a&gt; call to our knowledge base to get all the questions, and filter the question using the &lt;strong&gt;entry id&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make an &lt;a href="https://westus.dev.cognitive.microsoft.com/docs/services/5a93fcf85b4ccd136866eb37/operations/5ac266295b4ccd1554da7600"&gt;API&lt;/a&gt; call to remove the question from our &lt;strong&gt;knowledge base&lt;/strong&gt;. The payload will look similar to the following:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "delete":{
      "ids":[
         29
      ]
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy
&lt;/h3&gt;

&lt;p&gt;Once both functions are ready, they can be deployed to Azure Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_svxSH0V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/33g3RiFqq7D1f8K1T9HiR8/2a1c442f9e062f20f0df4472e755cabb/Screenshot_2020-08-26_at_11.04.16.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_svxSH0V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/33g3RiFqq7D1f8K1T9HiR8/2a1c442f9e062f20f0df4472e755cabb/Screenshot_2020-08-26_at_11.04.16.png%3Fq%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the deployment is complete, get the &lt;strong&gt;publishWebhookHandler&lt;/strong&gt; and &lt;strong&gt;unpublishWebhookHandler&lt;/strong&gt; &lt;strong&gt;URLs&lt;/strong&gt; from Microsoft Azure portal. Add them to the &lt;strong&gt;webhooks,&lt;/strong&gt; which we created in the &lt;strong&gt;first step&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing our chatbot end to end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Populate content
&lt;/h3&gt;

&lt;p&gt;We &lt;strong&gt;published&lt;/strong&gt; three &lt;strong&gt;Q&amp;amp;As&lt;/strong&gt; for testing in Contentful. Each of the publish events triggered a &lt;strong&gt;webhook&lt;/strong&gt; and they were pushed into our knowledge base. To check, we logged into &lt;a href="https://www.qnamaker.ai/"&gt;https://www.qnamaker.ai&lt;/a&gt; to see if the questions are published.&lt;/p&gt;

&lt;p&gt;On the screenshot below you can see the three published Q&amp;amp;As on Contentful as well as added on our knowledge base.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VupwPkwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1WPsAcKmNGrrgzK2hnvC6E/dd4fedf6fab1c0985f77acf4d4e0d1c3/Screenshot_2020-08-26_at_11.05.28.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VupwPkwT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/1WPsAcKmNGrrgzK2hnvC6E/dd4fedf6fab1c0985f77acf4d4e0d1c3/Screenshot_2020-08-26_at_11.05.28.png%3Fq%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, our knowledge base will train the content. Inside QnA Service, clicking on &lt;strong&gt;Test&lt;/strong&gt; you can test your chatbot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B8XXy66Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/20tj2zEsnpv58Ol6KhNptJ/92ca3a7b5a2732ca3291072fa4484c60/Screenshot_2020-08-26_at_11.50.56.png%3Fq%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8XXy66Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/20tj2zEsnpv58Ol6KhNptJ/92ca3a7b5a2732ca3291072fa4484c60/Screenshot_2020-08-26_at_11.50.56.png%3Fq%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Now you have an intelligent chatbot customers can talk to. This way customers do not need to go through the extensive list on a website searching for a simple answer. You can also measure the sentiments of the customers and their frequent questions. With this additional information, we are empowered to improve our FAQs. &lt;/p&gt;

&lt;h3&gt;
  
  
  Future enhancements for our chatbot
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Our content model can be extended to add multiple questions for a single answer, and for every entry, additional metadata could be added. For this we have to update the sync functionality within the &lt;strong&gt;publishWebhookHandler&lt;/strong&gt; function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contentful also provides different &lt;a href="https://www.contentful.com/developers/docs/concepts/locales/"&gt;localization&lt;/a&gt; patterns, which can be used to create content in multiple languages, allowing us to power multilingual bots. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In this article, we explored using the Microsoft tech stack, but similar things can be achieved using &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; and &lt;a href="https://aws.amazon.com/lex/"&gt;AWS Lex&lt;/a&gt;. Any chatbot platform that provides API access to their knowledge base can be used with Contentful. Using webhooks and cloud functions allow us to push content into a knowledge base and train it afterward.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’re looking to implement your own conversational chatbot, you can &lt;a href="https://www.contentful.com/get-started/"&gt;sign up&lt;/a&gt; and try it for yourself. Feel free to contact us. Our solution services team works with enterprise customers to accelerate their digital journey.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>contentful</category>
      <category>bots</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Meet the Example App - your kick-off point for developing with Contentful</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Tue, 04 Dec 2018 00:00:00 +0000</pubDate>
      <link>https://forem.com/contentful/meet-the-example-app---your-kick-off-point-for-developing-with-contentful-5dmn</link>
      <guid>https://forem.com/contentful/meet-the-example-app---your-kick-off-point-for-developing-with-contentful-5dmn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp2b6cwwl4lzjpulnz2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp2b6cwwl4lzjpulnz2d.png" alt="The Example App" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As developers, we know that selecting the best stack for the job is never easy. That’s why we at Contentful have been working hard to give you The Example App — a series of reference applications that walks you through the basics of Contentful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Example App — realize your project with Contentful
&lt;/h2&gt;

&lt;p&gt;We built The Example App to help you answer two questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can I realize my project with Contentful?&lt;/li&gt;
&lt;li&gt;How do I get started using my favorite language?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By experimenting with The Example App, you will learn how Contentful’s content infrastructure is ideal for your next project and how you can write an app that works to retrieve content from your space in Contentful.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/3b6BceQe3e0SYCoGeCgsUe/f524b981028278d2a5e430ad5e192aa9/exampleapp_img0.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/3b6BceQe3e0SYCoGeCgsUe/f524b981028278d2a5e430ad5e192aa9/exampleapp_img0.png" alt="The Example App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use your favorite language to learn about Contentful
&lt;/h3&gt;

&lt;p&gt;The Example App has introductory examples for how to build an application with Contentful using &lt;a href="https://the-example-app-csharp.contentful.com/" rel="noopener noreferrer"&gt;.NET&lt;/a&gt;, &lt;a href="https://the-example-app-java.contentful.com/" rel="noopener noreferrer"&gt;Java&lt;/a&gt;, &lt;a href="https://the-example-app-nodejs.contentful.com/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;, &lt;a href="https://the-example-app-rb.contentful.com/" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt;, &lt;a href="https://the-example-app-py.contentful.com/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://the-example-app-php.contentful.com/" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;, &lt;a href="https://github.com/contentful/the-example-app.kotlin" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt;, and &lt;a href="https://github.com/contentful/the-example-app.swift" rel="noopener noreferrer"&gt;Swift&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get up to speed on content modeling
&lt;/h3&gt;

&lt;p&gt;Content modelling is the practice of structuring your content. At Contentful, you do this by creating content types. The Example App features a thorough lesson on how to build a content model to fit your project. You can even get a working example of &lt;a href="https://play.google.com/store/apps/details?id=com.contentful.tea.kotlin" rel="noopener noreferrer"&gt;Example App to-go on your Android device&lt;/a&gt; as well as &lt;a href="https://itunes.apple.com/us/app/contentful-reference/id1333721890?mt=8" rel="noopener noreferrer"&gt;for iOS on the App Store&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Highlights
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Learn about our APIs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Contentful follows an API-first approach, which means that all of its functionality is provided by APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/wL7xWUj6Eg2OasYceWGWe/00a38690a665cfd4ec7dd113adf65d6f/exampleapp_img1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/wL7xWUj6Eg2OasYceWGWe/00a38690a665cfd4ec7dd113adf65d6f/exampleapp_img1.png" alt="Working with The Example App and Contentful APIs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify data schemas or configure a webhook through the &lt;a href="https://www.contentful.com/developers/docs/references/content-management-api/" rel="noopener noreferrer"&gt;Content Management API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deliver cross-channel content through the &lt;a href="https://www.contentful.com/developers/docs/references/content-delivery-api/" rel="noopener noreferrer"&gt;Content Delivery API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Preview unpublished content through the &lt;a href="https://www.contentful.com/developers/docs/references/content-preview-api/" rel="noopener noreferrer"&gt;Content Preview API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Resize, crop, or re-compress images through the &lt;a href="https://www.contentful.com/developers/docs/references/images-api/" rel="noopener noreferrer"&gt;Images API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Develop an app for mobile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With our Example App available in so many languages, you can choose to dive into developing a mobile-first solution to retrieve and serve your content. Let’s say you wanted to develop for Android — &lt;a href="https://github.com/contentful/the-example-app.java" rel="noopener noreferrer"&gt;our GitHub repo&lt;/a&gt; provides you with everything you need to load into your Java SDK.&lt;/p&gt;

&lt;p&gt;However, Java is legacy for building Android apps at this point and Kotlin is the future. Kotlin comes recommended by Google for writing Android apps since it’s a less verbose, concise language than Java. What do you do now? Lucky for you, we’re always striving ahead to support all sorts of (modern) ways for you to work and we’ve got you covered with &lt;a href="https://github.com/contentful/the-example-app.kotlin" rel="noopener noreferrer"&gt;a repo for The Example App in Kotlin&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://play.google.com/store/apps/details?id=com.contentful.tea.kotlin" rel="noopener noreferrer"&gt;Android version of The Example App&lt;/a&gt; is written in Kotlin and will be what the app would look like out-of-the-box from the GitHub repo; development of a variant of the Example App that uses GraphQL is also &lt;a href="https://github.com/contentful/the-example-app.graphql.kotlin" rel="noopener noreferrer"&gt;underway&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the app on the store and source code on GitHub, you can very easily learn how to combine new tech such as Kotlin, architecture components, and more to be integrated into your app using Contentful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone The Example App
&lt;/h2&gt;

&lt;p&gt;While theory is great, nothing beats learning from code examples. Head over to &lt;a href="https://github.com/contentful/" rel="noopener noreferrer"&gt;Contentful’s GitHub page&lt;/a&gt; to find The Example App’s GitHub repo in the language of your choice, and start modifying our examples to your liking.&lt;/p&gt;

&lt;p&gt;If you’re looking for a higher-level overview to get your bearings and go in the right direction, we also have &lt;a href="https://www.contentful.com/blog/2018/04/09/foolproof-guide-getting-started-contentful/?utm_campaign=example-app-introduction&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=example-app-introduction&amp;amp;utm_term=" rel="noopener noreferrer"&gt;this foolproof guide on how to get started with Contentful&lt;/a&gt; that you can check out before progressing to the Example App.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>app</category>
    </item>
    <item>
      <title>A loosely-coupled approach to creating MailChimp campaigns with Contentful webhooks</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Fri, 30 Nov 2018 00:00:00 +0000</pubDate>
      <link>https://forem.com/contentful/a-loosely-coupled-approach-to-creating-mailchimp-campaigns-with-contentful-webhooks-18ne</link>
      <guid>https://forem.com/contentful/a-loosely-coupled-approach-to-creating-mailchimp-campaigns-with-contentful-webhooks-18ne</guid>
      <description>&lt;p&gt;Liquid syntax error: 'raw' tag was never closed&lt;/p&gt;
</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>mailchimp</category>
      <category>email</category>
    </item>
    <item>
      <title>More than just serving JSON – five requirements of your next API-first CMS</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Thu, 29 Nov 2018 00:00:00 +0000</pubDate>
      <link>https://forem.com/contentful/more-than-just-serving-json--five-requirements-of-your-next-api-first-cms-4606</link>
      <guid>https://forem.com/contentful/more-than-just-serving-json--five-requirements-of-your-next-api-first-cms-4606</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnngv9wlngewtbebbgn5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnngv9wlngewtbebbgn5.jpg" alt="How to automatically watermark Contentful assets using a serverless function" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When your job is to build digital products, sooner or later you will have the urge to provide your co-workers with a way to edit content within your product. In the case of most websites, traditional CMS systems out there tend to do the job just fine. It becomes trickier when you have to ship the same content to different products running on different platforms using different technology stacks. &lt;/p&gt;

&lt;p&gt;In case your CMS stores (and serves) content as HTML, you won’t be able to port the content to other technologies easily. Platforms such as iOS and Android can not deal with HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4iwgP1YjGMSa6QW4GQsQ6a/aac53070cc35aba4c2a432317500e911/image_0.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4iwgP1YjGMSa6QW4GQsQ6a/aac53070cc35aba4c2a432317500e911/image_0.png" alt="If your CMS stores and serves content as HTML you cannot port the content to other technologies easily"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The solution to this problem is to serve structured JSON rather than HTML. After all, JSON is the language of the web, and all the common platforms support it which is a big advantage.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Cool! It’s just an HTTP GET then? I can create a JSON API based on content in a database, and I’m all good, right?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not entirely – it is true that you can start with "just an API" that returns JSON to achieve quick results; but in the long run, &lt;strong&gt;management of cross-platform and scalable content means way more than “just serving JSON”&lt;/strong&gt;. That’s when a headless or API-first CMS comes into play. These systems provide you not just with a scalable JSON API, but also an interface to edit your content and ways to evolve your content – a complete content infrastructure so to say.&lt;/p&gt;

&lt;p&gt;In this article, I will share five API-first CMS requirements that you should watch out for if you're going to master the problem of scalable future-proof content.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your CMS provides the content flexibility you need to move fast!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;_You should be able to setup content structures that fit your needs in minutes. _&lt;/strong&gt; When people think of a CMS, they always think of these massive systems you have to install yourself, that are structured in pages and include giant WYSIWYG editors ("&lt;strong&gt;W&lt;/strong&gt;hat &lt;strong&gt;y&lt;/strong&gt;ou &lt;strong&gt;s&lt;/strong&gt;ee &lt;strong&gt;i&lt;/strong&gt;s &lt;strong&gt;w&lt;/strong&gt;hat &lt;strong&gt;y&lt;/strong&gt;ou &lt;strong&gt;g&lt;/strong&gt;et"). One field or text area doesn’t provide enough structure to use the same content source everywhere and across distribution channels! Your system has to give you the ability to &lt;a href="https://www.contentful.com/r/knowledgebase/topics-and-assemblies/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=" rel="noopener noreferrer"&gt;break down all these big content blobs into small reusable pieces&lt;/a&gt; to really succeed in multi-channel delivery.&lt;/p&gt;

&lt;p&gt;You have to understand that content management is not about pages and certainly not about one WYSIWYG editor of a website, but rather about enabling digital experiences based on small building blocks. It may be that you’re building a small web app and want to include an editable announcements section, or that you want to manage copy for your mobile app – all these content pieces are use cases for a CMS. &lt;/p&gt;

&lt;p&gt;You have to be able to create, add and edit reusable content structures quickly – no matter how small or complex they are. You’ll never know which content requirements your next project will bring and your CMS setup should not become the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2T63K9uWNOEq8G4saEAAQM/d521ee3a3951012ca681efc3d6bb3f35/image_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2T63K9uWNOEq8G4saEAAQM/d521ee3a3951012ca681efc3d6bb3f35/image_1.png" alt="You have to be able to create, add and edit reusable content structures quickly"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All these content structures will evolve into a big picture, so they have to be able to be connected, too — all your content has to become a graph to scale!&lt;/p&gt;

&lt;p&gt;Let’s take content type for a collection of music as an example. A song probably was created by an artist, and this artist has written several songs throughout their career. To avoid content duplication in every song entry, you should be using two different content types here – a "song" type and an “artist” type. &lt;/p&gt;

&lt;p&gt;All entries from both the content types can then be related to one another — so that when you update an artist, your changes will be available to all other entries that reference it.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/356iPO7pduqseyK6KKcY4W/8dc72a301426b4d553eae6d73fb9f2c9/image_2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/356iPO7pduqseyK6KKcY4W/8dc72a301426b4d553eae6d73fb9f2c9/image_2.png" alt="All entries from both the content types can be related to one another"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a content graph structure, you can request particular nodes but also a sum of entries due to them being connected with one another. It’s up to you, but &lt;strong&gt;your CMS should give you the flexibility to fetch whatever data you need&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Your CMS treats rich text as JSON!
&lt;/h2&gt;

&lt;p&gt;When you have granular content structures to avoid duplication, the next step is to make sure your content works on every platform. To achieve that, you have to follow the rule of &lt;em&gt;separation of concerns&lt;/em&gt;. Separation of concerns defines that you break down pieces of your applications into distinct sections so that each section really only addresses one concern. &lt;/p&gt;

&lt;p&gt;Your CMS’ job is to enable you to create and edit content – nothing else! The styling and representation of this content is a different concern and should be dealt with by another part of your application. Whenever platform-specific content like HTML goes into your content to apply styles, you’re breaking the model of separation of concerns — and, trust me, this will give your future-self many headaches when you decide to serve that same piece of content to a different product or platform later!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A JSON object with fields including HTML is not good enough – forget systems that produce HTML!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One possible alternative is Markdown, which achieves the separation of concerns nicely and works well for writing articles on your blog, but it comes with three significant disadvantages when dealing with more complex content structures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Markdown’s feature set is limited.
&lt;/h3&gt;

&lt;p&gt;Markdown bets strongly on semantics but the content that developers have to deal with today has additional requirements. It misses formatting options such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Underline (yes, it’s achievable on a web page via CSS but there is no HTML equivalent for underlining which leads to the lack of an underline option in Markdown — try explaining this to a content creator...)&lt;/li&gt;
&lt;li&gt;Ability to embed video and several other formats of content&lt;/li&gt;
&lt;li&gt;Options to describe components (for example, an accordion) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could potentially make all these work in Markdown, but the fact remains that it is not the right tool for the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Markdown is not capable of representing relationships
&lt;/h3&gt;

&lt;p&gt;JSON is 100% structured — one object includes another object includes another object. Let’s have a look at a possible API response for an entry:&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;"fields"&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="err"&gt;“A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;title”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&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;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Jane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Doe”&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"artist"&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;"fields"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;“Jane&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Doe”&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;What you see above is structured JSON describing a song with a &lt;code&gt;title&lt;/code&gt;, a &lt;code&gt;body&lt;/code&gt; written in Markdown, and a reference to another object which represents the &lt;code&gt;artist&lt;/code&gt; of the song. &lt;/p&gt;

&lt;p&gt;Cases like this are where relationships between entries shine. Since each song includes a reference to the specific artist who made it and other songs, should you update the name of the artist, that change will be reflected in every entry referencing said artist.&lt;/p&gt;

&lt;p&gt;On the other hand, content stored in Markdown serves only as a long string that does not allow references to other entries, which makes maintaining a lot of content very hard and prone to errors. &lt;/p&gt;

&lt;h3&gt;
  
  
  Markdown allows HTML content
&lt;/h3&gt;

&lt;p&gt;Markdown allowing HTML input might not seem like a big deal, but if people get the chance to customize their content using HTML, they will do it and break your apps. It will happen, and you have to make sure that it won’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;There should be no way to mess up that cross-platform portability!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Rich content editing has to be JSON based!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4JDYcz8RgkGQMmO6W8akSe/0c730245815c1b18462ae39d1a821231/image_3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4JDYcz8RgkGQMmO6W8akSe/0c730245815c1b18462ae39d1a821231/image_3.png" alt="Use JSON-based rich content editing to succeed with complex data structures"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To succeed with complex data structures, &lt;a href="https://www.contentful.com/developers/docs/tutorials/general/getting-started-with-rich-text-field-type/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=" rel="noopener noreferrer"&gt;rich content editing&lt;/a&gt; should also be JSON-based. This way, editors can reference other entries right within text areas, and yes, in the best case they can underline words without breaking your systems, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Your CMS allows you to make your content creators feel safe!
&lt;/h2&gt;

&lt;p&gt;Once you’ve made it this far and all your content is JSON-based, the next question is how to preview the content. An editorial preview mechanism is crucial for editorial teams but how is that accomplished when you have only one JSON API?&lt;/p&gt;

&lt;p&gt;To build a proper preview environment, you need the option to see unpublished content changes. &lt;strong&gt;It’s time for a second API – a preview API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With this second API, &lt;a href="https://www.contentful.com/developers/bits-and-bytes/#creating-a-preview-environment-using-the-preview-api?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=" rel="noopener noreferrer"&gt;you can set up different environments&lt;/a&gt; in which editors can check content and the changes they make to it before publishing.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4GnA6BxoQMyeoOMIwGWI6u/ac2dc5375fbceda6f4c22abbef3d4733/image_4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4GnA6BxoQMyeoOMIwGWI6u/ac2dc5375fbceda6f4c22abbef3d4733/image_4.png" alt="Preview API lets  editors can check content and the changes they make to it before publishing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your editors have to feel safe and comfortable working with the system you provide – and a preview mechanism is a big part of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Your CMS gives you the tools to evolve fearlessly!
&lt;/h2&gt;

&lt;p&gt;Coming from traditional CMSes, developers are used to having an exact local copy of the production environment (that includes the production database) on their development machine. When making a structural change, they tend to write an SQL script, test it locally and apply it to production once they feel good about it – but how can you follow this workflow when your content isn’t stored in your databases but only accessible via an API?&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4KcZ2xvEE0meUsmasyICQK/21fb706b6e2c7c991c3675d983b55b9a/image_5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4KcZ2xvEE0meUsmasyICQK/21fb706b6e2c7c991c3675d983b55b9a/image_5.png" alt="Workflow requirements"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This workflow leads to two main requirements for your content infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ability to make a copy of the production content&lt;/li&gt;
&lt;li&gt;The ability to script content changes at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making copies of your production content
&lt;/h3&gt;

&lt;p&gt;At Contentful, you can create copies of your "master content", which you can access under a slightly different URL — we call these copies &lt;a href="https://www.contentful.com/faq/environments/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=" rel="noopener noreferrer"&gt;sandbox environments&lt;/a&gt;. There, you can create, change and delete them without affecting any production data; providing you with safety to develop content changes locally.&lt;/p&gt;

&lt;p&gt;Using a combination of your environment ID and the space ID, you can access its contents to build production, staging or local environments:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdn.contentful.com/spaces/{space_id}/environments/{environment_id}/entries

cdn.contentful.com/spaces/.../environments/master/entries
cdn.contentful.com/spaces/.../environments/my-new-feature/entries
~~~

A sandbox environment is an equivalent to the SQL database dump of traditional systems. The next question is – how do you write database scripts in this new world?

### Automated migration tooling

First of all, to enable developers to script content changes, **you need a third API to write data.** Unfortunately, barebones API endpoints with write access are not enough. Developers want to write scripts quickly and not deep-dive into documentation about API endpoints.

![Developers want to write scripts quickly and Contentful lets them do exactly](//images.ctfassets.net/fo9twyrwpveg/5Vs3xFOhwIs2ekM6ksWE0q/77a9b44443dc7e374172e83bddb4d680/image_6.png)

Defining scripts on top of API-endpoints usually will result in a custom solution. At Contentful we decided to make [content migrations a first-class citizen of our ecosystem](https://github.com/contentful/contentful-migration) to give developers a straightforward way to make changes to their content!

### CMS as Code workflow

With the combination of sandbox environments and migration scripts, you can handle your CMS the same way you would [treat the source code and database running in your application](https://www.contentful.com/r/knowledgebase/cms-as-code/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=). 

![Combination of environment sandboxes lets you work more effectively](//images.ctfassets.net/fo9twyrwpveg/5Egtp2Qs8ggiK6GQ6e2cmA/f73a584674aa10c7b8f08dedd613f499/image_7.png) 

You can create development workflows to spin up environments for certain features and run tests against them, or simply to have staging or QA environments, and also automate your CI pipeline. There are many instances of large-scale companies [implementing](https://www.contentful.com/blog/2018/09/13/content-model-changes-scale-telus-cms-as-code/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=)[ developer workflows successfully with Contentful.](https://www.contentful.com/blog/2018/09/13/content-model-changes-scale-telus-cms-as-code/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=)

## 5. Your CMS plays well with other vendors!

The developer ecosystem today consists out of very specialized services. There are solutions for continuous integration, commerce, translations; the list goes on and on. The content that will power your products will most likely have some touchpoints with other services in your stack. 

Your API-first CMS has to be ready to integrate nicely with other software-as-a-service (SaaS) products.

![Integrate your API-first CMS easily with other SaaS solutions with Contentful](//images.ctfassets.net/fo9twyrwpveg/7ePP9kr7eEywcIAsQ0WcgM/7e40552b83714f0a9b7d476e0e169453/image_8.png)

### An extendable editor interface

The possibility to configure the editor interface is highly essential to boost the productivity of content creators. Assuming you use different APIs in your product, you also might want to create short paths to, for example, retrieve [data from your commerce platform](https://www.contentful.com/developers/marketplace/contentful-ui-shopify/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=), [control AB-tests](https://blog.optimizely.com/2018/02/13/experimentation-content-creation-process/) or [translate words](https://www.contentful.com/developers/marketplace/translate/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=) right within your CMS.

### Flexible and adjustable webhooks

Additionally, you want to be able to notify other parts of your infrastructure whenever someone adds or deletes content. A common way to solve this is webhooks — requests that a cloud service sends to a URL of your choice.

Not every webhook implementation is the same and connecting different services can initially be more difficult than it seems. However, with particular webhook features such as the ability to define custom headers, control webhook actions at a very granular level and adjust the payload of webhooks you will make your API-providers talk to each other and you’ll be able to [connect services directly](https://www.contentful.com/developers/docs/tutorials/general/enhancing-search-experience-with-algolia/?utm_campaign=requirements-api-first-cms-json&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=requirements-api-first-cms-json&amp;amp;utm_term=) to save work and additional infrastructure.

## It’s more than one JSON API

As you’ve witnessed, in order to succeed with all the great content shipped in your products, it needs a little bit more than one scalable JSON API on top of a database as your CMS.

On an API-level, here’s what you need at minimum:

- a scalable cached API to serve your sites and apps
- a preview API to allow you to build environments for editors to preview their changes
- a write or management API that lets you perform automated content changes

*Tip: Bonus points if there is a way to automatically resize and crop images, too.*

Additionally, the content structures and editing experience are just as important as the APIs themselves, but if you’re dealing with a lot of content, **_your CMS should provide you with solutions to change content that’s already in your production system_**. There is nothing worse than being stuck and not being able to move forward anymore – this is a point that is often overlooked. 

With that word – choose your API-first CMS wisely. :)

(You can find a more detailed look at what features your CMS should have in the slides to the talk ["When a CMS is not enough – tales from a content infrastructure"](https://speakerdeck.com/stefanjudis/when-a-cms-is-not-enough)).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>serverless</category>
      <category>cms</category>
      <category>json</category>
    </item>
    <item>
      <title>Writing GraphQL queries in native Ruby = Love ❤️</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Tue, 20 Nov 2018 00:00:00 +0000</pubDate>
      <link>https://forem.com/contentful_blog/writing-graphql-queries-in-native-ruby--love--3kn5</link>
      <guid>https://forem.com/contentful_blog/writing-graphql-queries-in-native-ruby--love--3kn5</guid>
      <description>&lt;p&gt;GraphQL has made waves with front end developers, but if you’re a backend developer, chances are it hasn’t infiltrated your world –– you might not even know what it is. And it’s not your fault. There has been a limited amount of blog posts, talks and information around using GraphQL APIs on languages that don’t have an official Apollo client.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to automatically watermark Contentful assets using a serverless function</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Wed, 14 Nov 2018 13:57:28 +0000</pubDate>
      <link>https://forem.com/contentful_blog/how-to-automatically-watermark-contentful-assets-using-a-serverless-function-6j3</link>
      <guid>https://forem.com/contentful_blog/how-to-automatically-watermark-contentful-assets-using-a-serverless-function-6j3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdiuau0nemv1j8gkonu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdiuau0nemv1j8gkonu5.png" alt="How to automatically watermark Contentful assets using a serverless function" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We made it friends!&lt;/em&gt; We finally reached the point where developers can deploy HTTP endpoints without worrying about infrastructure. You might have guessed it, and you’re right – I’m talking about the serverless buzzword! The big infrastructure players; AWS, Microsoft, IBM, and others all provide functions as a service these days. Serverless functions make it possible for you to write 100 lines of code, test it and ship it – 100% scalable and doesn’t cost you a single dollar whenever the deployed code is not running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is an HTTP endpoint such a big deal?
&lt;/h3&gt;

&lt;p&gt;The industry is shifting towards specialized services to solve specific problems in ways that are better and quicker than I (and probably you) could do it when starting to build solutions today. You sign up to a service of choice and can start using it – saving you weeks, if not months, of developing software that doesn’t belong to your core product. How many times have you built a login mechanism in your software career? For me, it’s been many, many times...&lt;/p&gt;

&lt;p&gt;The question arises how you can automate certain things when you are not in control of the source code of all the services that can be in your future technology stack. The answer to this is two core components – APIs and webhooks. &lt;/p&gt;

&lt;p&gt;In case you haven’t worked with them previously, webhooks are HTTP requests sent by a service to a defined HTTP endpoint. These requests allow you to connect one service with another, and then combine all the small functionality pieces into something bigger, greater, and more powerful.&lt;/p&gt;

&lt;p&gt;However, before you go out and subscribe to a gazillion software-as-a-service (SaaS) products, let me give you a bit of advice. Not every cloud service that provides an API is the same and not every webhook implementation is useful. You should check for two main characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The provided API has to include read, create and update functionalities&lt;/li&gt;
&lt;li&gt;Webhooks have to be secure, filterable and adjustable &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, I want to describe how you can implement a webhook flow to improve your productivity when working with images in Contentful. Let’s assume that you’re dealing with photos that should be served to your applications as a watermarked version which includes your company logo. Your content creators could manually add watermarks each time on every image, but you could also automate this task away to save time and effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main flow – webhooks triggering serverless functions
&lt;/h2&gt;

&lt;p&gt;Here’s the plan: whenever someone creates a new asset and uploads an image in Contentful, a webhook is sent. This webhook triggers a serverless function that downloads the uploaded image. The composition of this image with, for instance a company logo, will then be re-uploaded to Contentful as the watermarked version.&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/7KhQyRQNMsmkSGuemeAESa/ffc2a891b9d54d0b51f8d890fe670a26/serverless-flow.jpg" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/7KhQyRQNMsmkSGuemeAESa/ffc2a891b9d54d0b51f8d890fe670a26/serverless-flow.jpg" alt="The flow with webhooks and serverless functions for image watermarking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way you get to both keep the original images and save the work of modifying these yourself. Sounds good? Let’s do it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started with the serverless framework
&lt;/h3&gt;

&lt;p&gt;To start developing functions that run in the cloud, &lt;a href="https://serverless.com/" rel="noopener noreferrer"&gt;the serverless framework&lt;/a&gt; is always my first choice. It gives you a nice abstraction layer on top of the functionality provided by all the infrastructure providers. You can develop and deploy functions right from your machine, which is way more comfortable than what’s usually provided by online editors.&lt;/p&gt;

&lt;p&gt;As a first step, head over to &lt;a href="https://serverless.com/framework/docs/getting-started/" rel="noopener noreferrer"&gt;their getting started guide&lt;/a&gt;. It explains how you install the &lt;code&gt;serverless&lt;/code&gt; executable and what you need to configure. When everything is set it takes you one command to create a project that is ready to deploy to AWS.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;serverless create &lt;span class="nt"&gt;--template&lt;/span&gt; aws-nodejs
~~~&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;

This &lt;span class="nb"&gt;command &lt;/span&gt;above creates only two files &lt;span class="k"&gt;for &lt;/span&gt;you, with &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;serverless.yml&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; being the main configuration file &lt;span class="k"&gt;for &lt;/span&gt;your serverless projects. Its initial creation includes lots of documentation and comments, but not many configuration parameters and values will be defined &lt;span class="k"&gt;in &lt;/span&gt;it at this point.&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;

~~~yaml
service: aws-nodejs &lt;span class="c"&gt;# NOTE: update this with your service name&lt;/span&gt;

&lt;span class="c"&gt;# define environment&lt;/span&gt;
provider:
  name: aws
  runtime: nodejs8.10
&lt;span class="c"&gt;# define available functions&lt;/span&gt;
functions:
  hello:
    handler: handler.hello
~~~&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;

The &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;serverless.yml&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; defines a service name &lt;span class="o"&gt;(&lt;/span&gt;this is how you’ll identify it &lt;span class="k"&gt;in &lt;/span&gt;AWS later&lt;span class="o"&gt;)&lt;/span&gt;, you can configure the environment &lt;span class="k"&gt;in &lt;/span&gt;AWS &lt;span class="o"&gt;(&lt;/span&gt;Node.js &lt;span class="k"&gt;in &lt;/span&gt;version 8.10&lt;span class="o"&gt;)&lt;/span&gt;, and it lists the functions that should be available later. Node.js version 8 means that you can use recent additions to the language like &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;async/await&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; which makes &lt;span class="nb"&gt;source &lt;/span&gt;code so much more readable!

The other file &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;handler.js&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; exports a &lt;span class="k"&gt;function &lt;/span&gt;that is ready to be deployed returning a &lt;span class="s2"&gt;"hello world"&lt;/span&gt; JSON example message.&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;

~~~javascript
&lt;span class="s1"&gt;'use strict'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

module.exports.hello &lt;span class="o"&gt;=&lt;/span&gt; async &lt;span class="o"&gt;(&lt;/span&gt;event, context&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;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    statusCode: 200,
    body: JSON.stringify&lt;span class="o"&gt;({&lt;/span&gt;
      message: &lt;span class="s1"&gt;'Go Serverless v1.0! Your function executed successfully!'&lt;/span&gt;,
      input: event,
    &lt;span class="o"&gt;})&lt;/span&gt;,
  &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
~~~&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;### Make the function executable via HTTP&lt;/span&gt;

While these two files are enough to deploy your first &lt;span class="k"&gt;function &lt;/span&gt;to AWS, this &lt;span class="k"&gt;function &lt;/span&gt;isn’t connected to an HTTP endpoint yet and thus not accessible from the outside. To make that happen, you have to add your first custom configuration to the &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;serverless.yml&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; 

In AWS, &lt;span class="o"&gt;[&lt;/span&gt;API gateway]&lt;span class="o"&gt;(&lt;/span&gt;https://aws.amazon.com/api-gateway/&lt;span class="o"&gt;)&lt;/span&gt; handles the configuration of HTTP endpoints. The cool thing about the serverless framework is that it provides ways to configure endpoints without jumping between all the different pages and settings &lt;span class="k"&gt;in &lt;/span&gt;the AWS console.

Events triggered by different services of the infrastructure are responsible &lt;span class="k"&gt;for function &lt;/span&gt;execution &lt;span class="k"&gt;in &lt;/span&gt;AWS. HTTP, &lt;span class="k"&gt;in &lt;/span&gt;this &lt;span class="k"&gt;case&lt;/span&gt;, is only one of them but is the foundation to build serverless &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="nt"&gt;-driven&lt;/span&gt; APIs. 

By adding the configuration &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;events&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; to the particular &lt;span class="k"&gt;function&lt;/span&gt;, the serverless framework configures API gateway &lt;span class="k"&gt;for &lt;/span&gt;you and make this &lt;span class="k"&gt;function &lt;/span&gt;available on the internet.&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;

~~~yaml
&lt;span class="c"&gt;# … service configuration&lt;/span&gt;
&lt;span class="c"&gt;# … provider configuration&lt;/span&gt;

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
~~~&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;

You can now deploy your new &lt;span class="k"&gt;function &lt;/span&gt;using &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;serverless deploy&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;.&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;

&lt;p&gt;$ serverless deploy&lt;br&gt;
[15:38:58]&lt;br&gt;
Serverless: Packaging service...&lt;br&gt;
Serverless: Excluding development dependencies...&lt;br&gt;
Serverless: Creating Stack...&lt;br&gt;
Serverless: Checking Stack create progress...&lt;br&gt;
Serverless: Stack create finished...&lt;br&gt;
Serverless: Uploading CloudFormation file to S3...&lt;br&gt;
Serverless: Uploading artifacts...&lt;br&gt;
Serverless: Uploading service .zip file to S3 (387 B)...&lt;br&gt;
Serverless: Validating template...&lt;br&gt;
Serverless: Updating Stack...&lt;br&gt;
Serverless: Checking Stack update progress...&lt;br&gt;
Serverless: Stack update finished...&lt;br&gt;
Service Information&lt;br&gt;
service: hello-world&lt;br&gt;
stage: dev&lt;br&gt;
region: us-east-1&lt;br&gt;
stack: hello-world-dev&lt;br&gt;
api keys:&lt;br&gt;
  None&lt;br&gt;
endpoints:&lt;br&gt;
  GET - &lt;a href="https://lbrdiu0h92.execute-api.us-east-1.amazonaws.com/dev/hello" rel="noopener noreferrer"&gt;https://lbrdiu0h92.execute-api.us-east-1.amazonaws.com/dev/hello&lt;/a&gt;&lt;br&gt;
functions:&lt;br&gt;
  hello: hello-world-dev-hello&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
The deployment logs give you information of where the endpoint is deployed so that you can open it in your browser and see the JSON response immediately.

{: img }
![Change the method name and HTTP method to POST](//images.ctfassets.net/fo9twyrwpveg/6ozEksTGfeQEmsyCqAWY68/76fe98aa88e169e04ddefea09a35f791/image_1.png)

As a last preparation step, let’s change the method name from {% raw %}`hello` to `watermark` and also use the HTTP method `POST` instead of `GET`.

~~~yaml
# … service configuration
# … provider configuration

functions:
  watermark:
    handler: handler.watermark

    events:
      - http:
          path: /watermark
          method: post
~~~

These two files and a few lines of additional configuration are all it takes to lay the foundation for an endpoint that is capable of handling Contentful webhooks to watermark images and re-upload them.

### Tailor the webhook to your needs

In Contentful you can define a new webhook that will be sent whenever you publish a new asset.

{: img }
![Define a new webhook to be sent after publishing a new asset](//images.ctfassets.net/fo9twyrwpveg/fFnXAMHeRaSk8ecsyUESC/963f5b6b2547bdc49380957affce42d2/image_2.png)

What is cool is that you can define custom payloads in the webhooks configuration. Using JSON pointers, you can slim down the payload and avoid complexity in your functions because it sends only the data you’re interested in.

Adjustable webhooks payloads are also critical when you want to connect two services directly because it might be that a service only accepts requests with particular headers or payloads.

{: img }
![The definition of the entire webhook payload sent to the serverless function](//images.ctfassets.net/fo9twyrwpveg/20MQxKhM4YqI24aS2kyqQK/73c012e719a0cb9d90e51795db31e836/image_3.png)

What you see above is the definition of the entire webhook payload sent to the serverless function. The syntax of JSON pointers (`{ /payload/… }`) makes it possible to access the original payload and restructure everything to your requirements. In this example, you’re going to use the following:

* The URL of the uploaded images to download it in the serverless function
* The filename of this file, to be used as a base for the filename of the watermarked image
* The content type of the uploaded file to only run watermarks for jpegs
* The width and height of the image so to not watermark images that are too small
* The title and description of the uploaded asset to be reuse in the watermarked asset

## Manipulating images in a serverless function using Jimp

Here comes the fun part... image manipulation often requires you to install native dependencies, which can be a little bit tricky when dealing with serverless functions. The [Jimp package](https://github.com/oliver-moran/jimp) is handy in this case because it performs image manipulations entirely in JavaScript. The manipulation is probably a little bit slower than a native implementation, but you don’t have to deal with the troubles of installing native dependencies on AWS.

### Adding npm dependencies to your serverless project

You can add a `package.json` to the project root with `npm init --yes` and then install the package as a dependency with `npm install --save jimp`. When you later deploy the project, the serverless framework scans your project and figures out what dependencies need to be included in the package that goes to AWS. It even excludes development dependencies to keep the package as small as possible. It’s honestly very cool!

You can happily use the Jimp package and manipulate images in your Lambda code.

~~~javascript
const original = await Jimp.read(url).opacity(0.4);
~~~

Your project structure should at this point look as follows:

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

&lt;/div&gt;

&lt;p&gt;handler.js&lt;br&gt;
serverless.yml&lt;br&gt;
package.json&lt;br&gt;
package-lock.json&lt;br&gt;
node_modules/&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### The composition of two images with Jimp

The watermark image creation can be divided into four parts:

* Read the original image that should be watermarked
* Read the image that acts as the watermark
* Make the watermark image transparent
* Combine both images

Luckily all these steps are reflected in the methods provided by Jimp.{% raw %}

~~~javascript
// step 1
const original = await Jimp.read('https://url/to/image');
// step 2
const mark = await Jimp.read('https://url/or/filepath/to/mark); 
// step 3
mark.opacity(0.5);
// step 4
const watermarkedImage = await original.composite(mark, 50, 50); 
~~~

What’s very handy about `Jimp.read` is that it accepts file paths and URLs which means that you don’t have to deal with file downloads yourself.

### Read the webhook payload and watermark the uploaded image

Remember the payload we defined in Contentful? This payload is available in the `body` property of the function argument `event` inside of the lambda function. You can combine the passed URL with the Jimp functionality and start watermarking images.

~~~javascript
module.exports.watermark = async (event) =&amp;gt; {
  try {
    console.log(
      `Received webhook: ${JSON.stringify(JSON.parse(event.body), null, 2)}`
    );
    const {
      url,
      title,
      description,
      fileName,
      contentType,
      width,
      height
    } = JSON.parse(event.body);

    // validate and check if a new watermark version
    // has to be created
    if ( contentType !== ‘image/jpeg’) {
      return {
        statusCode: 200,
        message: ‘Nothing to do...'
      }
    }

    // ... 

    const original = await Jimp.read(url);
    const mark = await Jimp.read('https://path/to/mark); 
    mark.opacity(0.5);
    const watermarkedImage = await original.composite(mark, 50, 50); 
  } catch(e) {
    return {
      statusCode: 500,
      error: e.message
    }
  }
~~~

The code snippet above also includes validation steps to not always create new watermarked images and to avoid neverending loops. It does not include the download of the watermark logo to avoid complexity, but you can find the full version [on GitHub.](https://github.com/stefanjudis/serverless-contentful-watermark/blob/master/handler.js)

### Uploading the new watermarked asset to Contentful

To upload the generated image, you can use the [Contentful Management SDK](https://github.com/contentful/contentful-management.js?files=1) which provides you the `createAssetsFromFiles` function to upload images with [the Content Management API](https://www.contentful.com/developers/docs/references/content-management-api/?utm_campaign=automatically-watermark-contentful-assets-using-serverless&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=automatically-watermark-contentful-assets-using-serverless&amp;amp;utm_term=).

~~~javascript
const contentful = require(‘contentful-management’);

const uploadAsset = async ({
  title,
  description,
  fileName,
  contentType,
  stream
}) =&amp;gt; {
  const client = contentful.createClient({ accessToken: ‘...’ });
  const space = await client.getSpace(SPACE_ID);
  const env = await space.getEnvironment('master');
  let asset = await env.createAssetFromFiles({
    fields: {
      title: {
        'en-US': title
      },
      description: {
        'en-US': description
      },
      file: {
        'en-US': {
          contentType,
          fileName,
          file: stream
        }
      }
    }
  });
  asset = await asset.processForAllLocales();
  asset = await asset.publish();
};
~~~

### The overall structure

Reading and validating the JSON payload, downloading and processing of the needed images and uploading a new asset to Contentful are the core parts of this function. The overall implementation for the complete functionality is around 200 lines of code and this article only describes some of the details. If you want to learn more or try it out you can go [the repository on GitHub.](https://github.com/stefanjudis/serverless-contentful-watermark/blob/master/handler.js)

The flow codewise should be as follows:

~~~javascript
Module.exports.watermark = async (event) {
  // read webhook payload
  // validate payload and check if you can skip watermark creation
  // read the image of the published asset
  // read the watermark logo
  // composite both images
  // upload it back to Contentful
}
~~~

## Serverless functions combined with webhooks are the SaaS glue of the future

It doesn’t matter if you use serverless functions to enrich the functionality of a single service like in this example the asset creation of Contentful. Functions are a perfect fit to combine different services, too. 

Think for example of Algolia (a Search SaaS provider). To use their search, you have to index data. With webhooks, you can either push data directly into Algolia or, for complex cases, you can spin up a serverless function that controls which data should go in. Head over to [the docs](https://www.contentful.com/developers/docs/tutorials/general/enhancing-search-experience-with-algolia/?utm_campaign=automatically-watermark-contentful-assets-using-serverless&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=automatically-watermark-contentful-assets-using-serverless&amp;amp;utm_term=) to learn more.

In my opinion, we’ve entered a very bright future with the ability to deploy these function-based HTTP endpoints, you and I can focus on building great products without reinventing the wheel!

### Additional comments and notes

#### How to develop Lambda functions locally?

To develop serverless functions deployed to AWS locally, there is the excellent [serverless-offline package](https://www.npmjs.com/package/serverless-offline) which emulates AWS on your local machine. I highly recommend checking it out.

#### Environment variables

The described use case of re-uploading assets to Contentful requires you to create a management token which has write access to your Contentful space. Be aware that this token should not make it into your version control and should be handled via environment variables (that’s the solution you’ll find in the [provided GitHub repository](https://github.com/stefanjudis/serverless-contentful-watermark)) or configuration files. Take care!

#### Limitations of API gateway

In this example, API gateway triggers the function that deals with image processing. API Gateway currently has a maximum timeout of 30 seconds whereas Lambda functions [can run for up to 15 minutes](https://aws.amazon.com/about-aws/whats-new/2018/10/aws-lambda-supports-functions-that-can-run-up-to-15-minutes/). For processing tasks taking longer than 30s, it is recommended to split this single function into two functions to avoid hitting that timeout. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>serverless</category>
      <category>images</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Build an Ecommerce Static Site with Jekyll, Contentful, and Commerce Layer</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Wed, 14 Nov 2018 13:48:32 +0000</pubDate>
      <link>https://forem.com/contentful_blog/how-to-build-an-ecommerce-static-site-with-jekyll-contentful-and-commerce-layer-3c9</link>
      <guid>https://forem.com/contentful_blog/how-to-build-an-ecommerce-static-site-with-jekyll-contentful-and-commerce-layer-3c9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze17j49ubh5ybzcie320.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze17j49ubh5ybzcie320.png" alt="Build an Ecommerce Static Site with Jekyll, Contentful, and Commerce Layer" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are plenty of guides out there showing how to use static site generators (SSGs) for basic ecommerce stores — with most of them using &lt;a href="https://www.contentful.com/blog/2016/02/10/snipcart-middleman-contentful/?utm_campaign=static-site-jekyll-contentful-commerce-layer&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=static-site-jekyll-contentful-commerce-layer&amp;amp;utm_term=" rel="noopener noreferrer"&gt;Snipcart&lt;/a&gt; to add shopping cart functionalities to a simple product catalog. &lt;/p&gt;

&lt;p&gt;The question is: are static sites a good alternative for enterprise-level ecommerce as well? &lt;/p&gt;

&lt;p&gt;We definitely think so, and this tutorial aims to prove just that.&lt;/p&gt;

&lt;h2&gt;
  
  
  About this tutorial
&lt;/h2&gt;

&lt;p&gt;By reading this guide, you will learn how to build a static site ecommerce with enterprise-level features. The resulting &lt;a href="https://contentful-commerce.netlify.com/" rel="noopener noreferrer"&gt;demo site&lt;/a&gt; will be a multi-country, multi-language online shop with the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each country will have &lt;strong&gt;its own catalogue&lt;/strong&gt; with custom product selection and sorting&lt;/li&gt;
&lt;li&gt;Each country will have &lt;strong&gt;its own price list&lt;/strong&gt; with localized currency&lt;/li&gt;
&lt;li&gt;Each country will have &lt;strong&gt;its own inventory&lt;/strong&gt; shipping from multiple warehouses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of diving into the technical details, we will focus on the site architecture and development workflow. After learning the big picture, we recommend you explore the source code and follow the step-by-step guide that is available on &lt;a href="https://github.com/commercelayer/contentful-commerce" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;To build our demo site, we selected some of the best tools, one for each piece of the site architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; as the SSG&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.contentful.com/?utm_campaign=static-site-jekyll-contentful-commerce-layer&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=static-site-jekyll-contentful-commerce-layer&amp;amp;utm_term=" rel="noopener noreferrer"&gt;Contentful&lt;/a&gt; to manage content&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://commercelayer.io/" rel="noopener noreferrer"&gt;Commerce Layer&lt;/a&gt; as the e-commerce platform&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for code versioning &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; for deployment and content delivery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's worth mentioning that we are going to manage &lt;em&gt;all&lt;/em&gt; content with Contentful, including the product information, categories, and catalogues. Unlike traditional platforms, Commerce Layer is a purely transactional engine that leaves content management to the CMS. This approach lets creatives and content editors design any customer experience, without locking them into any site structure or template system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture and workflow
&lt;/h2&gt;

&lt;p&gt;The picture below outlines how the tools fit together in the overall architecture. As you can see, each tool is dedicated to a specific step of the pipeline, contributing to the content editing and publishing workflow.&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/7wyKYa2crCCCUemOY6sMgY/c0f1164469d6846c880ae45e99af32a6/image_0.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/7wyKYa2crCCCUemOY6sMgY/c0f1164469d6846c880ae45e99af32a6/image_0.png" alt="How the tools fit together in the overall architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's have a look at each step of the diagram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Merchants work on the product offering within Commerce Layer. SKUs - the items that are being sold - are imported into Contentful for content enrichment. Prices and stock information are not imported.&lt;/li&gt;
&lt;li&gt;Content editors get the list of available SKUs and create products within Contentful. Their job is also to assign products to categories, categories to catalogues, and catalogues to countries. Published content is built into the Jekyll site on Netlify.&lt;/li&gt;
&lt;li&gt;Developers work on the site structure, layout, and client-side functions. The code is built into a static site and deployed to Netlify. Each product variant is tagged with the related SKU code, using the &lt;em&gt;data-sku-code&lt;/em&gt; data attribute on any DOM element. This tagging lets the client-side code interact with the Commerce Layer API in real-time. Every time the code changes, developers push the code to Github for versioning and continuous deployment. &lt;/li&gt;
&lt;li&gt;The client-side code fetches prices and inventory for each available SKU. It also adds shopping cart functionalities to the page and sends orders to Commerce Layer. Merchants fulfill orders through the OMS. Newly-created SKUs are sent to Contentful for content enrichment, feeding the same workflow.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Content model
&lt;/h2&gt;

&lt;p&gt;The second step of the workflow is where content editors manage the product catalogue in Contentful. This is a key step that determines how the site works. The diagram below outlines the content model that we designed to support the multi-country, multi-language structure:&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/1kmova7aFEcY0ciqccKi2Y/83875f4d6b9f0c779203ec106d830c12/image_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/1kmova7aFEcY0ciqccKi2Y/83875f4d6b9f0c779203ec106d830c12/image_1.png" alt="Content model that we designed to support the multi-country, multi-language structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's describe each model, their roles and relationships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Countries have one associated catalogue, and the same catalogue can be associated to many countries. The market ID attribute determines the price list, inventory model, payment methods, shipping methods and promotions that are available for that country, as defined within Commerce Layer.&lt;/li&gt;
&lt;li&gt;Catalogues are sorted list of categories. This means that each country can have its own categories with custom sorting.&lt;/li&gt;
&lt;li&gt;Categories can have one or more lists of products by country. Each list has its own sorting, which means the same category can have a custom product merchandising for each country. If content editors don't define any specific list for a given country, the default one will be used.&lt;/li&gt;
&lt;li&gt;Products can have one or more variants. All content that is common to all their variants can be defined at the product level.&lt;/li&gt;
&lt;li&gt;Variants are the items that are being sold. The SKU code attribute links them to the SKUs defined in Commerce Layer and makes the variants shoppable. In our use case, variants can have a size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Continuous delivery
&lt;/h2&gt;

&lt;p&gt;Our static site ecommerce is made of three types of data that need to be refreshed as they change: code, content, and commerce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When code changes&lt;/strong&gt;, developers push the changes to Github, triggering a new build on Netlify. The build command fetches all published content from Contentful (&lt;code&gt;jekyll contentful&lt;/code&gt;) and builds the static site (&lt;code&gt;jekyll build&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2CyRtcsFjekaMYYEMcsKkC/9e2c599dd9f1f63cd852834e1aa11402/image_2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2CyRtcsFjekaMYYEMcsKkC/9e2c599dd9f1f63cd852834e1aa11402/image_2.png" alt="Build command fetches published content from Contentful and builds the static site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When content changes&lt;/strong&gt;, content editors hit the publish button on Contentful. To trigger a new build for each publishing, we need to create a build hook on Netlify... &lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/6AiWsiI0ogUKGeIG8QQsEo/bde3563a6c0b2a21763eb1a34bbf4c54/image_3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/6AiWsiI0ogUKGeIG8QQsEo/bde3563a6c0b2a21763eb1a34bbf4c54/image_3.png" alt="Creating a build hook on Netlify"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and link it to the publish webhook on Contentful:&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4Ro8Wdbbzy0kaKu6SW4Yoc/ad2d0db739cf47b4846b45b308948b75/image_4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4Ro8Wdbbzy0kaKu6SW4Yoc/ad2d0db739cf47b4846b45b308948b75/image_4.png" alt="Link it to the publish webhook on Contentful"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When commerce changes&lt;/strong&gt;,  we don't need to trigger any new build, since prices and inventory are dynamically fetched from the static site JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the mix of dynamic and static data
&lt;/h2&gt;

&lt;p&gt;At this point what we have is a static site that includes product information based on the content stored in Contentful. To make this site a real shop we bring in the Commerce Layer JavaScript library which enriches the HTML with commerce data. &lt;/p&gt;

&lt;p&gt;When customers enter the site and choose a shipping country, this included JavaScript library gets an OAuth2 access token putting the related market ID in the request scope. &lt;strong&gt;All the subsequent requests are scoped to that market&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2KzMdAHauAQkaKYay0mWiO/6ff50dfa12cd168ca67206c5e863bba8/image_5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2KzMdAHauAQkaKYay0mWiO/6ff50dfa12cd168ca67206c5e863bba8/image_5.png" alt="Customer chooses a country"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The market in scope determines the right price list, inventory model, promotions, payment and shipping methods as defined by the merchant within Commerce Layer. As a developer, you just need to use the right access token to get country-specific commerce data and let customers place orders without errors.&lt;/p&gt;

&lt;p&gt;Being a static site, all pages are plain HTML files served by Netlify CDN. The only dynamic information is provided by Commerce Layer API as displayed by the graphic below (red boxes) for each relevant page.&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4qgp7K3aiscmUUaKa8u4ae/ea8763b37c1ee7cbdadf62b09de5df30/image_6.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4qgp7K3aiscmUUaKa8u4ae/ea8763b37c1ee7cbdadf62b09de5df30/image_6.png" alt="Dynamic information provided by Commerce Layer API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, not all pages require the same kind of data from Commerce Layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For each product, the category page fetches the price of the first variant&lt;/li&gt;
&lt;li&gt;The product page fetches the selected variant price and availability message&lt;/li&gt;
&lt;li&gt;The shopping bag is dynamically built from the current order line items&lt;/li&gt;
&lt;li&gt;The shopping bag preview is always populated and the "Proceed to checkout" button is linked to the current order checkout URL, that is part of the order API response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Considering that prices generally don't change very often - unless you have some adaptive pricing mechanism in place - we could also import prices into Jekyll as YAML files and leverage Commerce Layer webhooks system to trigger a new deploy on each price change. &lt;/p&gt;

&lt;p&gt;This would further improve the website speed by reducing the number of client-side requests to the Commerce Layer API. Pages, like the category overview page, could be almost completely static (apart from the shopping bag preview) as we wouldn't need to fetch the prices dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Placing orders
&lt;/h2&gt;

&lt;p&gt;When customers proceed to checkout, they are redirected to the Commerce Layer hosted checkout application, where they can add all the required information and place the order.&lt;/p&gt;

&lt;p&gt;{: img }&lt;br&gt;
&lt;a href="//images.ctfassets.net/fo9twyrwpveg/47fnE4U4esAcu8qkcu6skq/94984bd99b855bd0adc68cc78fb8e3a1/image_7.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/47fnE4U4esAcu8qkcu6skq/94984bd99b855bd0adc68cc78fb8e3a1/image_7.png" alt="Commerce Layer hosted checkout page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the easiest solution to reduce the time-to-market and start selling as soon as the content catalog is ready. That said, Commerce Layer also provides APIs to create a custom tailored experience on your own site. Just keep in mind that this would require us to add some server-side components to our static site, at least to manage the authentication and safely store its &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sum up
&lt;/h2&gt;

&lt;p&gt;Static sites are a great alternative to database-driven websites. They are fast, scalable, secure and cost-effective. All amazing features that can make your ecommerce shine.&lt;/p&gt;

&lt;p&gt;In this article, we described how some of the best tools could be combined to build the perfect stack - a.k.a. &lt;a href="https://jamstack.org/" rel="noopener noreferrer"&gt;JAMstack&lt;/a&gt; - for static ecommerce. The separation of concerns between code, content, and commerce is pretty clear. The work of developers, content editors and merchants is perfectly orchestrated.&lt;/p&gt;

&lt;p&gt;From a features perspective, we just scratched the surface of what we can do. There are a lot of great services that would perfectly fit in our diagram, making our static site even more &lt;em&gt;dynamic&lt;/em&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps – Improving functionality with more services
&lt;/h3&gt;

&lt;p&gt;Other than the fully branded checkout experience, our static site ecommerce could be improved adding the following features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Full-text search and filtering, using a tool like &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Customer account, with access to their order history, address books and wallets (credit cards on file) through the Commerce Layer customer APIs.&lt;/li&gt;
&lt;li&gt;Last but not least, better content. Despite focusing our demo on publishing a product catalogue, we strongly believe that commerce should be driven by content. A platform like Commerce Layer provides the security, performance and flexibility you need to sell your products with confidence. But this isn’t enough. Content must be creative, emotive, and inspiring.  By using Contentful, we can enrich our content model and build a unique customer journey. With our transactional engine sorted, we can focus on elevating our content experience to a whole new level.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ecommerce</category>
      <category>jekyll</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Develop, edit &amp; deploy websites entirely in the cloud with the CodeSandbox, Contentful and Netlify trio</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Tue, 07 Aug 2018 15:16:26 +0000</pubDate>
      <link>https://forem.com/contentful_blog/develop-edit--deploy-websites-entirely-in-the-cloud-with-the-codesandbox-contentful-and-netlify-trio-1nl7</link>
      <guid>https://forem.com/contentful_blog/develop-edit--deploy-websites-entirely-in-the-cloud-with-the-codesandbox-contentful-and-netlify-trio-1nl7</guid>
      <description>&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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F3toAvaEx2goY6KWwKqSQ6a%2F365366a812eb6b45da35b55aa8162e36%2F20180806_CodeSandbox_Netlify_Contentful.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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F3toAvaEx2goY6KWwKqSQ6a%2F365366a812eb6b45da35b55aa8162e36%2F20180806_CodeSandbox_Netlify_Contentful.png" alt="The power of different web services as building blocks for your site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up a development machine can be a tedious process. I once worked in a company where it was an achievement to complete the setting up of the development environment, for the custom shop system we were working on, in under two days (to be fair though, this was before Vagrant and Docker became a thing). And why’s that?&lt;/p&gt;

&lt;p&gt;Building software products relies heavily on things like your favorite editor or IDE to be productive, it depends on installed dependencies like databases, shell programs or servers to actually run and update your software. Is this still a necessity or could we ditch all of that and rely completely on cloud services today?&lt;/p&gt;

&lt;p&gt;I recently gave a talk about the &lt;a href="https://www.youtube.com/watch?list=PL03Lrmd9CiGfprrIjzbjdA2RRShJMzYIM&amp;amp;time_continue=1963&amp;amp;v=88K8oO_dYbI" rel="noopener noreferrer"&gt;Frontend stack 2018&lt;/a&gt; and had a look at how far you can get without placing a single file on your computer. &lt;strong&gt;As it turned out; you really can create websites, make them editable and later deploy them (&lt;a href="https://www.contentful.com/blog/2018/04/11/new-era-static-sites-rise-future/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=" rel="noopener noreferrer"&gt;I’m a big fan of the recent static site generators&lt;/a&gt;) from any computer using powerful online services today.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeSandbox – the new online editor in town
&lt;/h2&gt;

&lt;p&gt;A while back, I noticed CodeSandbox being increasingly used for React prototyping, when people started sharing sandboxes on Twitter with specific React patterns or best practices. "Do we need another online editor?" was my immediate response.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/3EByGDVT6os0C4OmAK0eSy/ab15b1a7ed05816b11abbbf3956e3e35/image_0.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/3EByGDVT6os0C4OmAK0eSy/ab15b1a7ed05816b11abbbf3956e3e35/image_0.png" alt="CodeSandbox for React prototyping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Earlier this year, I wrote an article &lt;a href="https://www.contentful.com/blog/2018/01/23/how-to-write-reusable-sane-api-based-components/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=" rel="noopener noreferrer"&gt;on how to use render props in React&lt;/a&gt; and decided to give &lt;a href="https://codesandbox.io/" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; a try. It doesn’t feel like my local editor (I’m using VSCode) – but it’s pretty close. &lt;/p&gt;

&lt;p&gt;In CodeSandbox you can start by forking one of the 500,000 (!) available user sandboxes, or choose to start from scratch using starter templates for React, Vue, Angular, and other frameworks. Looking at all the user-created sandboxes, you’ll see that the editor is used primarily for quick prototyping in the React ecosystem today. However, this doesn’t mean that you can’t use it to build something more complex inside or outside the React ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started with JavaScript development entirely in the cloud
&lt;/h3&gt;

&lt;p&gt;Getting started with a new JavaScript-based project using a modern framework was very tedious, and far and away from being beginner friendly in the past. The folks working on React discovered that this had to change and came up with &lt;a href="https://github.com/facebook/create-react-app" rel="noopener noreferrer"&gt;create-react-app&lt;/a&gt;. This project helps you to bootstrap and start a new React project in a few minutes by taking all &lt;a href="https://www.npmjs.com/package/react-scripts" rel="noopener noreferrer"&gt;the configuration away&lt;/a&gt; and providing all the needed defaults (#zeroconfig all the things 🎉).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;create-react-app&lt;/em&gt; is the base for CodeSandbox to create new React projects. For Preact, Vue and other frameworks similar CLI tools are available, and there’s even a "vanilla" starter template without heavy framework dependencies that uses &lt;a href="https://parceljs.org/" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt; (a new zero-config bundler – it’s fantastic, trust me!) under the hood to give you all the freedom you need.&lt;/p&gt;

&lt;p&gt;When you decide to go the React route and initialize a new project, you’ll get a codebase that is ready to dive into React development.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/G9oa8BNZKuig40o6SMEQk/a7a5af8c5585d5b9092b824750ea5028/image_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/G9oa8BNZKuig40o6SMEQk/a7a5af8c5585d5b9092b824750ea5028/image_1.png" alt="Codebase to enter React development"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Crucial editor features that let you forget that you’re "just" in an online editor
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;cmd/ctrl+p to quickly access files and commands&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are a few things that I can not live without while doing web development – first, &lt;code&gt;CMD+p&lt;/code&gt; and &lt;code&gt;CMD+Shift+p&lt;/code&gt;. These two shortcuts let you jump to any file or execute any command available via a quick and easy-to-use fuzzy search. Programming is very often about productivity, and these two shortcuts help you to achieve anything without leaving the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dependency handling and automatic installation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But now you’re in a cloud environment, right? How does it work to install dependencies then? CodeSandbox provides a dialog which lets you choose dependencies from npm easily. When you install packages with this dialog, the &lt;code&gt;package.json&lt;/code&gt; will be automatically updated. Sweet!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prettier included by default&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When developing in CodeSandbox, &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; is enabled by default, is configurable, and also runs very smoothly!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hot reloading in a separate window&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Looking at the screenshot above, the editor provides you with an in-browser preview. The cool thing is that you can open the preview in a separate window, which is perfect for two monitor setups like mine. This way, the code is on one monitor and I can see the changes in near-realtime on the other one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Autocompletion for projects shipping with TypeScript type definitions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I discovered that VSCode picks up type definition included in npm packages, I finally decided to go for TypeScript. As a JavaScript developer, I’m very used to working without great autocompletion but seeing my editor picking up TypeScript definitions is excellent. To see that CodeSandbox does the same is nice!&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/6Dir45X3HOQk6uEAcMyOgA/bf763232d391c5399088a6222ee975bd/image_2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/6Dir45X3HOQk6uEAcMyOgA/bf763232d391c5399088a6222ee975bd/image_2.png" alt="Autocompletion with TypeScript type definitions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GitHub integration makes CodeSandbox a real tool to work with&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last feature that got me was GitHub integration, which lets you create a project in CodeSandbox, push it to GitHub, and then make commits from CodeSandbox directly to GitHub. Very cool stuff!&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2mavX9IAZq00MyMmMsqMc2/5135686f271462e9a8ba86dad55a444c/image_3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2mavX9IAZq00MyMmMsqMc2/5135686f271462e9a8ba86dad55a444c/image_3.png" alt="GitHub integration in CodeSandbox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The only feature missing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Split-view mode for viewing multiple files at once is the only thing missing to make it my everyday application for development. Let’s hope that it’ll come soon! 🤞🏻&lt;/p&gt;

&lt;h3&gt;
  
  
  Contentful – the content infrastructure for any project
&lt;/h3&gt;

&lt;p&gt;With CodeSandbox you can quickly create your next JavaScript project and push it to GitHub. Very often when you do website development, the projects are built for people that are not that comfortable with writing code, though.&lt;/p&gt;

&lt;p&gt;Take a quick, one-pager portfolio site for a friend as an example. How would you realize this project saving the effort of updating content with a pull request but also without setting up a complete content management system yourself? You can use Contentful’s content infrastructure for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2o0jJUroucKkqCue2CscKq/7d5d07f07fec3cb59799bf08164418cd/image_4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2o0jJUroucKkqCue2CscKq/7d5d07f07fec3cb59799bf08164418cd/image_4.png" alt="Example one-pager site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Contentful you can define your needed data models in minutes and get the data back using JSON APIs. For the above example, you need an entity with individual fields for an image, a headline, and a paragraph respectively. This flexibility is where Contentful shines – create a content type &lt;code&gt;portfolio&lt;/code&gt; and define the three needed fields without any need to set up a server  or something similar. &lt;/p&gt;

&lt;p&gt;Your non-techy friend can now make content changes to the JavaScript app you’re building without editing JSON files or React code.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/5foa9d0dI4wIoSGaQY6aWc/f125ca32ef30fbb76dbf0426b933ceb2/image_5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/5foa9d0dI4wIoSGaQY6aWc/f125ca32ef30fbb76dbf0426b933ceb2/image_5.png" alt="Make content changes to the JavaScript app you’re building without editing JSON files or React code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later, you can use the provided &lt;a href="https://github.com/contentful/contentful.js/" rel="noopener noreferrer"&gt;JavaScript SDK&lt;/a&gt; to get the Contentful data edited by your friend.&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="nx"&gt;React&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="nx"&gt;ReactDOM&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-dom&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;createClient&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;contentful&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// create the SDK client with the needed credentials&lt;/span&gt;
&lt;span class="c1"&gt;// which you can get in the web app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&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;Portfolio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&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="na"&gt;portfolio&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="c1"&gt;// fetch the entry of your portfolio entry type&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;content_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;portfolio&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;items&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isLoading&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="na"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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;render&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&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="p"&gt;(&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="nx"&gt;Loading&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&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;Portfolio&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;portfolio&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="sr"&gt;/&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;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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="o"&gt;~~~&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&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="nx"&gt;When&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;look&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;above&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//codesandbox.io/s/5wxvp413zl), one of my favorite things is that you can connect content entries with your frontend component quite easily ({% raw %}`&amp;lt;Portfolio {...this.state.portfolio.fields} /&amp;gt;`).  This connection makes Contentful [a perfect fit for component-driven applications and sites](https://www.contentful.com/blog/2017/10/11/love-letter-to-component-ready-cms/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=).&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="err"&gt;–&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;few&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;
&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;editable&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;connected&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Github&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;CodeSandbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;edit&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;powers&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;via&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;somewhere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Netlify&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//www.netlify.com/) is a relatively new service out there that specializes in static deployments. The cool thing about Netlify is that they also allow you to define build scripts - they’re your CI service and host, so to say. &lt;/span&gt;

&lt;span class="nx"&gt;To&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;specific&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Deploy&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;specific&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/5k48xcI4xO8KcYmwEwqMYc/5008a772edae369b530bc6b30965929b/image_6.png)&lt;/span&gt;

&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;which&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;present&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`npm run build`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`build`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt; &lt;span class="nx"&gt;After&lt;/span&gt; &lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="nx"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;ll&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;subdomain&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;netlify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 

&lt;span class="nx"&gt;One&lt;/span&gt; &lt;span class="nx"&gt;less&lt;/span&gt; &lt;span class="nx"&gt;obvious&lt;/span&gt; &lt;span class="nx"&gt;thing&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="nx"&gt;also&lt;/span&gt; &lt;span class="nx"&gt;defines&lt;/span&gt; &lt;span class="nx"&gt;webhooks&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Now&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;automatically&lt;/span&gt; &lt;span class="nx"&gt;redeploy&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Magic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Magic&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Webhooks&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;automatically&lt;/span&gt; &lt;span class="nx"&gt;redeploy&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/JrUjqEneesI2S8YgomK6O/d5cd2a5d6f77ec80b542a66782c2096b/image_7.png)&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Usage&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;webhooks&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;pieces&lt;/span&gt;

&lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;portfolio&lt;/span&gt; &lt;span class="nx"&gt;demo&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;there&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;additional&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="nx"&gt;needed&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;fetches&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;side&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;applications&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;significant&lt;/span&gt; &lt;span class="nx"&gt;downside&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;they&lt;/span&gt; &lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="nx"&gt;spinner&lt;/span&gt; &lt;span class="nx"&gt;initially&lt;/span&gt; &lt;span class="nx"&gt;until&lt;/span&gt; &lt;span class="nx"&gt;they&lt;/span&gt; &lt;span class="nx"&gt;fetched&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;needed&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="nx"&gt;though&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="nx"&gt;performant&lt;/span&gt; &lt;span class="nx"&gt;way&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;statically&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;something&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;called&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hydration&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//reactjs.org/docs/react-dom.html#hydrate) when the client-side React code kicks in.&lt;/span&gt;

&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;approach&lt;/span&gt; &lt;span class="nx"&gt;means&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;would&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;rerender&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt; &lt;span class="nx"&gt;HTML&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;them&lt;/span&gt; &lt;span class="nx"&gt;whenever&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;friend&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="nx"&gt;her&lt;/span&gt; &lt;span class="nx"&gt;portfolio&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;too&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;An&lt;/span&gt; &lt;span class="nx"&gt;additional&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;notifying&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;configured&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/jPkoNvQOEoW4Ae286OG20/1c285c2794cc523aea5edcef70eefb6b/image_8.jpg)&lt;/span&gt;

&lt;span class="nx"&gt;With&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;little&lt;/span&gt; &lt;span class="nx"&gt;bit&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt; &lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="nx"&gt;installation&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="nx"&gt;shipping&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;sites&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="nx"&gt;spinners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;reinvent&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;wheel&lt;/span&gt;

&lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;conclusion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="nx"&gt;amazed&lt;/span&gt; &lt;span class="nx"&gt;how&lt;/span&gt; &lt;span class="nx"&gt;much&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;development&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Serverless&lt;/span&gt; &lt;span class="nx"&gt;technologies&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;way&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//www.contentful.com/blog/2018/04/05/graphql-and-serverless-where-cloud-computing-is-heading/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=) and we no longer have to worry about all the tiny pieces that might be needed in a project. For running Node.js projects we can use [Zeit](https://zeit.co/), for authentication [Auth0](https://auth0.com/), and for a performant search experience [Algolia](https://www.algolia.com/).&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;different&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;building&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/qWbrjftQbuSsq64Q6wq62/354b97dfc1c04876db1907283427c896/image_9.png)&lt;/span&gt;

&lt;span class="nx"&gt;Webhooks&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;serverless&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="nx"&gt;give&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;possibility&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="nx"&gt;quick&lt;/span&gt; &lt;span class="nx"&gt;connectors&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;easy&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;speedy&lt;/span&gt; &lt;span class="nx"&gt;manner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;That&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;very&lt;/span&gt; &lt;span class="nx"&gt;exciting&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="nx"&gt;focus&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;building&lt;/span&gt; &lt;span class="nx"&gt;things&lt;/span&gt; &lt;span class="nx"&gt;rather&lt;/span&gt; &lt;span class="nx"&gt;than&lt;/span&gt; &lt;span class="nx"&gt;setting&lt;/span&gt; &lt;span class="nx"&gt;things&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;even&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;websites&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt; &lt;span class="nx"&gt;friend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s computer while we’re sitting on his balcony.**
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Develop, edit &amp; deploy websites entirely in the cloud with the CodeSandbox, Contentful and Netlify trio</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Tue, 07 Aug 2018 15:16:26 +0000</pubDate>
      <link>https://forem.com/contentful_blog/develop-edit--deploy-websites-entirely-in-the-cloud-with-the-codesandbox-contentful-and-netlify-trio-4mpa</link>
      <guid>https://forem.com/contentful_blog/develop-edit--deploy-websites-entirely-in-the-cloud-with-the-codesandbox-contentful-and-netlify-trio-4mpa</guid>
      <description>&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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F3toAvaEx2goY6KWwKqSQ6a%2F365366a812eb6b45da35b55aa8162e36%2F20180806_CodeSandbox_Netlify_Contentful.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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F3toAvaEx2goY6KWwKqSQ6a%2F365366a812eb6b45da35b55aa8162e36%2F20180806_CodeSandbox_Netlify_Contentful.png" alt="The power of different web services as building blocks for your site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up a development machine can be a tedious process. I once worked in a company where it was an achievement to complete the setting up of the development environment, for the custom shop system we were working on, in under two days (to be fair though, this was before Vagrant and Docker became a thing). And why’s that?&lt;/p&gt;

&lt;p&gt;Building software products relies heavily on things like your favorite editor or IDE to be productive, it depends on installed dependencies like databases, shell programs or servers to actually run and update your software. Is this still a necessity or could we ditch all of that and rely completely on cloud services today?&lt;/p&gt;

&lt;p&gt;I recently gave a talk about the &lt;a href="https://www.youtube.com/watch?list=PL03Lrmd9CiGfprrIjzbjdA2RRShJMzYIM&amp;amp;time_continue=1963&amp;amp;v=88K8oO_dYbI" rel="noopener noreferrer"&gt;Frontend stack 2018&lt;/a&gt; and had a look at how far you can get without placing a single file on your computer. &lt;strong&gt;As it turned out; you really can create websites, make them editable and later deploy them (&lt;a href="https://www.contentful.com/blog/2018/04/11/new-era-static-sites-rise-future/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=" rel="noopener noreferrer"&gt;I’m a big fan of the recent static site generators&lt;/a&gt;) from any computer using powerful online services today.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeSandbox – the new online editor in town
&lt;/h2&gt;

&lt;p&gt;A while back, I noticed CodeSandbox being increasingly used for React prototyping, when people started sharing sandboxes on Twitter with specific React patterns or best practices. "Do we need another online editor?" was my immediate response.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/3EByGDVT6os0C4OmAK0eSy/ab15b1a7ed05816b11abbbf3956e3e35/image_0.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/3EByGDVT6os0C4OmAK0eSy/ab15b1a7ed05816b11abbbf3956e3e35/image_0.png" alt="CodeSandbox for React prototyping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Earlier this year, I wrote an article &lt;a href="https://www.contentful.com/blog/2018/01/23/how-to-write-reusable-sane-api-based-components/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=" rel="noopener noreferrer"&gt;on how to use render props in React&lt;/a&gt; and decided to give &lt;a href="https://codesandbox.io/" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; a try. It doesn’t feel like my local editor (I’m using VSCode) – but it’s pretty close. &lt;/p&gt;

&lt;p&gt;In CodeSandbox you can start by forking one of the 500,000 (!) available user sandboxes, or choose to start from scratch using starter templates for React, Vue, Angular, and other frameworks. Looking at all the user-created sandboxes, you’ll see that the editor is used primarily for quick prototyping in the React ecosystem today. However, this doesn’t mean that you can’t use it to build something more complex inside or outside the React ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started with JavaScript development entirely in the cloud
&lt;/h3&gt;

&lt;p&gt;Getting started with a new JavaScript-based project using a modern framework was very tedious, and far and away from being beginner friendly in the past. The folks working on React discovered that this had to change and came up with &lt;a href="https://github.com/facebook/create-react-app" rel="noopener noreferrer"&gt;create-react-app&lt;/a&gt;. This project helps you to bootstrap and start a new React project in a few minutes by taking all &lt;a href="https://www.npmjs.com/package/react-scripts" rel="noopener noreferrer"&gt;the configuration away&lt;/a&gt; and providing all the needed defaults (#zeroconfig all the things 🎉).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;create-react-app&lt;/em&gt; is the base for CodeSandbox to create new React projects. For Preact, Vue and other frameworks similar CLI tools are available, and there’s even a "vanilla" starter template without heavy framework dependencies that uses &lt;a href="https://parceljs.org/" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt; (a new zero-config bundler – it’s fantastic, trust me!) under the hood to give you all the freedom you need.&lt;/p&gt;

&lt;p&gt;When you decide to go the React route and initialize a new project, you’ll get a codebase that is ready to dive into React development.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/G9oa8BNZKuig40o6SMEQk/a7a5af8c5585d5b9092b824750ea5028/image_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/G9oa8BNZKuig40o6SMEQk/a7a5af8c5585d5b9092b824750ea5028/image_1.png" alt="Codebase to enter React development"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Crucial editor features that let you forget that you’re "just" in an online editor
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;cmd/ctrl+p to quickly access files and commands&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are a few things that I can not live without while doing web development – first, &lt;code&gt;CMD+p&lt;/code&gt; and &lt;code&gt;CMD+Shift+p&lt;/code&gt;. These two shortcuts let you jump to any file or execute any command available via a quick and easy-to-use fuzzy search. Programming is very often about productivity, and these two shortcuts help you to achieve anything without leaving the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dependency handling and automatic installation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But now you’re in a cloud environment, right? How does it work to install dependencies then? CodeSandbox provides a dialog which lets you choose dependencies from npm easily. When you install packages with this dialog, the &lt;code&gt;package.json&lt;/code&gt; will be automatically updated. Sweet!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prettier included by default&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When developing in CodeSandbox, &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; is enabled by default, is configurable, and also runs very smoothly!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hot reloading in a separate window&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Looking at the screenshot above, the editor provides you with an in-browser preview. The cool thing is that you can open the preview in a separate window, which is perfect for two monitor setups like mine. This way, the code is on one monitor and I can see the changes in near-realtime on the other one.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Autocompletion for projects shipping with TypeScript type definitions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I discovered that VSCode picks up type definition included in npm packages, I finally decided to go for TypeScript. As a JavaScript developer, I’m very used to working without great autocompletion but seeing my editor picking up TypeScript definitions is excellent. To see that CodeSandbox does the same is nice!&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/6Dir45X3HOQk6uEAcMyOgA/bf763232d391c5399088a6222ee975bd/image_2.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/6Dir45X3HOQk6uEAcMyOgA/bf763232d391c5399088a6222ee975bd/image_2.png" alt="Autocompletion with TypeScript type definitions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GitHub integration makes CodeSandbox a real tool to work with&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last feature that got me was GitHub integration, which lets you create a project in CodeSandbox, push it to GitHub, and then make commits from CodeSandbox directly to GitHub. Very cool stuff!&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2mavX9IAZq00MyMmMsqMc2/5135686f271462e9a8ba86dad55a444c/image_3.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2mavX9IAZq00MyMmMsqMc2/5135686f271462e9a8ba86dad55a444c/image_3.png" alt="GitHub integration in CodeSandbox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The only feature missing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Split-view mode for viewing multiple files at once is the only thing missing to make it my everyday application for development. Let’s hope that it’ll come soon! 🤞🏻&lt;/p&gt;

&lt;h3&gt;
  
  
  Contentful – the content infrastructure for any project
&lt;/h3&gt;

&lt;p&gt;With CodeSandbox you can quickly create your next JavaScript project and push it to GitHub. Very often when you do website development, the projects are built for people that are not that comfortable with writing code, though.&lt;/p&gt;

&lt;p&gt;Take a quick, one-pager portfolio site for a friend as an example. How would you realize this project saving the effort of updating content with a pull request but also without setting up a complete content management system yourself? You can use Contentful’s content infrastructure for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/2o0jJUroucKkqCue2CscKq/7d5d07f07fec3cb59799bf08164418cd/image_4.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/2o0jJUroucKkqCue2CscKq/7d5d07f07fec3cb59799bf08164418cd/image_4.png" alt="Example one-pager site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Contentful you can define your needed data models in minutes and get the data back using JSON APIs. For the above example, you need an entity with individual fields for an image, a headline, and a paragraph respectively. This flexibility is where Contentful shines – create a content type &lt;code&gt;portfolio&lt;/code&gt; and define the three needed fields without any need to set up a server  or something similar. &lt;/p&gt;

&lt;p&gt;Your non-techy friend can now make content changes to the JavaScript app you’re building without editing JSON files or React code.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/5foa9d0dI4wIoSGaQY6aWc/f125ca32ef30fbb76dbf0426b933ceb2/image_5.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/5foa9d0dI4wIoSGaQY6aWc/f125ca32ef30fbb76dbf0426b933ceb2/image_5.png" alt="Make content changes to the JavaScript app you’re building without editing JSON files or React code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later, you can use the provided &lt;a href="https://github.com/contentful/contentful.js/" rel="noopener noreferrer"&gt;JavaScript SDK&lt;/a&gt; to get the Contentful data edited by your friend.&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="nx"&gt;React&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="nx"&gt;ReactDOM&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-dom&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;createClient&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;contentful&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// create the SDK client with the needed credentials&lt;/span&gt;
&lt;span class="c1"&gt;// which you can get in the web app&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&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;Portfolio&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&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="na"&gt;portfolio&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="c1"&gt;// fetch the entry of your portfolio entry type&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;content_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;portfolio&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;items&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isLoading&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="na"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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;render&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&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="p"&gt;(&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="nx"&gt;Loading&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&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;Portfolio&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;portfolio&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="sr"&gt;/&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;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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="o"&gt;~~~&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&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="nx"&gt;When&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;look&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;above&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//codesandbox.io/s/5wxvp413zl), one of my favorite things is that you can connect content entries with your frontend component quite easily ({% raw %}`&amp;lt;Portfolio {...this.state.portfolio.fields} /&amp;gt;`).  This connection makes Contentful [a perfect fit for component-driven applications and sites](https://www.contentful.com/blog/2017/10/11/love-letter-to-component-ready-cms/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=).&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="err"&gt;–&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;few&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;clicks&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;
&lt;span class="nx"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;editable&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;connected&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Github&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;CodeSandbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;edit&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;powers&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;via&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;somewhere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Netlify&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//www.netlify.com/) is a relatively new service out there that specializes in static deployments. The cool thing about Netlify is that they also allow you to define build scripts - they’re your CI service and host, so to say. &lt;/span&gt;

&lt;span class="nx"&gt;To&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;specific&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Deploy&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;specific&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/5k48xcI4xO8KcYmwEwqMYc/5008a772edae369b530bc6b30965929b/image_6.png)&lt;/span&gt;

&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;which&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;present&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`npm run build`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`build`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt; &lt;span class="nx"&gt;After&lt;/span&gt; &lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt; &lt;span class="nx"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;ll&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;unique&lt;/span&gt; &lt;span class="nx"&gt;subdomain&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;netlify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; 

&lt;span class="nx"&gt;One&lt;/span&gt; &lt;span class="nx"&gt;less&lt;/span&gt; &lt;span class="nx"&gt;obvious&lt;/span&gt; &lt;span class="nx"&gt;thing&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;importing&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="nx"&gt;also&lt;/span&gt; &lt;span class="nx"&gt;defines&lt;/span&gt; &lt;span class="nx"&gt;webhooks&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Now&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;will&lt;/span&gt; &lt;span class="nx"&gt;automatically&lt;/span&gt; &lt;span class="nx"&gt;redeploy&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Magic&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Magic&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Webhooks&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;automatically&lt;/span&gt; &lt;span class="nx"&gt;redeploy&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/JrUjqEneesI2S8YgomK6O/d5cd2a5d6f77ec80b542a66782c2096b/image_7.png)&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Usage&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;webhooks&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;pieces&lt;/span&gt;

&lt;span class="nx"&gt;For&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="nx"&gt;portfolio&lt;/span&gt; &lt;span class="nx"&gt;demo&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;there&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;additional&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="nx"&gt;needed&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="nx"&gt;fetches&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;side&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;applications&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;significant&lt;/span&gt; &lt;span class="nx"&gt;downside&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;they&lt;/span&gt; &lt;span class="nx"&gt;show&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="nx"&gt;spinner&lt;/span&gt; &lt;span class="nx"&gt;initially&lt;/span&gt; &lt;span class="nx"&gt;until&lt;/span&gt; &lt;span class="nx"&gt;they&lt;/span&gt; &lt;span class="nx"&gt;fetched&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;needed&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="nx"&gt;though&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="nx"&gt;performant&lt;/span&gt; &lt;span class="nx"&gt;way&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;statically&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;something&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;called&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hydration&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//reactjs.org/docs/react-dom.html#hydrate) when the client-side React code kicks in.&lt;/span&gt;

&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;approach&lt;/span&gt; &lt;span class="nx"&gt;means&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;would&lt;/span&gt; &lt;span class="nx"&gt;have&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;rerender&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;generated&lt;/span&gt; &lt;span class="nx"&gt;HTML&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;them&lt;/span&gt; &lt;span class="nx"&gt;whenever&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;friend&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt; &lt;span class="nx"&gt;her&lt;/span&gt; &lt;span class="nx"&gt;portfolio&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;too&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;An&lt;/span&gt; &lt;span class="nx"&gt;additional&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;notifying&lt;/span&gt; &lt;span class="nx"&gt;Netlify&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;configured&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;Contentful&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/jPkoNvQOEoW4Ae286OG20/1c285c2794cc523aea5edcef70eefb6b/image_8.jpg)&lt;/span&gt;

&lt;span class="nx"&gt;With&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;little&lt;/span&gt; &lt;span class="nx"&gt;bit&lt;/span&gt; &lt;span class="nx"&gt;more&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt; &lt;span class="nx"&gt;deployment&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="nx"&gt;installation&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="nx"&gt;shipping&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;sites&lt;/span&gt; &lt;span class="nx"&gt;without&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="nx"&gt;spinners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;reinvent&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;wheel&lt;/span&gt;

&lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;conclusion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="nx"&gt;amazed&lt;/span&gt; &lt;span class="nx"&gt;how&lt;/span&gt; &lt;span class="nx"&gt;much&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;development&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Serverless&lt;/span&gt; &lt;span class="nx"&gt;technologies&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;way&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//www.contentful.com/blog/2018/04/05/graphql-and-serverless-where-cloud-computing-is-heading/?utm_campaign=deploy-cloud-codesandbox&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=deploy-cloud-codesandbox&amp;amp;utm_term=) and we no longer have to worry about all the tiny pieces that might be needed in a project. For running Node.js projects we can use [Zeit](https://zeit.co/), for authentication [Auth0](https://auth0.com/), and for a performant search experience [Algolia](https://www.algolia.com/).&lt;/span&gt;

&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;different&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;building&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="c1"&gt;//images.ctfassets.net/fo9twyrwpveg/qWbrjftQbuSsq64Q6wq62/354b97dfc1c04876db1907283427c896/image_9.png)&lt;/span&gt;

&lt;span class="nx"&gt;Webhooks&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;serverless&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="nx"&gt;give&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;possibility&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="nx"&gt;quick&lt;/span&gt; &lt;span class="nx"&gt;connectors&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="nx"&gt;easy&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;speedy&lt;/span&gt; &lt;span class="nx"&gt;manner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;That&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;very&lt;/span&gt; &lt;span class="nx"&gt;exciting&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="nx"&gt;focus&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;building&lt;/span&gt; &lt;span class="nx"&gt;things&lt;/span&gt; &lt;span class="nx"&gt;rather&lt;/span&gt; &lt;span class="nx"&gt;than&lt;/span&gt; &lt;span class="nx"&gt;setting&lt;/span&gt; &lt;span class="nx"&gt;things&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;even&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt; &lt;span class="nx"&gt;websites&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;my&lt;/span&gt; &lt;span class="nx"&gt;friend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s computer while we’re sitting on his balcony.**
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>cloud</category>
      <category>netlify</category>
      <category>website</category>
      <category>cms</category>
    </item>
    <item>
      <title>An extremely picky developer's take on static site generators for PHP: Part 2 - Jigsaw</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Wed, 01 Aug 2018 14:24:04 +0000</pubDate>
      <link>https://forem.com/contentful_blog/an-extremely-picky-developers-take-on-static-site-generators-for-php-part-2---jigsaw-2mp1</link>
      <guid>https://forem.com/contentful_blog/an-extremely-picky-developers-take-on-static-site-generators-for-php-part-2---jigsaw-2mp1</guid>
      <description>

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KUhFnDv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/290TvARZuEIAIwEuyeKMeE/328c6423fb224b80a4b89d3c138780ba/20180731_Developer_PHP_SSG_Jigsaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KUhFnDv---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/290TvARZuEIAIwEuyeKMeE/328c6423fb224b80a4b89d3c138780ba/20180731_Developer_PHP_SSG_Jigsaw.png" alt="An extremely picky developer's take on static site generators for PHP: Part 2 - Jigsaw"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.contentful.com/blog/2018/04/04/an-extremely-picky-developers-take-on-php-static-site-generators-part-1-sculpin/?utm_campaign=php-ssg-developer-review-jigsaw&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=php-ssg-developer-review-jigsaw&amp;amp;utm_term="&gt;first article of the series&lt;/a&gt; we took a look at &lt;a href="https://sculpin.io/"&gt;Sculpin&lt;/a&gt;, the PHP static site generator which is currently the most starred on Github. Today we’re exploring &lt;a href="https://jigsaw.tighten.co/"&gt;Jigsaw&lt;/a&gt;, a tool which promises to bring a Laravel-based approach to the world of PHP static site generators (SSGs).&lt;/p&gt;

&lt;p&gt;I’m gonna be honest with you—I’m not the biggest Laravel user. I appreciate what it's done for PHP, and how it has helped push forward the world of PHP development in a time where older frameworks were struggling to keep up with innovations coming from other platforms. It’s also a prime example of the good kind of cross pollination happening in the PHP world: it employs packages from the Symfony world, The PHP League, Doctrine, and more. It’s a good lesson in building something cool on top of great foundations, without having to continuously reinvent the wheel. Overall, I think that Laravel is an excellent framework, but I'm definitely inexperienced with it. If you think I have overlooked or misjudged something (both Laravel and Jigsaw-related), please feel free to reach out to me on Twitter: constructive criticism is always welcome! &lt;/p&gt;

&lt;h2&gt;Jigsaw (~900★)&lt;/h2&gt;

&lt;p&gt;Just like I did with Sculpin, I’ll jump straight into the action, so let’s create a sample project and play with it! We’ll open the &lt;a href="http://jigsaw.tighten.co/docs/installation/"&gt;official docs&lt;/a&gt; and start from there.&lt;/p&gt;

&lt;p&gt;Unlike Sculpin, Jigsaw doesn’t provide a skeleton project we can use as quickstart. Instead, we must create a normal project, add &lt;code&gt;tightenco/jigsaw&lt;/code&gt; as dependency, and use its CLI: &lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;jigsaw-example &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;jigsaw-example
composer require tightenco/jigsaw
./vendor/bin/jigsaw init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s go through the default directory structure that was just generated. In the top level directory, we have a &lt;code&gt;config.php&lt;/code&gt; with this code:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'baseUrl'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'production'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'collections'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;I’m not sure yet what this means, but having a configuration file clearly placed seems like a good approach 👍. The second PHP file is called &lt;code&gt;bootstrap.php&lt;/code&gt;, and it’s empty (only some commented-out code), but it hints at how we could use the event system at some point.&lt;/p&gt;

&lt;p&gt;We also have Javascript dependencies. I see a &lt;code&gt;webpack.mix.js&lt;/code&gt;, which lets me know that &lt;a href="https://laravel.com/docs/5.6/mix"&gt;Laravel Mix&lt;/a&gt; is being used. So I run &lt;code&gt;npm install&lt;/code&gt; and in the meantime I’ll explore the docs to try to understand a bit more of what I’m doing 😉.&lt;/p&gt;

&lt;p&gt;About 10 dependencies and 300MB in &lt;code&gt;node_modules&lt;/code&gt; later, we’re back. Now, let’s try to build our site. It appears we have 2 ways of doing so: we can use the Jigsaw CLI to build and then serve, or use npm and its live reload capabilities provided by Browsersync:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With Jigsaw CLI&lt;/span&gt;
./vendor/bin/jigsaw build
./vendor/bin/jigsaw serve &lt;span class="c"&gt;# localhost:8000&lt;/span&gt;


&lt;span class="c"&gt;# With npm&lt;/span&gt;
npm run watch &lt;span class="c"&gt;# localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Regardless, both work and you get this!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dersm692--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/skbns55sgxjg/7eJ6r5avBuyw62wEq4Mgeg/05595df4a9b41d5ac2ca7c1aebbaade2/Schermata_2018-07-19_alle_18.51.46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dersm692--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/skbns55sgxjg/7eJ6r5avBuyw62wEq4Mgeg/05595df4a9b41d5ac2ca7c1aebbaade2/Schermata_2018-07-19_alle_18.51.46.png" alt="Website built"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Naturally, only John Oliver can really convey my reaction to this page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/pikL8QDx238ly/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/pikL8QDx238ly/giphy.gif" alt="The site works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;The directory structure&lt;/h2&gt;

&lt;p&gt;Now let’s explore the skeleton project that was generated earlier. The order in which we’re exploring parts of the project might appear to be random because, well, it is. That’s the cool thing about discovering something new, right? The exploratory phase, where you peek at this, take a look at that, and oh, a &lt;code&gt;source&lt;/code&gt; directory! What lies therein?&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/4rqefiTPDaAquciQsEcmWk/89532fd0cff10e76079245da220c39f0/php-jigsaw-1.png?w=300" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/4rqefiTPDaAquciQsEcmWk/89532fd0cff10e76079245da220c39f0/php-jigsaw-1.png?w=300" alt="Directory structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being a Laravel-based project, I’m not surprised to find an &lt;code&gt;index.blade.php&lt;/code&gt; file, which contains the shiny &lt;em&gt;Hello world!&lt;/em&gt; from above. The &lt;code&gt;@extends('_layouts.master')&lt;/code&gt; directive hints to me that I should go look in the &lt;code&gt;_layouts&lt;/code&gt; directory for a &lt;code&gt;master.blade.php&lt;/code&gt; template, and lo and behold, there it is. Everything seems nice and tidy with the templates, let’s go check how assets are handled.&lt;/p&gt;

&lt;h2&gt;Assets and configuration&lt;/h2&gt;

&lt;p&gt;Laravel Mix provides out of the box support for Sass, so &lt;code&gt;source/_assets/sass/main.scss&lt;/code&gt; is your starting point. SCSS might not be the latest and shiniest thing on the CSS landscape, but it’s battle-tested and definitely gets the job done, so I approve of its inclusion. Let’s turn that page into something that will get your attention&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* source/_assets/sass/main.scss */&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="nt"&gt;bg-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;red&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;bg-color&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;Let’s hit save and webpack should do its magic and turn our background red, right? Yes, it does! I'll spare you the screenshot for your eyes' sake, and now let's continue our exploration.&lt;/p&gt;

&lt;p&gt;I’d like to set up a basic blog, with posts written in Markdown files. To do that, let’s introduce the concept of &lt;a href="http://jigsaw.tighten.co/docs/collections/"&gt;collections&lt;/a&gt;, which, as the docs state, &lt;em&gt;give you the ability to access your content at an aggregate level&lt;/em&gt;. Sounds about right for a blog. Remember the &lt;code&gt;config.php&lt;/code&gt; file from earlier? Let’s tweak it to tell Jigsaw about our collection of blog posts:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'baseUrl'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'production'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'collections'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'posts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'blog/{date|Y-m-d}/{filename}'&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;Jigsaw will look for the corresponding directory for every collection we define. In our example, &lt;code&gt;posts&lt;/code&gt; will become &lt;code&gt;source/_posts&lt;/code&gt;, so in that directory let’s create a blog post to show our love for one of the greatest songs of all times.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_layouts.post&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Fresh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Prince"&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Will Smith&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1990-09-10&lt;/span&gt;
&lt;span class="na"&gt;section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;content&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Now this is a story all about how
My life got flipped-turned upside down
And I'd like to take a minute
Just sit right there
I'll tell you how I became the prince of a town called Bel-Air
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s save this file as &lt;code&gt;the-fresh-prince.md&lt;/code&gt;, and then let’s create the appropriate template as specified in the &lt;code&gt;extends&lt;/code&gt; part of the front matter: &lt;code&gt;source/_layouts/post.blade.php&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@extends('_layouts.master')

@section('body')
&amp;lt;h1&amp;gt;&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;By  - &amp;lt;/p&amp;gt;

    @yield('content')
@endsection
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s run &lt;code&gt;./vendor/bin/jigsaw build&lt;/code&gt;, and we’ll have a nice &lt;code&gt;build_local&lt;/code&gt; directory generated with our shiny website, so let’s open the URL (which we still have to build ourselves) and… It works!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zdMUppbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/skbns55sgxjg/6wxnnVZ3qgCoU0uOmwi8IS/f03bf7486fba413393aa962b42bc9b2c/Schermata_2018-07-19_alle_20.01.13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zdMUppbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/skbns55sgxjg/6wxnnVZ3qgCoU0uOmwi8IS/f03bf7486fba413393aa962b42bc9b2c/Schermata_2018-07-19_alle_20.01.13.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Author’s note: at this point I would like to add more GIFs, but I have to keep it professional. Let’s all just pretend to have a GIF of Jake Peralta saying &lt;em&gt;noice&lt;/em&gt;, thank you.)&lt;/p&gt;

&lt;h2&gt;Is Jigsaw Contentful-ready?&lt;/h2&gt;

&lt;p&gt;We’ve covered some of the basics, so now the million-dollar question: is Jigsaw Contentful-ready? With this, of course, I mean how easily content stored in Contentful can be integrated in a Jigsaw-powered website. We open the &lt;a href="http://jigsaw.tighten.co/docs/event-listeners/"&gt;documentation page about event listeners&lt;/a&gt;, and with that page open in the tab, we can give this a try. Remember &lt;code&gt;bootstrap.php&lt;/code&gt;? It contained some commented-out code which hinted at how events work, so let’s reopen that:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;TightenCo\Jigsaw\Jigsaw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="sd"&gt;/** @var $container \Illuminate\Container\Container */&lt;/span&gt;
&lt;span class="sd"&gt;/** @var $events \TightenCo\Jigsaw\Events\EventBus */&lt;/span&gt;

&lt;span class="sd"&gt;/**
* You can run custom code at different stages of the build process by
* listening to the 'beforeBuild', 'afterCollections', and 'afterBuild' events.
*
* For example:
*
* $events-&amp;gt;beforeBuild(function (Jigsaw $jigsaw) {
*     // Your code here
* });
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ok, seems to be reasonably easy. As extra help, after some googling I found an &lt;a href="https://mattstauffer.com/blog/adding-an-auto-generated-sitemap-to-your-jigsaw-based-static-site/"&gt;article&lt;/a&gt; about creating a sitemap with Jigsaw, and the original &lt;a href="https://github.com/tightenco/jigsaw/pull/189"&gt;pull request&lt;/a&gt; for the events feature, so I have enough to figure this out. Let’s &lt;code&gt;composer require contentful/contentful&lt;/code&gt; and hack this thing together!&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// In bootstrap.php&lt;/span&gt;

&lt;span class="k"&gt;require_once&lt;/span&gt; &lt;span class="s1"&gt;'contentful_fetcher.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$events&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;beforeBuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ContentfulFetcher&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="c1"&gt;// In contentful_fetcher.php&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;Contentful\Delivery\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;Contentful\Delivery\Resource\Entry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;TightenCo\Jigsaw\Jigsaw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContentfulFetcher&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sd"&gt;/**
     * Jigsaw requires either a callable or the name of
     * a class which implements a method called "handle"
     *
     * @param Jigsaw $jigsaw
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Jigsaw&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Let's only build for production&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getEnvironment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'local'&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="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getCollections&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;cleanSourceDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getPlainEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entries&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;writeEntryToMarkdownFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$collection&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;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Jigsaw&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contentful.accessToken'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contentful.spaceId'&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="sd"&gt;/**
     * Before every run, we remove the previous contents of the source directory.
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cleanSourceDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Jigsaw&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getSourcePath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'/_'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getFilesystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;deleteDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dir&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="sd"&gt;/**
     * We transform objects of type Contentful\Delivery\Resource\Entry
     * into plain arrays for ease of use later on.
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPlainEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Convention:&lt;/span&gt;
        &lt;span class="c1"&gt;// Every defined collection corresponds to a content type ID in Contentful&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Contentful\Delivery\Query&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;setContentType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&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="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entries&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="p"&gt;}&lt;/span&gt;

        &lt;span class="sd"&gt;/** @var Contentful\Delivery\Resource\ContentType $contentType */&lt;/span&gt;
        &lt;span class="nv"&gt;$contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getContentType&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;\array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Entry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Convention:&lt;/span&gt;
            &lt;span class="c1"&gt;// The content types may contain a field called body,&lt;/span&gt;
            &lt;span class="c1"&gt;// which will be used as the markdown content of the generated file,&lt;/span&gt;
            &lt;span class="c1"&gt;// so we give it a default value&lt;/span&gt;
            &lt;span class="nv"&gt;$plainEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&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;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getFields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$plainEntry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$field&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$field&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getId&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="nv"&gt;$plainEntry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;$entries&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;getItems&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="sd"&gt;/**
     * Converts an entry into a markdown file and writes it to disk.
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;writeEntryToMarkdownFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Jigsaw&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'---
extends: _layouts.'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'
section: content
'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entry&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$field&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&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="nv"&gt;$field&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="nv"&gt;$field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;': '&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="s1"&gt;'---'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_%s/%s.md'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nv"&gt;$jigsaw&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;writeSourceFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$contents&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;This code is more of a proof of concept than complete implementation (it relies a lot on conventions, and it lacks handling of special fields such as locations, links, etc); however, it should get the job done for a basic example.&lt;/p&gt;

&lt;p&gt;First of all, we pass a fully-qualified class name (FQCN) to &lt;code&gt;$events-&amp;gt;beforeBuild()&lt;/code&gt;, and Jigsaw will know that it must create an instance of that class and execute the &lt;code&gt;handle&lt;/code&gt; method of it. Truth be told, I think there should be an interface in order to have a stronger contract, something like this:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;TightenCo\Jigsaw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;TightenCo\Jigsaw\Jigsaw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EventHandlerInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Jigsaw&lt;/span&gt; &lt;span class="nv"&gt;$jigsaw&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;Or perhaps just requiring any valid callable (thus including objects implementing &lt;code&gt;__invoke&lt;/code&gt;). It works now in its present state but I like strong contracts, and you should know by now that I'm picky—it's in the title of the series!&lt;/p&gt;

&lt;p&gt;This is an overview of what &lt;code&gt;ContentfulFetcher&lt;/code&gt; actually does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the &lt;code&gt;local&lt;/code&gt; environment is detected, the whole process will be stopped. This is done to avoid the performance impact due to having to fetch all data from Contentful on every build (which can likely be very often, if running on watch mode). The idea is to run a &lt;code&gt;./vendor/bin/jigsaw build production&lt;/code&gt; initially to fetch all content, and then operate with what’s been stored locally. Of course, a good idea would be to add the generated files to the local &lt;code&gt;.gitignore&lt;/code&gt;, to avoid keeping them in version control (content doesn't belong there).&lt;/li&gt;
&lt;li&gt;We create an instance of a client object using the Contentful Delivery SDK for PHP.&lt;/li&gt;
&lt;li&gt;We iterate through all the defined collections, and we use a convention to map a local collection to a content type ID in Contentful. We then clean the corresponding directory, where we will dump the generated files.&lt;/li&gt;
&lt;li&gt;We fetch entries for the given content type, and we use the &lt;code&gt;Contentful\Delivery\Resource\ContentType&lt;/code&gt; object to get a list of fields. With those fields, we build the appropriate YAML front matter section, and then we add the body at the end (string handling here is very basic and could be improved).&lt;/li&gt;
&lt;li&gt;We finally dump the contents of the generated file. We use the Contentful ID to save them, as we can define a pretty URL when configuring the collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a lot of gluing going on, but it works on my machine™️. This could be done a thousand times better, with more flexible configuration, but as a proof of concept I’m reasonably satisfied with it. &lt;/p&gt;

&lt;h2&gt;Verdict&lt;/h2&gt;

&lt;p&gt;Before our final verdict, I think it's only fair to mention that during the process of writing this article, I experienced a couple of issues which have since been fixed. Before publishing, I got in touch with &lt;a href="https://twitter.com/stauffermatt"&gt;Matt Stauffer&lt;/a&gt; (one of the people behind Jigsaw) and explained that I was experiencing some difficulties with the handling of assets and events (misconfiguration for the former, and lack of docs for the latter). Over the course of a weekend, both issues have been fixed 👏&lt;/p&gt;

&lt;p&gt;I quite like Jigsaw, but it’s rather barebones in its out-of-the-box configuration. Unlike Sculpin, it makes no assumption over what kind of use you’ll have for it, which is a double-edged sword: it avoids clutter and gives you complete control, but it also creates a higher barrier of entry for simple use cases such as blog.&lt;/p&gt;

&lt;p&gt;To solve this issue, Matt revealed to me that they’re building a generator with website skeletons, to address the most common needs (such as a blog, a documentation website, etc), with a &lt;a href="https://github.com/tightenco/jigsaw/issues/236"&gt;Github issue&lt;/a&gt; to track progress. He also mentioned that they just launched a new &lt;a href="https://builtwithjigsaw.com/"&gt;showcase of websites built with Jigsaw&lt;/a&gt;, which I encourage you to check out.&lt;/p&gt;

&lt;p&gt;As a non-Laravel developer, I was afraid that Jigsaw would be difficult to pick up and use, but it definitely wasn’t the case. It is actively being developed (the events system was added just a few months ago) and is on track to quickly become the most popular PHP static site generator, in terms of stars on Github. It provides very good defaults, and assets are preconfigured to use a modern stack (Sass for CSS, and webpack with Babel for JS).&lt;/p&gt;

&lt;p&gt;The only real criticisms I can make are actually the same as when I discussed Sculpin: content and presentation (assets and templates) could be more separate; instead, they belong to the same &lt;code&gt;source&lt;/code&gt; directory, and I would love to be able to have a more defined structure for collections, instead of relying exclusively on the YAML bit before the actual Markdown contents.&lt;/p&gt;

&lt;p&gt;Next article will be about &lt;a href="http://couscous.io/"&gt;Couscous&lt;/a&gt;, which describes itself as a documentation generator. Stay tuned!&lt;/p&gt;


</description>
      <category>jigsaw</category>
      <category>staticsite</category>
      <category>php</category>
      <category>generator</category>
    </item>
    <item>
      <title>Making the most of Marketo with UI extensions and Lambda functions</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Fri, 20 Jul 2018 07:43:24 +0000</pubDate>
      <link>https://forem.com/contentful_blog/making-the-most-of-marketo-with-ui-extensions-and-lambda-functions-52b6</link>
      <guid>https://forem.com/contentful_blog/making-the-most-of-marketo-with-ui-extensions-and-lambda-functions-52b6</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KucGJFUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/4ePBTSoNs4w6QgOQkAyUKI/846d320c26c17c3d869ef7d3db626e9b/20180706_Marketo_UI_Extensions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KucGJFUB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.ctfassets.net/fo9twyrwpveg/4ePBTSoNs4w6QgOQkAyUKI/846d320c26c17c3d869ef7d3db626e9b/20180706_Marketo_UI_Extensions.png" alt="Marketo forms, Contentful UI Extensions and Lambda functions"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Marketo offers a suite of marketing automation tools that addresses the different subfields of marketing and hence, can be utilized extensively by the various arms of a full-blown marketing department. We at Contentful use Marketo as one of the solutions to handle duties performed by our marketing folks, such as forms on our landing pages.&lt;/p&gt;

&lt;p&gt;Being such a comprehensive platform, Marketo has a bit of a steep learning curve and, at first glance, some of its features can seem tricky to implement. In our case of forms, handled by authors such as our demand generation team, the out-of-the-box process to embed the code of a Marketo form into a page is both tedious and time-consuming.&lt;/p&gt;

&lt;p&gt;The process is an involved sequence of grabbing the embed code and pasting it into a HTML block or a markdown field on a page, and then repeating for every page where a form is desired. The stock appearance of the forms is also somewhat generic, so a degree of customization is needed to make them look more appealing.&lt;/p&gt;

&lt;p&gt;We found a quick workaround to take manual repetition out of the equation and fill that in with easy-to-use selection dropdowns in our Contentful system using &lt;a href="https://www.contentful.com/blog/2017/10/09/creating-ui-extensions-with-contentful/?utm_campaign=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_term="&gt;UI extensions&lt;/a&gt; (read more in &lt;a href="https://www.contentful.com/developers/docs/concepts/uiextensions/?utm_campaign=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_term="&gt;our concept documentation&lt;/a&gt;). We also managed to style the forms to align more with Contentful’s own branding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prettifying Marketo forms
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/6IWw7kgZUs8CocwmKsKQIw/2fb2f88e7083987ecfa3d03e2b95021d/marketo_forms_1.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/6IWw7kgZUs8CocwmKsKQIw/2fb2f88e7083987ecfa3d03e2b95021d/marketo_forms_1.png" alt="Setting up a form in Marketo"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;First and foremost, Marketo forms utilize inline styles on a form’s elements and have a plain default design. These forms are also decidedly proprietary with a very unique markup pattern, leading us to having to rethink our forms in order to work with Marketo.&lt;/p&gt;

&lt;p&gt;To make sure we removed all potential conflicting styles from Marketo, we found an &lt;a href="https://codepen.io/mirshko/pen/LrZaWB"&gt;existing function&lt;/a&gt;, that we modified to be a little cleaner, which uses a &lt;code&gt;div&lt;/code&gt; on the page to get the ID of the form (generated by our static site generator with Contentful data from the UI extension). From there, we load the form from Marketo and strip all styles and conflicting CSS added by the JavaScript from Marketo, leaving us with an unstyled HTML form.&lt;/p&gt;

&lt;p&gt;With our new clean HTML form and the UI extension, we are able to style the Marketo form using SASS &lt;code&gt;@extend&lt;/code&gt; with our own form design’s styles, configured to be Marketo-form-compatible. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="nc"&gt;.custom_mkto_form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nc"&gt;.mktoLabel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@extend&lt;/span&gt; &lt;span class="nc"&gt;.label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mktoButton&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@extend&lt;/span&gt; &lt;span class="nc"&gt;.btn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;@extend&lt;/span&gt; &lt;span class="nc"&gt;.btn--blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;@extend&lt;/span&gt; &lt;span class="nc"&gt;.btn--large&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mktoErrorMsg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;@extend&lt;/span&gt; &lt;span class="nc"&gt;.error-message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we have a form that is both easy for the author to use on a page and has nice brand-specific styles.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI Extensions and Lambda save the day
&lt;/h2&gt;

&lt;p&gt;Since it can also be a tedious and time-consuming process to embed forms manually on each page where a form is desired, we addressed this by building a UI extension in Contentful. This offers authors the ability to select the form they wanted to use on a landing page via a list of choices hosted in something like a dropdown menu.&lt;/p&gt;

&lt;p&gt;There was a hurdle to leap over first, however. Contentful UI extensions only perform client-side API calls, but calls to the Marketo API had to be done server-side due to &lt;a href="https://docs.marketo.com/display/public/DOCS/Configure+Protocols+for+Marketo"&gt;Marketo’s CORS&lt;/a&gt; and the server endpoint restrictions in place.&lt;/p&gt;

&lt;p&gt;To do this, we built a Lambda function, hosted on Netlify to take advantage of their awesome &lt;a href="https://www.netlify.com/docs/functions/"&gt;new functions feature&lt;/a&gt;. The UI extension would perform a HTTP call to this function, which would return a payload of all of the forms in our Marketo instance, and subsequently parse through the payload and generate a dropdown selection of all the forms available for the editors to use on their landing pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/39UbV7o93qkG4kSGKYeWkk/74639beac16bbaff26abff29e6258478/marketo_forms_2a.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/39UbV7o93qkG4kSGKYeWkk/74639beac16bbaff26abff29e6258478/marketo_forms_2a.png" alt="Installing a UI extension in Contentful"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use this very UI Extension by utilizing our new UI Extension example installer. Simply head to Settings from within the Contentful application, click on Extensions and then &lt;em&gt;Add Extension&lt;/em&gt;. From there, click on &lt;em&gt;Install an example&lt;/em&gt;, locate Marketo Forms from the list and &lt;em&gt;Install&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The next step would be to fill the required &lt;em&gt;Lambda Function URL&lt;/em&gt; installation parameter with the URL endpoint of your Lambda function. You can find the code for this Lambda function &lt;a href="https://github.com/contentful/extensions/blob/master/samples/marketo-forms/lambda-function.js"&gt;in the repository of our extensions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is best to create this Lambda function in either &lt;a href="https://www.netlify.com/docs/functions/"&gt;Netlify Functions&lt;/a&gt;, direct use of &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;, or any of the &lt;a href="https://thepowerofserverless.info/services.html"&gt;other serverless function platforms&lt;/a&gt;. Not all Lambda function services are created equal and the code might have to be modified to fit your platform of choice.&lt;/p&gt;

&lt;p&gt;Setting it up is relatively easy; &lt;a href="https://github.com/contentful/extensions/tree/master/samples/marketo-forms#usage"&gt;follow our tutorial&lt;/a&gt; on how you can get your Marketo credentials and the Environment Variables needed by the Lambda function. To use it, select the &lt;em&gt;JSON object Field&lt;/em&gt; when you are creating your content model for a new landing page, and proceed to set the Appearance tab to be &lt;em&gt;Marketo forms&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now when your editors select a form to use on a landing page, your developers will have a nice JSON dump of the form data to work with and integrate with the &lt;a href="http://developers.marketo.com/javascript-api/forms/"&gt;Marketo Forms JavaScript API&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Try out UI extensions
&lt;/h2&gt;

&lt;p&gt;UI extensions are a great way to customize and extend your editorial experience with Contentful; anyone with some JavaScript knowledge can create their own extension, leaving you with an incredibly accessible, powerful feature with endless possibilities. Start by creating a &lt;a href="https://www.contentful.com/sign-up/?utm_campaign=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=customize-integrate-marketo-forms-ui-extensions-lambda&amp;amp;utm_term="&gt;free Contentful account&lt;/a&gt;, if you don't already have one, and discover UI extensions... and also how great our content infrastructure works with your code and static site projects. &lt;/p&gt;

</description>
      <category>marketo</category>
      <category>forms</category>
      <category>content</category>
      <category>lambda</category>
    </item>
    <item>
      <title>The understated innovation of static site generators</title>
      <dc:creator>Contentful Blog</dc:creator>
      <pubDate>Tue, 17 Jul 2018 08:18:34 +0000</pubDate>
      <link>https://forem.com/contentful_blog/the-understated-innovation-of-static-site-generators-mla</link>
      <guid>https://forem.com/contentful_blog/the-understated-innovation-of-static-site-generators-mla</guid>
      <description>&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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F7gaObMiwo0eqUOYs4gYsI8%2Fe74bb796e13035205fcedd2f1f6fde4a%2F20180716_potential_static_site_generators.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%2Fimages.ctfassets.net%2Ffo9twyrwpveg%2F7gaObMiwo0eqUOYs4gYsI8%2Fe74bb796e13035205fcedd2f1f6fde4a%2F20180716_potential_static_site_generators.png" alt="The understated potential of static site generators"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This article is a reflection on the bigger picture around the new generation of static sites generators that are currently gaining traction and adoption.&lt;/p&gt;

&lt;p&gt;Static site generators have been around for a while and an early player in the field was &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll, which gained a lot of traction at launch, triggered the birth of GitHub Pages and hence created a developer-friendly solution to content&lt;/a&gt;. We’re now seeing a new wave of static site generators like &lt;a href="https://www.gatsbyjs.org/" rel="noopener noreferrer"&gt;GatsbyJS&lt;/a&gt;, that no longer are limited to publishing needs of the developer audience (as in, manage your content completely on GitHub), and instead have the ambition of becoming &lt;em&gt;the&lt;/em&gt; new tool for content publishing.&lt;/p&gt;

&lt;p&gt;The current wave of static site generators comes with an entire ecosystem that makes the movement wider in scope than site-building tools themselves or even just content. While there’s a lot of attention surrounding what this means for &lt;a href="https://www.contentful.com/blog/2018/04/11/new-era-static-sites-rise-future/?utm_campaign=innovation-ssg&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=innovation-ssg&amp;amp;utm_term=" rel="noopener noreferrer"&gt;applications that directly touch the browser&lt;/a&gt;, there’s much less thought put into behind-the-scenes reasons on why these new stacks like &lt;a href="https://www.contentful.com/?utm_campaign=innovation-ssg&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=innovation-ssg&amp;amp;utm_term=" rel="noopener noreferrer"&gt;Contentful&lt;/a&gt; + &lt;a href="https://www.gatsbyjs.org/" rel="noopener noreferrer"&gt;GatsbyJS&lt;/a&gt; + &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; are getting attention and being chosen by development teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime that reaches out for data and some adventures in game servers
&lt;/h3&gt;

&lt;p&gt;Years ago, I worked at &lt;a href="https://www.wooga.com/" rel="noopener noreferrer"&gt;Wooga&lt;/a&gt;, building game servers for social games—one problematic performance aspect of games is that the state of the game has to be updated very frequently. This means game servers are write-heavy beasts that hammer your databases in a way where caching, the technique most often used in lifting database load to cope with high traffic, would not be an option.&lt;/p&gt;

&lt;p&gt;It was back then when I started reflecting on the implication of choices regarding the position of the runtime. By runtime, I mean the process where the code is running—which is with respect to all databases and services that need to be reached in order for actions, that allow you to fulfill the current request, to be performed.&lt;/p&gt;

&lt;p&gt;The solution to the pressure that our applications put on databases was a complete turn-around of all strategies surrounding interaction with the data layer; which meant transitioning from a model, where each and every request had a direct database interaction, to one where a user’s entire game-state would be loaded in the runtime (we chose Erlang) at the beginning of a session. You can still find the slides—from a scary number of years ago—from the &lt;a href="https://www.slideshare.net/hungryblank/erlang-factory-sf2011v12" rel="noopener noreferrer"&gt;beginning of the project&lt;/a&gt; and the &lt;a href="https://www.slideshare.net/hungryblank/getting-real-with-erlang" rel="noopener noreferrer"&gt;considerations after running it in production&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In a nutshell, that project led to the decrease number of interactions with databases by two or three orders of magnitude and achieve sub-millisecond response time improvement by two orders of magnitude compared to previous approach. The idea must have also germinated in other brains within the gaming industry, as Microsoft launched &lt;a href="https://github.com/dotnet/orleans" rel="noopener noreferrer"&gt;Orleans&lt;/a&gt; a year later.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we learned from the push for having data in the runtime
&lt;/h3&gt;

&lt;p&gt;So, how is our work at Wooga on strategies around working with game-state now relevant in the scope of static site generators?&lt;/p&gt;

&lt;p&gt;Working on that topic brought me to more general reflections on how stacks are built (data at last)—the runtime is the part that coordinates all actions, the request enters the runtime and from the runtime, you ensure that a number of other systems are touched or triggered.&lt;/p&gt;

&lt;p&gt;After a request enters your runtime, you write your code which is a mix of two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Interacting with elements external to the process&lt;/p&gt;

&lt;p&gt;a. Reading data&lt;br&gt;
b. Writing data&lt;br&gt;
c. Checking authorization&lt;br&gt;
d. Triggering tracking&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The actual business logic that must be performed in order to satisfy the request&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now if you think about the two places most of the pain (bugs, errors, outage) comes from; writing and operating application, I’m sure that the former is thought of as the more evil place to be.&lt;/p&gt;

&lt;p&gt;I remember clearly what it meant to start programming in a paradigm where writing was mostly out of the picture, which enabled me to realize the idea of ninja state or "flow" that you can find in books like &lt;a href="https://gettingthingsdone.com/" rel="noopener noreferrer"&gt;“Getting things done”&lt;/a&gt; or “&lt;a href="https://www.amazon.com/Flow-Psychology-Experience-Perennial-Classics/dp/0061339202" rel="noopener noreferrer"&gt;Flow&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;It’s a superior state of mind, where you are able to just think and focus on what you want to achieve and are not stuck with internal dialogs like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Where’s the data I need?" - all data is just there; just access and manipulate it as needed&lt;/li&gt;
&lt;li&gt;"In what order would these things happen?" - in the exact order you’re writing them, which should be arranged flow naturally according to how one would think&lt;/li&gt;
&lt;li&gt;"Is there anything that could fail in this?" - the only problem you might have is with the data at hand; but there’s no other interference or clutter that could influence what you’re writing (no network errors, collisions, race condition).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These considerations have led me to think about the "upside down stack", one where the database or other services come first—not in between or just in time as the logic progresses—and where you enter the runtime with all data and authorization necessary in order to perform the request.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/fo9twyrwpveg/263CbYT8D2WaeoQaIOMGME/79621ab7d709996df955d451615f7c8a/20180716_stacks.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/fo9twyrwpveg/263CbYT8D2WaeoQaIOMGME/79621ab7d709996df955d451615f7c8a/20180716_stacks.png" alt="The classic stack versus the upside down stack"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How are static site generators a step towards the "upside-down stack"?
&lt;/h3&gt;

&lt;p&gt;Static site generators are essentially build processes, processes that are triggered because of change in either the code or data underlying the static artifact (this is a novelty of SSGs building both data &amp;amp; code versus building just code). Because these processes are triggered by changes in the data or the code, those changes are what enter computation rather than something that is fetched a runtime.&lt;/p&gt;

&lt;p&gt;This is how static site generators are akin to the "upside-down stack"; data comes first and is what you enter the process with, versus being something that is sought after only during an ongoing process.&lt;/p&gt;

&lt;p&gt;This vision is already present in a modern build system like Concourse CI with its concept of &lt;a href="https://concourse-ci.org/pipelines.html" rel="noopener noreferrer"&gt;Pipelines being composed of Job and Resources&lt;/a&gt;. This goes in the direction of thinking "Okay, this job is triggered by a change on some resources and, in order to perform the job, other resources will also be needed." The dependency tree of the resources required in order to perform an action is specified beforehand and you can ensure that all resources are in place prior to performing a job.&lt;/p&gt;

&lt;p&gt;This is where I see static site generators as early adopters of certain patterns that will gain more generalized adoption. If you have a domain where the data is read more than it is written, a build-driven approach has a number of advantages (among them excellent performance, low cost and a &lt;a href="https://www.contentful.com/blog/2018/03/16/reducing-the-attack-surface-with-static-sites/?utm_campaign=innovation-ssg&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=innovation-ssg&amp;amp;utm_term=" rel="noopener noreferrer"&gt;better security model&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  The intersection with serverless
&lt;/h3&gt;

&lt;p&gt;As mentioned in my previous post, where I predicted some &lt;a href="https://www.contentful.com/blog/2018/04/05/graphql-and-serverless-where-cloud-computing-is-heading/?utm_campaign=innovation-ssg&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=innovation-ssg&amp;amp;utm_term=" rel="noopener noreferrer"&gt;bigger trends behind the surface of Serverless &amp;amp; GraphQL&lt;/a&gt;, serverless has deeper implications than what is usually known for. For example, it can be seen as Serverless is a very compelling technology to deal with workloads that are build driven like static site generators, you need to handle workloads that are event driven (with events generated by changes to the application, the data or the domain model) and then different in scale and distribution over time than workloads to serving constant traffic.&lt;/p&gt;

&lt;p&gt;To support build cycles and pipelines, Serverless becomes relevant again as infrastructure used to host these new kinds of architectures. &lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;Step functions&lt;/a&gt; are something to look at in order to understand how this future might look like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Before concluding this post, it’s important to look at what static site generators represent and which factors they rely on to achieve the popularity they currently enjoy. The first important realization is that static site generators can be &lt;a href="https://www.contentful.com/blog/2018/04/13/dynamic-static-sites-implementing-an-oxymoron/?utm_campaign=innovation-ssg&amp;amp;utm_medium=referral&amp;amp;utm_source=devto&amp;amp;utm_content=innovation-ssg&amp;amp;utm_term=" rel="noopener noreferrer"&gt;more dynamic than the name suggests&lt;/a&gt; and that the concepts they apply might see much wider adoption if accompanied with a significant innovation of the toolchains currently in use. As an example static site generators might be a pointer to the fact that build processes are about to be re-evaluated as a delivery mechanism for more than application logic.&lt;/p&gt;

&lt;p&gt;In short, here are trends to watch for, as they might serve as seeds for future looking-thoughts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data enters the runtime at the very beginning of the computation, instead of being grabbed during computation&lt;/li&gt;
&lt;li&gt;The event that triggers the computation is a change in application logic, in the data of the domain that the application deals with, or changes of the data domain itself&lt;/li&gt;
&lt;li&gt;A user request can be satisfied by serving a pre-compiled set of assets (that are built only when the underlying data is updated, and not at request time)&lt;/li&gt;
&lt;li&gt;The data is "built", instead of being fetched and integrated at every request; requiring more modern build systems and innovations of the concept of build systems&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Next step
&lt;/h3&gt;

&lt;p&gt;It’s important to keep watching how the phenomenon of static sites continues to evolve and seeing the prospects of the new generation of web architectures that are build-based rather than the current request/response-focused patterns.&lt;/p&gt;

&lt;p&gt;In order for that trend to grow, other trends need to fall into place; build systems and databases (or systems that front the database) are some areas we need to look at for innovations. This will be something I will elaborate in follow up posts of this series.&lt;/p&gt;

</description>
      <category>staticsite</category>
      <category>stack</category>
      <category>webdev</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
