<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nop33.eth</title>
    <description>The latest articles on DEV Community by nop33.eth (@nop33).</description>
    <link>https://dev.to/nop33</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%2F62099%2F4c7f8a2a-87e0-4af4-abaf-7e78300c2ac5.png</url>
      <title>DEV Community: nop33.eth</title>
      <link>https://dev.to/nop33</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nop33"/>
    <language>en</language>
    <item>
      <title>Looking for (React) open-source contributors to reward</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Fri, 20 Dec 2024 12:11:00 +0000</pubDate>
      <link>https://dev.to/nop33/looking-for-react-open-source-contributors-to-reward-4mee</link>
      <guid>https://dev.to/nop33/looking-for-react-open-source-contributors-to-reward-4mee</guid>
      <description>&lt;p&gt;As our user base grows so does the GitHub issue backlog. My teammate and I at &lt;a href="//www.alephium.org"&gt;Alephium&lt;/a&gt; would be delighted to collaborate with open-source contributors from the React community. &lt;/p&gt;

&lt;p&gt;We will reward good code through our &lt;a href="https://github.com/alephium/community/blob/master/Grant%26RewardProgram.md" rel="noopener noreferrer"&gt;grands &amp;amp; rewards program&lt;/a&gt;. 🤑&lt;/p&gt;

&lt;p&gt;Our codebase consists of a monorepo with a React webapp, a React Electron app, and a React Native app, alongside shared packages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/alephium/alephium-frontend/" rel="noopener noreferrer"&gt;https://github.com/alephium/alephium-frontend/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit us up on Discord, Telegram, GitHub, X, or wherever you can find us!&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Bun challenges: @shopify/react-native-skia</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Tue, 21 Nov 2023 13:51:00 +0000</pubDate>
      <link>https://dev.to/nop33/bun-challenges-shopifyreact-native-skia-1hl7</link>
      <guid>https://dev.to/nop33/bun-challenges-shopifyreact-native-skia-1hl7</guid>
      <description>&lt;p&gt;At work we recently switched from a poly-repo to a mono-repo set up and from using npm to using &lt;a href="https://bun.sh/"&gt;Bun&lt;/a&gt; as our project manager. One of the challenges that I faced today was that I could not run our React Native app in an Android simulator anymore. The main error I was getting was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Cannot find source file:

      node_modules/@shopify/react-native-skia/android/cpp/jsi/JsiHostObject.cpp

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Click here for full traceback
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':shopify_react-native-skia:configureCMakeDebug[arm64-v8a]'.
&amp;gt; [CXX1429] error when building with cmake using /path/to/monorepo/node_modules/@shopify/react-native-skia/android/CMakeLists.txt: -- Android: Targeting API '21' with architecture 'arm64', ABI 'arm64-v8a', and processor 'aarch64'
  -- Android: Selected unified Clang toolchain
  -- The C compiler identification is Clang 12.0.8
  -- The CXX compiler identification is Clang 12.0.8
  -- Detecting C compiler ABI info
  -- Detecting C compiler ABI info - done
  -- Check for working C compiler: /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang - skipped
  -- Detecting C compile features
  -- Detecting C compile features - done
  -- Detecting CXX compiler ABI info
  -- Detecting CXX compiler ABI info - done
  -- Check for working CXX compiler: /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ - skipped
  -- Detecting CXX compile features
  -- Detecting CXX compile features - done
  -- Configuring done

  C++ build system [configure] failed while executing:
      /path/to/android/sdk/cmake/3.22.1/bin/cmake \
        -H/path/to/monorepo/node_modules/@shopify/react-native-skia/android \
        -DCMAKE_SYSTEM_NAME=Android \
        -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
        -DCMAKE_SYSTEM_VERSION=21 \
        -DANDROID_PLATFORM=android-21 \
        -DANDROID_ABI=arm64-v8a \
        -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
        -DANDROID_NDK=/path/to/android/sdk/ndk/23.1.7779620 \
        -DCMAKE_ANDROID_NDK=/path/to/android/sdk/ndk/23.1.7779620 \
        -DCMAKE_TOOLCHAIN_FILE=/path/to/android/sdk/ndk/23.1.7779620/build/cmake/android.toolchain.cmake \
        -DCMAKE_MAKE_PROGRAM=/path/to/android/sdk/cmake/3.22.1/bin/ninja \
        "-DCMAKE_CXX_FLAGS=-fexceptions -frtti -std=c++1y -DONANDROID" \
        -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/intermediates/cxx/Debug/5v1r3a6q/obj/arm64-v8a \
        -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/intermediates/cxx/Debug/5v1r3a6q/obj/arm64-v8a \
        -DCMAKE_BUILD_TYPE=Debug \
        -DCMAKE_FIND_ROOT_PATH=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/.cxx/Debug/5v1r3a6q/prefab/arm64-v8a/prefab \
        -B/path/to/monorepo/node_modules/@shopify/react-native-skia/android/.cxx/Debug/5v1r3a6q/arm64-v8a \
        -GNinja \
        -DANDROID_STL=c++_shared \
        -DREACT_NATIVE_VERSION=72 \
        -DNODE_MODULES_DIR=/path/to/monorepo/node_modules \
        "-DPREBUILT_DIR=/path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/react-native-0*/jni"
    from /path/to/monorepo/node_modules/@shopify/react-native-skia/android
  -- ABI : arm64-v8a
  -- PREBUILT: /path/to/monorepo/node_modules/@shopify/react-native-skia/android/build/react-native-0*/jni
  -- BUILD : /path/to/monorepo/node_modules/@shopify/react-native-skia/android/build
  -- LIBRN :
  -- LOG : /path/to/android/sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/21/liblog.so
  -- JSI : ReactAndroid::jsi
  -- REACT : ReactAndroid::react_nativemodule_core
  -- FBJNI : fbjni::fbjni
  -- TURBO : ReactAndroid::turbomodulejsijni
  CMake Error at CMakeLists.txt:40 (add_library):
    Cannot find source file:

      /path/to/monorepo/node_modules/@shopify/react-native-skia/android/cpp/jsi/JsiHostObject.cpp

    Tried extensions .c .C .c++ .cc .cpp .cxx .cu .mpp .m .M .mm .ixx .cppm .h
    .hh .h++ .hm .hpp .hxx .in .txx .f .F .for .f77 .f90 .f95 .f03 .hip .ispc

  CMake Error at CMakeLists.txt:40 (add_library):
    No SOURCES given to target: rnskia

  CMake Generate step failed. Build files cannot be regenerated correctly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the following file DID exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/@shopify/react-native-skia/cpp/jsi/JsiHostObject.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems like some symlinking is not working properly.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/Shopify/react-native-skia/issues/780"&gt;unrelated issue&lt;/a&gt; reminded me that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bun does not execute arbitrary lifecycle scripts for installed dependencies, such as postinstall and node-gyp builds&lt;a href="https://bun.sh/guides/install/trusted"&gt;https://bun.sh/guides/install/trusted&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Adding a &lt;code&gt;trustedDependencies&lt;/code&gt; property in the package.json file of my project, deleting the &lt;code&gt;bun.lockb&lt;/code&gt; file and &lt;code&gt;node_modules&lt;/code&gt; and reinstalling with &lt;code&gt;bun i&lt;/code&gt; fixed the issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "trustedDependencies": [
    "@shopify/react-native-skia"
  ]

rm -rf node_modules
rm bun.lockb
bun i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is also verified when looking at the logs og the &lt;code&gt;bun i&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updating symlinks for Android build...
Creating symlink to api /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to jsi /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to rnskia /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to skia /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Creating symlink to utils /path/to/monorepo/node_modules/@shopify/react-native-skia/scripts /path/to/monorepo/node_modules/@shopify/react-native-skia
Symlinks created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Storing a function in React state</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Wed, 07 Jun 2023 14:11:00 +0000</pubDate>
      <link>https://dev.to/nop33/storing-a-function-in-react-state-1pl2</link>
      <guid>https://dev.to/nop33/storing-a-function-in-react-state-1pl2</guid>
      <description>&lt;p&gt;I felt very React-dumb today. I simply wanted to store a function in the state of a React component. So, I would initialize the component state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [myCallback, setMyCallback] = useState&amp;lt;() =&amp;gt; void&amp;gt;()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later in the code I would have a button that sets this state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cb = () =&amp;gt; console.log('hi!')
setMyCallback(cb)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve been breaking my head on why the hell my callback function gets called even though I never call it anywhere in my code! Notice that we didn’t execute &lt;code&gt;cb()&lt;/code&gt; or &lt;code&gt;myCallback()&lt;/code&gt; anywhere.&lt;/p&gt;

&lt;p&gt;The problem is that the argument of the &lt;code&gt;setState&lt;/code&gt; functions can be a function that let’s us update the state based on the previous value. See the &lt;a href="https://react.dev/reference/react/Component#setstate"&gt;docs of &lt;code&gt;setState&lt;/code&gt;&lt;/a&gt; for details. React cannot tell if the function we pass is the aforementioned function, or the value that we want to store in our state. A solution to this is to simply wrap the value we want to store into a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setMyCallback(() =&amp;gt; cb)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So funny yet so frustrating at the same time! Hope I save someone’s time!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using npm distribution tags the right way</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Sat, 02 Apr 2022 15:06:00 +0000</pubDate>
      <link>https://dev.to/nop33/using-npm-distribution-tags-the-right-way-562f</link>
      <guid>https://dev.to/nop33/using-npm-distribution-tags-the-right-way-562f</guid>
      <description>&lt;p&gt;I learnt a lot about npm package distribution tags at work in the past couple of weeks and I thought to share my learnings in this blog post.&lt;/p&gt;

&lt;p&gt;Publishing a package on npm is quite simple. All you need is an account on npmjs.com and a project containing a &lt;code&gt;package.json&lt;/code&gt; file. Then, with a few simple commands you can publish your package to npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm login
npm publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command will pack your project and deploy it to the npm registry. What’s more, is that it will make the &lt;code&gt;latest&lt;/code&gt; distribution tag of your npm package point to the version of the package that is defined in the &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example package.json file&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;But what is a &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag"&gt;distribution tag&lt;/a&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What this means is that when a user of your package installs it with &lt;code&gt;npm i my-awesome-package&lt;/code&gt; or &lt;code&gt;npm i my-awesome-package@latest&lt;/code&gt; it will install version &lt;code&gt;0.1.0&lt;/code&gt; from the example above. So, when running &lt;code&gt;npm publish&lt;/code&gt; in a project where the &lt;code&gt;version&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; is set to &lt;code&gt;0.1.0&lt;/code&gt;, the &lt;code&gt;latest&lt;/code&gt; package distribution tag is an alias to the &lt;code&gt;0.1.0&lt;/code&gt; version of your package.&lt;/p&gt;

&lt;p&gt;But what if you want to publish a version of your package to npm, but you don’t want the &lt;code&gt;latest&lt;/code&gt; alias to point to that version? You may want, for example, to publish a release candidate version (say &lt;code&gt;1.0.0-rc.0&lt;/code&gt;) so that your users can download it (with &lt;code&gt;npm i my-awesome-package@1.0.0-rc.0&lt;/code&gt;) and test your upcoming changes before you publish a new patch/minor/major version of your package. But while you want your release candidate to be available on npmjs.com for people to find and download, you don’t want this version to show up when users are upgrading their packages, for example, with &lt;code&gt;npm outdated&lt;/code&gt; and &lt;code&gt;npm upgrade&lt;/code&gt;, nor when users install it with &lt;code&gt;npm i my-awesome-package&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The answer is in the &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-publish"&gt;docs of &lt;code&gt;npm publish&lt;/code&gt;&lt;/a&gt;. Running &lt;code&gt;npm publish&lt;/code&gt; is the same as running &lt;code&gt;npm publish --tag latest&lt;/code&gt;. That’s why the package version is always mapped to the &lt;code&gt;latest&lt;/code&gt; distribution tag. You can easily override this, by simply providing a different distribution tag. The mistake I made (guilty for not reading the &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag"&gt;distribution tags docs&lt;/a&gt; properly) was that I thought “OK, I can just pass the version of my package as the tag and then the &lt;code&gt;latest&lt;/code&gt; won’t point to it”. So I tried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish &lt;span class="nt"&gt;--tag&lt;/span&gt; 1.0.0-rc.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only to receive the error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm ERR! Tag name must not be a valid SemVer range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going back to &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-dist-tag#caveats"&gt;the docs&lt;/a&gt; helped me understand the issue better. But it was this &lt;a href="https://stackoverflow.com/a/48038690/1644591"&gt;StackOverflow answer&lt;/a&gt; that really brought the issue home.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Package distribution tags are not meant to be version numbers. They are aliases to version numbers. They are words like &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;stable&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;rc&lt;/code&gt;, &lt;code&gt;experimental&lt;/code&gt;, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the correct way to publish a release candidate to npm, without making the &lt;code&gt;latest&lt;/code&gt; alias point to it is to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm publish &lt;span class="nt"&gt;--tag&lt;/span&gt; rc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workflow for versioning and publishing
&lt;/h2&gt;

&lt;p&gt;Overall, this is the workflow I follow to version my package and publish it on npm:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt; : Bump the version of the package with &lt;code&gt;npm version&lt;/code&gt; to create a git commit and a git tag (not to be confused with package distribution tags as I did above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm version patch &lt;span class="c"&gt;# for a patch version&lt;/span&gt;
npm version minor &lt;span class="c"&gt;# for a minor version&lt;/span&gt;
npm version major &lt;span class="c"&gt;# for a major version&lt;/span&gt;
npm version prerelease &lt;span class="nt"&gt;--pre-id&lt;/span&gt; rc &lt;span class="c"&gt;# for a release candidate version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt; : Have a GitHub Action workflow that runs when pushing tags on GitHub. The workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;reads the name of the tag that was pushed&lt;/li&gt;
&lt;li&gt;defines the distribution tag (&lt;code&gt;latest&lt;/code&gt; if the tag matches the pattern &lt;code&gt;X.Y.Z&lt;/code&gt; or &lt;code&gt;rc&lt;/code&gt; if it matches the pattern &lt;code&gt;X.Y.Z-rcN&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;runs &lt;code&gt;npm publish --tag [latest|rc]&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt; : Push generated tag on GitHub to trigger the workflow&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt; : Profit ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;Something that helped me clear things out in my head was the command &lt;code&gt;npm view&lt;/code&gt;. You can run it to inspect any published package on npm. For example, you can use it to inspect the distribution tags of a package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm view web3 dist-tags
&lt;span class="o"&gt;{&lt;/span&gt; latest: &lt;span class="s1"&gt;'1.7.1'&lt;/span&gt;, next: &lt;span class="s1"&gt;'2.0.0-alpha.1'&lt;/span&gt;, rc: &lt;span class="s1"&gt;'1.7.2-rc.0'&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>npm</category>
      <category>dist</category>
      <category>publish</category>
    </item>
    <item>
      <title>Git commits from GitLab CI</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Wed, 04 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/git-commits-from-gitlab-ci-2l90</link>
      <guid>https://dev.to/nop33/git-commits-from-gitlab-ci-2l90</guid>
      <description>&lt;p&gt;Last week I implemented a search functionality for a static website with &lt;a href="https://lunrjs.com/" rel="noopener noreferrer"&gt;Lunr.js&lt;/a&gt;. The way I implemented the search system, depends on 2 files to work, the &lt;code&gt;pagesData.json&lt;/code&gt; and the &lt;code&gt;searchIndex.json&lt;/code&gt;. These 2 files get generated by running an npm script (I called it &lt;code&gt;yarn createSearchIndex&lt;/code&gt;) which will parse all markdown files of the static website (pages and blog posts) and create the 2 aforementioned files. Obviously, I do not want to be responsible for running this script on my machine every time someone publishes a new blog post through our CMS (we use &lt;a href="https://www.netlifycms.org/" rel="noopener noreferrer"&gt;NetlifyCMS&lt;/a&gt;, so when someone wants to create a new blog post, they simply log in into the CMS, create the blog post, and the CMS will create a new commit on the branch NetlifyCMS connected to). What I want is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User commits a new blog post in branch &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A GitLab CI pipeline is triggered, which runs the &lt;code&gt;yarn createSearchIndex&lt;/code&gt; script, which in turn generates the new &lt;code&gt;pagesData.json&lt;/code&gt; and the &lt;code&gt;searchIndex.json&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;GitLab CI commits the new &lt;code&gt;pagesData.json&lt;/code&gt; and &lt;code&gt;searchIndex.json&lt;/code&gt; files without triggering a new CI pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Allowing GitLab runner to push to the git repo
&lt;/h2&gt;

&lt;p&gt;We need to ensure that the GitLab runner machine can push new changes to our repository, so we need to have an SSH key and a git user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create SSH key pair
&lt;/h3&gt;

&lt;p&gt;Create a public (&lt;code&gt;deploy_key.pub&lt;/code&gt;) and private (&lt;code&gt;deploy_key&lt;/code&gt;) SSH key pair without a passphrase with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -f deploy_key -N ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create environment variables
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;settings/ci_cd&lt;/code&gt; page of your GitLab project, define 3 environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CI_GIT_USER_EMAIL&lt;/code&gt; with the email of the git user that will appear in the commits made by the GitLab runner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CI_GIT_USER_USERNAME&lt;/code&gt; with the name of the git user that will appear in the commits made by the GitLab runner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt; with the contents of the private SSH key (you can copy the contents with &lt;code&gt;pbcopy &amp;lt; deploy_key&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2Fa8db2c0a6ef7848bd72db61acd1b810e%2Fe9b56%2Fci-vars.jpg" 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%2Fwww.iliascreates.com%2Fstatic%2Fa8db2c0a6ef7848bd72db61acd1b810e%2Fe9b56%2Fci-vars.jpg" alt="CI environment variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create deploy key
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;settings/repository&lt;/code&gt; page of the GitLab project we need to create a &lt;strong&gt;deploy key&lt;/strong&gt;. Create a new deploy key and paste the contents of the previously generated public key (&lt;code&gt;pbcopy &amp;lt; deploy_key.pub&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Make sure to enable &lt;em&gt;Grant write permissions to this key&lt;/em&gt;, otherwise the runner won’t be able to push new commits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F830a2cdb1e175eb135bb6050d6fa84ed%2F82b8c%2Fci-deploy-key.jpg" 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%2Fwww.iliascreates.com%2Fstatic%2F830a2cdb1e175eb135bb6050d6fa84ed%2F82b8c%2Fci-deploy-key.jpg" alt="CI deploy key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Write .gitlab-ci.yml file
&lt;/h3&gt;

&lt;p&gt;A basic version of my &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:14&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;commit&lt;/span&gt;

&lt;span class="na"&gt;update-index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commit&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Set up the SSH key and the known_hosts file&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;which&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ssh-agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt-get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt-get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;openssh-client&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;)'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eval $(ssh-agent -s)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - &amp;gt; /dev/null&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mkdir -p ~/.ssh&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ssh-keyscan my.selfhostedgitlab.com &amp;gt;&amp;gt; ~/.ssh/known_hosts&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Configure git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.email "${CI_GIT_USER_EMAIL}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git config --global user.name "${CI_GIT_USER_USERNAME}"&lt;/span&gt;
      &lt;span class="s"&gt;# Cone git repo&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git clone git@my.selfhostedgitlab.com:${CI_PROJECT_PATH}.git&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd ${CI_PROJECT_NAME}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git checkout main&lt;/span&gt;
      &lt;span class="s"&gt;# run scripts to generate new pagesData.json and searchIndex.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;yarn &amp;amp;&amp;amp; yarn createSearchIndex&lt;/span&gt;
      &lt;span class="s"&gt;# Check if there is something to commit, commit files, and push&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;if [[-n $(git status --porcelain)]]; then&lt;/span&gt;
        &lt;span class="s"&gt;git add src/pagesData.json src/searchindex.json &amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;git commit -m "Update search index" &amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="s"&gt;git push origin main -o ci.skip&lt;/span&gt;
      &lt;span class="s"&gt;fi&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to add the &lt;code&gt;-o ci.skip&lt;/code&gt; flag in the &lt;code&gt;git push&lt;/code&gt; command, so that you don’t trigger another pipeline.&lt;/p&gt;

&lt;p&gt;The commit should then appear, with the name of the git user you have configured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.iliascreates.com%2Fstatic%2F219584c2b0e939bd9c6ddc759306f156%2F82b8c%2Fci-commit.jpg" 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%2Fwww.iliascreates.com%2Fstatic%2F219584c2b0e939bd9c6ddc759306f156%2F82b8c%2Fci-commit.jpg" alt="CI commit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;These resources helped me figure it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forum.gitlab.com/t/git-push-from-inside-a-gitlab-runner/30554" rel="noopener noreferrer"&gt;https://forum.gitlab.com/t/git-push-from-inside-a-gitlab-runner/30554&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.gitlab.com/blog/2017/11/02/automating-boring-git-operations-gitlab-ci/" rel="noopener noreferrer"&gt;https://about.gitlab.com/blog/2017/11/02/automating-boring-git-operations-gitlab-ci/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marcosschroh.github.io/posts/autobumping-with-gitlab/" rel="noopener noreferrer"&gt;https://marcosschroh.github.io/posts/autobumping-with-gitlab/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forum.gitlab.com/t/is-it-possible-to-commit-artifacts-into-the-git/22218/7" rel="noopener noreferrer"&gt;https://forum.gitlab.com/t/is-it-possible-to-commit-artifacts-into-the-git/22218/7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@pankajpatel?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Pankaj Patel&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/gitlab?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>ci</category>
      <category>git</category>
      <category>commit</category>
    </item>
    <item>
      <title>Question regarding best practice on managing state</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Thu, 22 Jul 2021 12:52:04 +0000</pubDate>
      <link>https://dev.to/nop33/question-regarding-best-practice-on-managing-state-h89</link>
      <guid>https://dev.to/nop33/question-regarding-best-practice-on-managing-state-h89</guid>
      <description>&lt;p&gt;I am working on an app that presents a list of questions to the user and expects answers. The representation of data is something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&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="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;answer&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My question is, should I store this whole object in the state, or only store the answers?&lt;/p&gt;

&lt;p&gt;In the first case, the code would look something like the following. Here we can see that questions and answers are bundled together, which makes it easier to iterate over them and display them together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WheelOfLife&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStepsData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;answer&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="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStepsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;newStepsData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;
    &lt;span class="nx"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newStepsData&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&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;In the second case, I am following the recommendation of the &lt;a href="https://reactjs.org/docs/thinking-in-react.html"&gt;React docs&lt;/a&gt; that says &lt;em&gt;"Does it remain unchanged over time? If so, it probably isn’t state."&lt;/em&gt;, but now questions and answers are separated and it becomes a tiny bit more complicated to access the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepsData&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Career&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How satisfied are you with Love&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WheelOfLife&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStepsAnswers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;newStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt;
    &lt;span class="nx"&gt;setStepsAnswers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newStepsAnswers&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stepsAnswers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&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;Thank you for the feedback!&lt;/p&gt;

</description>
      <category>react</category>
      <category>question</category>
      <category>state</category>
    </item>
    <item>
      <title>Splitting huge NetlifyCMS config.yml file to multiple JS files</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Thu, 15 Jul 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/splitting-huge-netlifycms-config-yml-file-to-multiple-js-files-4hn0</link>
      <guid>https://dev.to/nop33/splitting-huge-netlifycms-config-yml-file-to-multiple-js-files-4hn0</guid>
      <description>&lt;p&gt;For a long time, I have been procrastinating in finding a solution to my ever-growing NetlifyCMS config.yml file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;The website I have been working on consists of 18 unique pages (home page, about page, contact page, etc) as well as of a blog feature. The content of each page is stored in frontmatter inside markdown files, except for the content of the blog posts which is stored in actual markdown.&lt;/p&gt;

&lt;p&gt;To demonstrate the differences between the content of each page, have a look at the following snippets of 3 of those pages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Home&lt;/span&gt;
&lt;span class="na"&gt;seo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do&lt;/span&gt;
    &lt;span class="s"&gt;eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim&lt;/span&gt;
    &lt;span class="s"&gt;veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea&lt;/span&gt;
    &lt;span class="s"&gt;commodo consequat.&lt;/span&gt;
&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text_part_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sed ut perspiciatis&lt;/span&gt;
    &lt;span class="na"&gt;text_part_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unde omnis iste natus&lt;/span&gt;
  &lt;span class="na"&gt;button&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/lorem&lt;/span&gt;
  &lt;span class="na"&gt;subtitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&lt;/span&gt;
&lt;span class="na"&gt;key_facts_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;At vero eos et accusamus&lt;/span&gt;
  &lt;span class="na"&gt;key_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static/images/icons/clock.svg&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nam libero tempore&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static/images/icons/earth.svg&lt;/span&gt;
&lt;span class="na"&gt;testimonials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;About page&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;About&lt;/span&gt;
&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;
  &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;span class="na"&gt;seo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Quis autem vel eum&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita distinctio.&lt;/span&gt;
&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;About&lt;/span&gt;
&lt;span class="na"&gt;video_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
    &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita distinctio. Nam libero&lt;/span&gt;
    &lt;span class="s"&gt;tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus&lt;/span&gt;
    &lt;span class="s"&gt;id quod maxime placeat facere possimus.&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rerum necessitatibus saepe&lt;/span&gt;
  &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://player.vimeo.com/video/845028457&lt;/span&gt;
&lt;span class="na"&gt;team_section&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Blog post&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Et harum quidem rerum facilis est et expedita&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;2017-02-28&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit&lt;/span&gt;
  &lt;span class="s"&gt;quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda&lt;/span&gt;
  &lt;span class="s"&gt;est, omnis dolor repellendus. Temporibus autem quibusdam et aut.&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/images/uploads/featured-image.jpg&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;This is the body of the **blog post** , written in markdown.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see the content of each page is vastly different from the others, yet there are some patterns that repeat themselves. To create a NetlifyCMS configuration file for all of the 18 pages and other data files I needed a &lt;strong&gt;2’000 lines of code&lt;/strong&gt; config.yml file. This approach works, but the code is not very DRY and it is really hard to change something or add something new. It’s time to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the NetlifyCMS configuration code DRY
&lt;/h2&gt;

&lt;p&gt;One valid approach to not repeatting parts of the YAML configuration would be to use &lt;a href="https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/"&gt;YAML anchors (&lt;code&gt;&amp;amp;&lt;/code&gt;), aliases (&lt;code&gt;*&lt;/code&gt;), and overrides (&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;)&lt;/a&gt;. But I discovered a better approach. ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  Using JavaScript instead of YAML
&lt;/h3&gt;

&lt;p&gt;Through the &lt;a href="https://github.com/netlify/netlify-cms/issues/3624#issuecomment-616076374"&gt;related GitHub issue&lt;/a&gt; and the new &lt;a href="https://www.netlifycms.org/docs/beta-features/#manual-initialization"&gt;beta NetlifyCMS feature of manual initialization&lt;/a&gt; I discovered that I am not restricted to using YAML to configure the CMS. Using JS does not only allow me to split the configuration into multiple files but to create objects and functions to keep my code DRY as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  File structure
&lt;/h4&gt;

&lt;p&gt;The file structure that fits my needs is the following (the tree view is simplified to only show the related pages from the example snippets I mentioned above). Below I explain what the purpose and contents of each file are.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── cms
    ├── index.js
    ├── editor-components.js
    └── config/
        ├── index.js
        ├── fields.js
        ├── patterns.js
        └── collections/
            ├── blog-posts
            │ └── index.js
            └── pages
                ├── about.js
                ├── blog.js
                ├── home.js
                └── index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;Here is where I import the NetlifyCMS library. This file will eventually be parsed by Webpack in my setup and included in the &lt;code&gt;admin.html&lt;/code&gt; file that is used to load the CMS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import NetlifyCMS library&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CMS&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;netlify-cms&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;netlify-cms/dist/cms.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;// Import custom editor component from cms/editor-components.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;myCustomEditorComponent&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;./editor-components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Import NetlifyCMS JS configuration object from cms/config/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;config&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;./config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Disable loading of the configuration from the default config.yml file&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CMS_MANUAL_INIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Initialize NetlifyCMS with the JS configuration objext&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CMS_CONFIGURATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Register the custom editor component&lt;/span&gt;
&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerEditorComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myCustomEditorComponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/editor-components.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This file is not necessary to explain the setup, I’ve written another blog post on &lt;a href="https://dev.to/blog/post/embeding-youtube-videos-markdown-gatsby-netlifycms/"&gt;creating custom NetlifyCMS editor components&lt;/a&gt;, check it out!&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This is where we build our NetlifyCMS JS configuration object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import the configuration of each collection from cms/config/collections&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;blogPostsCollection&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;./collections/blog-posts&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;pagesCollection&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;./collections/pages&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;pressReleasesCollection&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;./collections/press-releases&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;servicesCollection&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;./collections/services&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;siteConfigurationCollection&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;./collections/site-configuration&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;testimonialsCollection&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;./collections/testimonials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Build the Netlify JS configuration object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gitlab&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth_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;implicit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;app_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MY_APP_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;api_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://my-self-hosted-gitlab.com/api/v4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://my-self-hosted-gitlab.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;oauth/authorize&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="c1"&gt;// It is not required to set `load_config_file` if the `config.yml` file is&lt;/span&gt;
  &lt;span class="c1"&gt;// missing, but will improve performance and avoid a load error.&lt;/span&gt;
  &lt;span class="na"&gt;load_config_file&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;publish_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editorial_workflow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;media_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site/static/images/uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;public_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Include the collections imported from cms/config/collections&lt;/span&gt;
    &lt;span class="nx"&gt;pagesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;servicesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;blogPostsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;commonPageSectionsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;testimonialsCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pressReleasesCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;siteConfigurationCollection&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/collections/pagesCollection/index.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;This file groups together all the configuration objects from each individual page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collectionDefaults&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;../../patterns&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;homePageConfig&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;./home&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;aboutPageConfig&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;./about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pagesCollection&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;collectionDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;homePageConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aboutPageConfig&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;pagesCollection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/collections/pagesCollection/home.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;The configuration of a page. It utilizes fields and groups of fields (patterns) to keep the configuration code of the page DRY.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listField&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;../../fields&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;pageDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;buttonDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;,&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;../../patterns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site/content/_index.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;buttonDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keyfacts section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyfacts_section&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;listField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Keyfacts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyfacts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/fields.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;The basic building blocks of the configuration file. Creating functions that return JS objects allows us to keep the code DRY by passing the necessary parameters when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;String&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s2"&gt;`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;code&gt;cms/config/patterns.js&lt;/code&gt;
&lt;/h5&gt;

&lt;p&gt;Sometimes, patterns in the configuration of pages and collections emerge. This file groups fields together and exports these patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hiddenField&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;./fields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collectionDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Menu title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;hiddenField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;menu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;seo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]),&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiColorTitleDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text part 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text_part_1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text part 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text_part_2&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttonDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;objectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;stringField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleWithSubtitleDefaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtitleIsMarkdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;multiColorTitleDefaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subtitleIsMarkdown&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;markdownField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subtitle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subtitle&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The above setup helped me reduce the total lines of code from around 2000 to just 900. It has also been proven that making updates to the configuration of each page has become a piece of cake 🍰 (navigating to the dedicated file is very easy by typing for example &lt;em&gt;“cms about”&lt;/em&gt; into the search bar of my editor to reach the configuration of the about page).&lt;/p&gt;

&lt;h2&gt;
  
  
  Other resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mrkaluzny.com/blog/dry-netlify-cms-config-with-manual-initialization/"&gt;Configuring NetlicyCMS manual initialization in Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Embedding YouTube videos in Gatsby blog posts using NetlifyCMS</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/embedding-youtube-videos-in-gatsby-blog-posts-using-netlifycms-263j</link>
      <guid>https://dev.to/nop33/embedding-youtube-videos-in-gatsby-blog-posts-using-netlifycms-263j</guid>
      <description>&lt;p&gt;In one of the websites I have built, I needed to be able to embed YouTube videos in the markdown content of the blog posts. I am using &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt; together with &lt;a href="https://www.netlifycms.org/"&gt;NetlifyCMS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two are the things that I had to figure out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do I represent the YouTube video in markdown so that the &lt;code&gt;gatsby-transformer-remark&lt;/code&gt; plugin (which is responsible for converting markdown to HTML) can render it? Bonus: How do I make sure that the iframe of the embedded videos is responsive and looks good on mobile devices?&lt;/li&gt;
&lt;li&gt;How do I extend NetlifyCMS’ markdown widget to support adding YouTube links in the content, without forcing the user to switch to the markdown view?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Gatbsy part
&lt;/h2&gt;

&lt;p&gt;Thankfully, Gatsby’s ecosystem is huge! From Gatsby’s &lt;a href="https://www.gatsbyjs.com/docs/how-to/images-and-media/working-with-video/"&gt;Working with Video&lt;/a&gt; guide, I discovered the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/"&gt;&lt;code&gt;gatsby-remark-embed-video&lt;/code&gt;&lt;/a&gt; plugin. This allows me to represent a YouTube video in markdown as a simple code block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`youtube: https://www.youtube.com/watch?v=dQw4w9WgXcQ`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simply install the plugin and include it in the list of plugins in &lt;code&gt;gatsby-config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add gatsby-remark-embed-video
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure that the video will be responsive, use the &lt;code&gt;gatsby-remark-responsive-iframe&lt;/code&gt; plugin and place it &lt;strong&gt;after&lt;/strong&gt; the &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; one, as &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/?=video#install"&gt;recommended&lt;/a&gt; by the plugin’s author:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-transformer-remark`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-embed-video`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-images`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;`gatsby-remark-responsive-iframe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now add a YouTube video in your markdown file and Gatsby will know how to render it. Your markdown file should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Here&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blog&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;YouTube&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;video"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-06-03"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## This is my fav song of all times&lt;/span&gt;

Don't cha think?

&lt;span class="sb"&gt;`youtube: https://www.youtube.com/watch?v=dQw4w9WgXcQ`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The NetlifyCMS part
&lt;/h2&gt;

&lt;p&gt;Great, now all we need is to figure out how to extend NetlifyCMS’ markdown widget to be able to add code blocks like the above to our markdown content. Checking out the &lt;a href="https://www.netlifycms.org/docs/widgets/#markdown"&gt;official documentation of the markdown widget&lt;/a&gt; I discovered the guide about &lt;a href="https://www.netlifycms.org/docs/custom-widgets/#registereditorcomponent"&gt;Creating Custom Widgets&lt;/a&gt; and specifically the &lt;code&gt;registerEditorComponent&lt;/code&gt; method. And hey, what luck, there is an actual example of registering an editor component for adding YouTube videos! Today is a good day.&lt;/p&gt;

&lt;p&gt;The question now is, where do I add the code to call this method? I am simply using the &lt;code&gt;gatsby-plugin-netlify-cms&lt;/code&gt; plugin in my &lt;code&gt;gatsby-config.js&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`gatsby-plugin-netlify-cms`&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;Diving into the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-netlify-cms/#options"&gt;documentation of the plugin&lt;/a&gt;, I discovered the &lt;code&gt;modulePath&lt;/code&gt; option which lets me specify a path to a file where I can import the &lt;code&gt;CMS&lt;/code&gt; object. Sweet!&lt;/p&gt;

&lt;p&gt;Update your configuration to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-netlify-cms`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;modulePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/src/cms.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and create the &lt;code&gt;cms.js&lt;/code&gt; file in the &lt;code&gt;/src&lt;/code&gt; directory (feel free to place it wherever you want as long as you link it properly in the &lt;code&gt;gatsby-config.js&lt;/code&gt; file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CMS&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;netlify-cms-app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;CMS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerEditorComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;youtube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YouTube&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Youtube video URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^`youtube:&lt;/span&gt;&lt;span class="se"&gt;\s(&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;`$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;`youtube: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&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;toPreview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&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="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To explain what's going on above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;pattern&lt;/code&gt; attribute defines the regular expression that will look for the line in the markdown content that starts with the string "`youtube: " and catch anything that comes after it, which in our case is the link to the video.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;fromBlock&lt;/code&gt; attribute defines a function that is responsible to initialize the value of the rendered widget. In other words, it takes the link of the video from the &lt;code&gt;pattern&lt;/code&gt; and adds it in the input field of the YouTube widget.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;toBlock&lt;/code&gt; function defines the representation of the YouTube video in the markdown content. To comply with the &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; Gatsby plugin, this needs to be a line that starts with a backtick, followed by the word "youtube: ", followed by the video link, and ends with another backtick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it! You can now go to your CMS page, click the ”+” icon in the markdown editor, select “YouTube”, paste your YouTube link and save your blog post.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/55f3730698e71321addb6711b12b4a20/b89ad/markdown-widget-youtube.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FeB0MCz9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://iliascreates.com/static/55f3730698e71321addb6711b12b4a20/828fb/markdown-widget-youtube.jpg" alt="NetlifyCMS markdown widget" title="NetlifyCMS markdown widget" width="630" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about Vimeo and VideoPress?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;gatsby-remark-embed-video&lt;/code&gt; plugin supports &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-remark-embed-video/#usage"&gt;multiple video hosting services&lt;/a&gt; like Vimeo and VideoPress, not just YouTube (it also says it supports Twitch and Twitch live, but I couldn’t manage to make it work). Simply replace the instances of &lt;code&gt;youtube&lt;/code&gt; with &lt;code&gt;video&lt;/code&gt; in the &lt;code&gt;cms.js&lt;/code&gt; file to support them!&lt;/p&gt;

</description>
      <category>netlifycms</category>
      <category>gatsby</category>
      <category>video</category>
      <category>markdown</category>
    </item>
    <item>
      <title>From googling "React Native" to having a working app on my phone in 5 hours</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Thu, 20 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/from-googling-react-native-to-having-a-working-app-on-my-phone-in-5-hours-3jeg</link>
      <guid>https://dev.to/nop33/from-googling-react-native-to-having-a-working-app-on-my-phone-in-5-hours-3jeg</guid>
      <description>&lt;p&gt;This morning I challenged myself to see how difficult it would be to have a running Android app installed on my phone, starting from scratch. I wanted a simple app that given a number it would calculate a random integer number between 1 and the number I provided. I wanna use it for performing a “random walk” using the train system of Switzerland to explore the country. I know, silly, but why not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the dev environment
&lt;/h2&gt;

&lt;p&gt;I started by googling “React Native”. Headed to the &lt;a href="https://reactnative.dev/"&gt;official website&lt;/a&gt;. I read through the home page, run the command to start a new project, and went on to &lt;a href="https://reactnative.dev/docs/tutorial"&gt;read the basics&lt;/a&gt; and the &lt;a href="https://reactnative.dev/docs/getting-started"&gt;getting started guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx react-native init MyTestApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I saw in the &lt;a href="https://reactnative.dev/docs/getting-started"&gt;getting started guide&lt;/a&gt; is that I need to set up my dev environment. Since I am not familiar with mobile development, I decided to start with Expo CLI instead of the React Native CLI (which is what I used above to set up my first project). This &lt;a href="https://stackoverflow.com/questions/39170622/what-is-the-difference-between-expo-and-react-native"&gt;SO thread&lt;/a&gt; describes well the differences between the 2 cli’s.&lt;/p&gt;

&lt;p&gt;Simply following the &lt;a href="https://reactnative.dev/docs/environment-setup"&gt;Expo CLI Quickstart guide&lt;/a&gt; didn’t work. I installed the global expo-cli package, initialized a new project, started the development server, installed the &lt;a href="https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=en&amp;amp;gl=US"&gt;Expo Go Android app&lt;/a&gt; on my phone, connected both my laptop and my phone on the same WiFi, but there was no QR code on my terminal to scan with the Expo Go app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ yarn global add expo-cli
...
success Installed "expo-cli@4.4.7" with binaries:
      - expo
      - expo-cli
✨ Done in 65.40s.

$ expo init AwesomeProject
✔ Choose a template: › minimal bare and minimal, just the essentials to get you started
✔ Downloaded and extracted project files.
🧶 Using Yarn to install packages. Pass --npm to use npm instead.
✔ Installed JavaScript dependencies.

$ cd AwesomeProject
$ yarn start
yarn run v1.22.10
$ react-native start

               ######                ######
             ###     ####        ####     ###
            ##          ###    ###          ##
            ##             ####             ##
            ##             ####             ##
            ##           ##    ##           ##
            ##         ###      ###         ##
             ##  ########################  ##
          ######    ###            ###    ######
      ###     ##    ##              ##    ##     ###
   ###         ## ###      ####      ### ##         ###
  ##           ####      ########      ####           ##
 ##             ###     ##########     ###             ##
  ##           ####      ########      ####           ##
   ###         ## ###      ####      ### ##         ###
      ###     ##    ##              ##    ##     ###
          ######    ###            ###    ######
             ##  ########################  ##
            ##         ###      ###         ##
            ##           ##    ##           ##
            ##             ####             ##
            ##             ####             ##
            ##          ###    ###          ##
             ###     ####        ####     ###
               ######                ######

                 Welcome to React Native!
                Learn once, write anywhere

To reload the app press "r"
To open developer menu press "d"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next idea was to learn more about how this expo thing works. Going on &lt;a href="https://expo.io/"&gt;Expo’s website&lt;/a&gt;, clicking &lt;a href="https://docs.expo.io/"&gt;Get Started&lt;/a&gt;, moving to the &lt;a href="https://docs.expo.io/get-started/installation/"&gt;installation guide&lt;/a&gt; and finally to the &lt;a href="https://docs.expo.io/get-started/create-a-new-app/"&gt;Create a new app guide&lt;/a&gt; I found the command that displayed the QR code on my screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ expo start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scanning the QR code with the &lt;a href="https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=en&amp;amp;gl=US"&gt;Expo Go app&lt;/a&gt; on my Android phone opened up the app on my phone. Editing the source code on my laptop changes quickly refreshes the app on my phone. Nice! The whole process took a bit less than 1.5h 🤦‍♂️ But whatever, we’re moving!&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding the app
&lt;/h2&gt;

&lt;p&gt;Without having much experience with React, I managed to code the simple concept of an app that I mentioned earlier by simply reading the &lt;a href="https://reactnative.dev/docs/getting-started"&gt;Introduction&lt;/a&gt;, &lt;a href="https://reactnative.dev/docs/intro-react-native-components"&gt;Core Components and Native Components&lt;/a&gt;, &lt;a href="https://reactnative.dev/docs/intro-react"&gt;React Fundamentals&lt;/a&gt; and &lt;a href="https://reactnative.dev/docs/handling-text-input"&gt;Handling Text Input&lt;/a&gt; React Native guides. Great, the app seems to be working on the dev environment, let’s compile it and install it on my phone, I said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling the app
&lt;/h2&gt;

&lt;p&gt;Initially, I was planning on releasing the app on the Google Play Store and thus started by following the &lt;a href="https://reactnative.dev/docs/signed-apk-android"&gt;Publishing to Google Play Store guide&lt;/a&gt; of the React Native documentation. I installed the latest Java JDK, generated a private signing key, set up the Grandle variables, added the signing configuration to my app’s Grandle config file only to realize that the Google Play store required a $25 fee for registering. I decided to postpone it for the day that I’ll actually have a useful app I’d like to share with the world 😅&lt;/p&gt;

&lt;p&gt;I moved on with generating the Android App Bundle with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd android
$ ./gradlew bundleRelease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but I received the error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;General error during semantic analysis: Unsupported class file major version 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is that the Grandle 6.8 version that my app is using does not support the latest Java JDK (16). Someone &lt;a href="https://github.com/gradle/gradle/issues/13629#issuecomment-844561525"&gt;commented literally 11 hours ago&lt;/a&gt; that Gradle 7.0 fully supports Java 16, so I upgraded Grandle by simply changing the version in the file &lt;code&gt;android/gradle/wrapper/gradle-wrapper.properties&lt;/code&gt;, but now I got &lt;a href="https://github.com/expo/expo/issues/12774"&gt;a new error&lt;/a&gt; after running the above command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plugin with id ‘maven’ not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decided to downgrade to Java 11:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install java11
$ echo 'export PATH="/usr/local/opt/openjdk@11/bin:$PATH"' &amp;gt;&amp;gt; ~/.zshrc
$ export CPPFLAGS="-I/usr/local/opt/openjdk@11/include"
$ source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but then I got yet another error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Could not determine the dependencies of task ':app:bundleReleaseResources'.
&amp;gt; SDK location not found. Define location with an ANDROID_SDK_ROOT environment variable or by setting the sdk.dir path in your project's local properties file at '.../android/local.properties'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some googling on how to install the Android SDK (since &lt;a href="https://formulae.brew.sh/cask/android-sdk"&gt;the one I found in Homebrew&lt;/a&gt; is deprecated), I decided to install the &lt;a href="https://formulae.brew.sh/cask/android-studio"&gt;Android Studio&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install --cask android-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I opened the application to download and install all necessary packages for Android development, including the Android SDK.&lt;/p&gt;

&lt;p&gt;But we’re not done with errors apparently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Failed to install the following Android SDK packages as some licences have not been accepted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fix this, I &lt;a href="https://stackoverflow.com/questions/54273412/failed-to-install-the-following-android-sdk-packages-as-some-licences-have-not/66392874"&gt;installed the Android SDK command line tool&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opened Android Studio&lt;/li&gt;
&lt;li&gt;Went to SDK manager&lt;/li&gt;
&lt;li&gt;Clicked on SDK tools tab&lt;/li&gt;
&lt;li&gt;Installed Android SDK command line tool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For one last time, I run the build command and it finally worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./gradlew bundleRelease
...
BUILD SUCCESSFUL in 3m 14s
494 actionable tasks: 494 executed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing the app on my Android phone
&lt;/h2&gt;

&lt;p&gt;The last step of my challenge was to install the app on my phone. I figured the easiest way would be to move the output APK to my phone via USB. And so I did by installing and using the &lt;a href="https://formulae.brew.sh/cask/android-file-transfer"&gt;Android File Transfer&lt;/a&gt; app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A few years back I was extremely hesitant and scared of even attempting to create a native app of my own. My impostor syndrome was hitting me hard every time I was facing a problem. An inner voice was judging me that I am not good enough and that I should not even try. If you are stuck in a similar situation, I hope that this blog post will unblock you from a potential roadblock you’re facing. Keep believing in your skill to learn. Not in your dev skills. It might have taken me 5 hours to simply set up a dev environment, code a simple app and figure out how to compile it and install it, but I did it. And so can you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using Ansible unarchive module with Alpine Linux</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Mon, 05 Apr 2021 15:18:00 +0000</pubDate>
      <link>https://dev.to/nop33/using-ansible-unarchive-module-with-alpine-linux-3k3k</link>
      <guid>https://dev.to/nop33/using-ansible-unarchive-module-with-alpine-linux-3k3k</guid>
      <description>&lt;p&gt;If you are running &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; on &lt;a href="https://alpinelinux.org/"&gt;Alpine Linux&lt;/a&gt; and you use the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/unarchive_module.html"&gt;&lt;code&gt;unarchive&lt;/code&gt; builtin module&lt;/a&gt; you might have faced the following error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Errno 2] No such file or directory: b'-T'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for this is that the &lt;code&gt;unzip&lt;/code&gt; tool in Alpine does not have a &lt;code&gt;-T&lt;/code&gt; option. To be able to use the &lt;code&gt;unarchive&lt;/code&gt; Ansible module with Alpine Linux, you need to install &lt;code&gt;unzip&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apk add unzip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/ansible/ansible/issues/39029"&gt;related GitHub issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@simonfitall?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Simon Fitall&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/apls?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Recording screen, mic input and audio output with one app</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Fri, 26 Mar 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/nop33/recording-screen-mic-input-and-audio-output-with-one-app-58g4</link>
      <guid>https://dev.to/nop33/recording-screen-mic-input-and-audio-output-with-one-app-58g4</guid>
      <description>&lt;p&gt;I’ve been for years a very happy user of &lt;a href="https://getkap.co/"&gt;Kap&lt;/a&gt;, a free open-source app to record the screen of my MacBook.&lt;/p&gt;

&lt;p&gt;Recently I wanted to perform some interviews during a requirements gathering process for a new project at work. I needed a way to record my screen, my voice but also the voice of the interviewee. Essentially I needed to capture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screen&lt;/li&gt;
&lt;li&gt;Audio input&lt;/li&gt;
&lt;li&gt;Audio output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, as usual, I started with a simple &lt;a href="https://www.google.com/search?q=record+screen+audio+input+and+output+mac&amp;amp;sxsrf=ALeKk03pGfOhM0PDc64SpqsGcRIu_a3Tkw%3A1616781405527&amp;amp;ei=XSBeYLnAH4X0kwXXk4TYDw&amp;amp;oq=record+screen+audio+input+and+output+mac&amp;amp;gs_lcp=Cgdnd3Mtd2l6EAM6BwgAEEcQsANQurYQWLq2EGDGuhBoA3ACeACAAVuIAZ4BkgEBMpgBAKABAaoBB2d3cy13aXrIAQjAAQE&amp;amp;sclient=gws-wiz&amp;amp;ved=0ahUKEwj54uuKxM7vAhUF-qQKHdcJAfsQ4dUDCA0&amp;amp;uact=5"&gt;Google search&lt;/a&gt; to see what is out there. I found many solutions that required downloading some apps (like &lt;a href="https://github.com/mattingalls/Soundflower"&gt;Soundflower&lt;/a&gt;, &lt;a href="https://rogueamoeba.com/loopback/"&gt;LoopBack&lt;/a&gt; or &lt;a href="https://www.telestream.net/screenflow/"&gt;ScreenFlow&lt;/a&gt;) and use some of them in combination with Quicktime Player, a native macOS app.&lt;/p&gt;

&lt;p&gt;5 minutes had passed and I already felt overwhelmed. I thought this was gonna be easier. And then it hit me: &lt;em&gt;“Why haven’t you checked if Kap can do that?”&lt;/em&gt;. Dah!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All I literally had to do was to go to the preferences of the app and turn 2 toggles on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Enable &lt;strong&gt;Audio recording&lt;/strong&gt; from the General tab&lt;/li&gt;
&lt;li&gt;Enable the &lt;strong&gt;soundflower plugin&lt;/strong&gt; from the Plugins tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finito.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/2d412def287cfcdf24aec2dedaad4a6b/7321b/general-preferences.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V1aTTBkM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://iliascreates.com/static/2d412def287cfcdf24aec2dedaad4a6b/f058b/general-preferences.png" alt="Kap general preferences tab" title="Kap general preferences tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/85a426bb4392aabd111ef151615d868a/7321b/plugin-preferences.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rtQySmzw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://iliascreates.com/static/85a426bb4392aabd111ef151615d868a/f058b/plugin-preferences.png" alt="Kap plugin preferences tab" title="Kap plugin preferences tab"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mac</category>
      <category>capture</category>
      <category>screen</category>
      <category>audio</category>
    </item>
    <item>
      <title>Dear frontender, how do you collaborate with your designer?</title>
      <dc:creator>nop33.eth</dc:creator>
      <pubDate>Fri, 19 Mar 2021 11:49:54 +0000</pubDate>
      <link>https://dev.to/nop33/dear-frontender-how-do-you-collaborate-with-your-designer-4790</link>
      <guid>https://dev.to/nop33/dear-frontender-how-do-you-collaborate-with-your-designer-4790</guid>
      <description>&lt;p&gt;Dear frontend engineers,&lt;/p&gt;

&lt;p&gt;How do you collaborate with your designers?&lt;/p&gt;

&lt;p&gt;Every frontender's wet dream is a clearly defined design system&lt;sup&gt;&lt;a href="https://www.invisionapp.com/inside-design/guide-to-design-systems/"&gt;1&lt;/a&gt;, &lt;a href="https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969"&gt;2&lt;/a&gt;&lt;/sup&gt; delivered in an inspectable format so that they can inspect every element of it, measure distances, paddings and margins, see colors and variants and basically every property that needs to eventually be translated in CSS.&lt;/p&gt;

&lt;p&gt;In my career, I have worked with designers that delivered their designs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in &lt;a href="https://www.sketch.com/"&gt;Sketch&lt;/a&gt; files, so I had to tell my boss to buy me a Sketch licence so I can inspect them&lt;/li&gt;
&lt;li&gt;with &lt;a href="https://www.invisionapp.com/"&gt;InVision&lt;/a&gt; which did not require any additional software, simply the web browser&lt;/li&gt;
&lt;li&gt;with &lt;a href="https://zeplin.io/"&gt;Zeplin&lt;/a&gt;, which only required installing the free software, no need for a licence&lt;/li&gt;
&lt;li&gt;sadly, I have also worked with designers that delivered their designs in PDF format...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far I had the best experience with Zeplin and InVision cause I could inspect the typography and design system separately from the main designs (although this highly depends on the quality of the designer's work).&lt;/p&gt;

&lt;p&gt;How do you prefer working with the designers you collaborate with? Which is your preferred tool and why? Do you have a specific process that you can share? How do you bridge the gap between designs and CSS/code?&lt;/p&gt;

&lt;p&gt;Thank you!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@timmossholder?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Tim Mossholder&lt;/a&gt; on &lt;a href="/s/photos/collaborate?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>software</category>
      <category>frontend</category>
      <category>collaboration</category>
    </item>
  </channel>
</rss>
