<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Vadim Smirnov</title>
    <description>The latest articles on Forem by Vadim Smirnov (@fuzzyreason).</description>
    <link>https://forem.com/fuzzyreason</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%2F366698%2Fe64a9613-cf27-4931-8a1d-2241a595b31f.jpeg</url>
      <title>Forem: Vadim Smirnov</title>
      <link>https://forem.com/fuzzyreason</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fuzzyreason"/>
    <language>en</language>
    <item>
      <title>Storybook Starter Guide: Learn Design System Principles</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 28 Mar 2025 17:44:22 +0000</pubDate>
      <link>https://forem.com/ckeditor/storybook-starter-guide-learn-design-system-principles-2b61</link>
      <guid>https://forem.com/ckeditor/storybook-starter-guide-learn-design-system-principles-2b61</guid>
      <description>&lt;p&gt;Run this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create storybook@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, it's a generic starter for a component library: a button, a header, and a few example stories. But underneath that simplicity lies something much more powerful — the foundation of a design system.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how Storybook’s starter is an introduction to core design system principles — reusability, composability, and consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Storybook?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt; is an open-source tool for building and testing UI components in isolation. Think of it as a dedicated workshop where you can create, preview, and document components in every possible state without spinning up the full application.&lt;/p&gt;

&lt;p&gt;But Storybook isn’t just a place to organize your UI components. In practice, it becomes a powerful platform that organizes frontend workflows by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Letting you build and debug components without business logic getting in the way.
&lt;/li&gt;
&lt;li&gt;Supporting a wide range of frameworks like React, Vue, Angular, Svelte, and more.
&lt;/li&gt;
&lt;li&gt;Offering a natural cross-team collaboration workflow — designers, developers, and stakeholders can all review and work on components to deliver great quality user interfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you're crafting a single button or a complete design system, Storybook helps you move faster, stay consistent, and collaborate more effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Using Storybook
&lt;/h3&gt;

&lt;p&gt;Now, let's address some more arguments in favor of using Storybook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visual Testing of Component States:&lt;/strong&gt; Each story represents a specific state of a component. This makes it easier to notice edge cases and ensure a consistent look across the application.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Generated Docs:&lt;/strong&gt; &lt;a href="https://storybook.js.org/docs/writing-docs/autodocs" rel="noopener noreferrer"&gt;Autodocs&lt;/a&gt; turns your stories into complete, shareable documentation. Anyone on the team can explore usage examples, parameters, and behaviors. This is also a great point when onboarding new team members on the project.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth Collaboration:&lt;/strong&gt; Designers, product managers, and QA engineers can view and interact with components in the browser, leave feedback, and review changes even without technical knowledge of setting up a local environment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works Across Frameworks:&lt;/strong&gt; Storybook supports React, Vue, Angular, Svelte, Web Components, and more, making it a good option for any team to avoid framework support limitations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted by Top Companies:&lt;/strong&gt; &lt;a href="https://storybook.js.org/showcase/the-washington-post-wpds" rel="noopener noreferrer"&gt;The Washington Post&lt;/a&gt;, &lt;a href="https://storybook.js.org/showcase/shopify-polaris-react" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt;, &lt;a href="https://storybook.js.org/showcase/nasa-jpl-explorer-1" rel="noopener noreferrer"&gt;NASA&lt;/a&gt;, and others rely on Storybook to power their UI workflows. It’s also the foundation behind popular component libraries like &lt;a href="https://storybook.js.org/showcase/chakra-chakra-ui" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt; or &lt;a href="https://storybook.js.org/showcase/microsoft-fluent-ui-web-components" rel="noopener noreferrer"&gt;Fluent UI&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Core Design System Principles in the Starter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reusability
&lt;/h3&gt;

&lt;p&gt;Reusability is a fundamental principle of any design system. It’s about creating abstract components that can be used in different use cases while maintaining a consistent look and feel.&lt;/p&gt;

&lt;p&gt;Let's review the included &lt;code&gt;Button&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;primary&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="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;primary&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button--primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button--secondary&lt;/span&gt;&lt;span class="dl"&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;button&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storybook-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`storybook-button--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;size&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;mode&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&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;label&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;button&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;It might look basic, but out of the box, it has a good set of props:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boolean prop &lt;code&gt;primary&lt;/code&gt; to toggle variants
&lt;/li&gt;
&lt;li&gt;Props for size, color, and label&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stories for this component reinforce reusability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;Primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&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;Secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&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;Large&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The story format encourages creating components in a &lt;a href="https://dev.to/jps27cse/understanding-reusable-components-and-the-dry-principle-4ijm"&gt;Don’t Repeat Yourself&lt;/a&gt; (DRY), consistent manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composability
&lt;/h3&gt;

&lt;p&gt;Design systems aren’t just about single-use components — they’re about components that combine like LEGO blocks.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Header&lt;/code&gt; component showcases this principle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onLogout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onCreateAccount&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HeaderProps&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;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"storybook-header"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"32"&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0 0 32 32"&lt;/span&gt; &lt;span class="na"&gt;xmlns&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;http://www.w3.org/2000/svg&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      ...
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Acme&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&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;user&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              Welcome, &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;b&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;b&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;!
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onLogout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Log out"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onLogin&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Log in"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;primary&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"small"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onCreateAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Sign up"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&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;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we don't hardcode buttons that should be present in the header but reuse the existing&lt;code&gt;Button&lt;/code&gt; component. This is how good design systems are built — small parts are assembled into larger elements.&lt;/p&gt;

&lt;p&gt;Storybook makes it easier to follow this mindset by isolating each component. You focus on one piece at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency
&lt;/h3&gt;

&lt;p&gt;Consistency is key in a design system — visually, behaviorally, and structurally. That can be achieved with design tokens.&lt;/p&gt;

&lt;p&gt;The starter doesn’t include an implementation out of the box, but its architecture makes it easy to add one.&lt;/p&gt;

&lt;p&gt;For example, at &lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;CKEditor&lt;/a&gt;, we use a hybrid approach — &lt;a href="https://sass-lang.com/" rel="noopener noreferrer"&gt;Syntactically Awesome Style Sheets&lt;/a&gt; (Sass) preprocessor and CSS variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sass"&gt;&lt;code&gt;&lt;span class="nv"&gt;$radius-medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-medium&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$radius-big&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-big&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$radius-bigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;app-radius-bigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we can define theme-specific values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.app-theme-custom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-big&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--app-radius-bigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&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;
  
  
  Extending with Addons
&lt;/h2&gt;

&lt;p&gt;One of Storybook’s biggest strengths is its ecosystem of addons. These allow tailoring a Storybook setup for more specific needs, such as testing the accessibility of your components.&lt;/p&gt;

&lt;p&gt;With the accessibility addon, accessibility checks can be integrated directly into your component stories.&lt;/p&gt;

&lt;p&gt;To enable it, start by installing the npm package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @storybook/addon-a11y &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register it inside your &lt;code&gt;.storybook/main.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&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;addons&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/addon-a11y&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once set up, every component story gets an &lt;strong&gt;Accessibility&lt;/strong&gt; panel that performs automated audits. It highlights issues like missing labels, contrast problems, and more without leaving your development environment.&lt;/p&gt;

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

&lt;p&gt;This addon serves as a perfect example of how you can extend Storybook beyond the basics.&lt;/p&gt;

&lt;p&gt;The full list of available addons can be found on the &lt;a href="https://storybook.js.org/addons" rel="noopener noreferrer"&gt;Storybook Integrations page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In this article, we explored the Storybook starter and its main concepts and features.&lt;/p&gt;

&lt;p&gt;It introduces you to the core principles of a good design system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusability
&lt;/li&gt;
&lt;li&gt;Composability
&lt;/li&gt;
&lt;li&gt;Consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not code that is meant to be removed as soon as generated, but a foundation for your design system. Study, expand, and use it to create a design system that will help you create stunning user interfaces!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Detect Bot Traffic using Next.js Middleware: A Quick Guide</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 28 Feb 2025 15:30:35 +0000</pubDate>
      <link>https://forem.com/ckeditor/how-to-detect-bot-traffic-using-nextjs-middleware-a-quick-guide-14o8</link>
      <guid>https://forem.com/ckeditor/how-to-detect-bot-traffic-using-nextjs-middleware-a-quick-guide-14o8</guid>
      <description>&lt;p&gt;Bots make up a significant portion of internet traffic. Some, like &lt;strong&gt;Googlebot&lt;/strong&gt;, are beneficial, while others — such as scrapers — can harm your site. In this article, we'll explore how to detect and block bots using &lt;strong&gt;Next.js Middleware&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bot Detection and Why Does it Matter?
&lt;/h2&gt;

&lt;p&gt;Not all web traffic is human. In fact, almost half of web traffic is automated. Bots can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrape content
&lt;/li&gt;
&lt;li&gt;Spam your website
&lt;/li&gt;
&lt;li&gt;Slow down your app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Should You Implement Bot Detection?
&lt;/h3&gt;

&lt;p&gt;From a security point of view, bot detection functionality is an essential part of the security toolset.&lt;/p&gt;

&lt;p&gt;With bot detection, you can improve security by blocking malicious traffic, reduce server load by filtering unnecessary requests, and protect analytics accuracy by preventing bot traffic.&lt;/p&gt;

&lt;p&gt;Since Next.js Middleware runs before a request is processed, it’s the perfect solution for implementing bot detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Middleware in Next.js?
&lt;/h2&gt;

&lt;p&gt;Middleware in Next.js allows you to run code and execute logic before completing the request.&lt;/p&gt;

&lt;p&gt;Additionally, Middleware can be run globally. However, you can modify this behavior for specific routes. It's important to mention that Middleware works really well with platforms like Vercel and Netlify.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Next.js Middleware Works
&lt;/h3&gt;

&lt;p&gt;Let's simplify the explanation by identifying key facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Middleware must be placed in the project root as a &lt;code&gt;middleware.ts&lt;/code&gt; or &lt;code&gt;middleware.js&lt;/code&gt; file depending if you are using TypeScript or not.
&lt;/li&gt;
&lt;li&gt;It automatically applies to &lt;strong&gt;all routes&lt;/strong&gt; unless configured otherwise.
&lt;/li&gt;
&lt;li&gt;It uses the &lt;code&gt;NextResponse&lt;/code&gt; to modify requests and perform specific actions like redirecting or rewrite&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Simple Middleware Implementation
&lt;/h2&gt;

&lt;p&gt;Before we add bot detection, let's create a basic Middleware implementation to log requests. First, we need to create &lt;code&gt;middleware.ts&lt;/code&gt; in the root directory.&lt;/p&gt;

&lt;p&gt;For a basic implementation, our Middleware will do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log every incoming request in the terminal.
&lt;/li&gt;
&lt;li&gt;Pass the request through without modification.
&lt;/li&gt;
&lt;/ul&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;NextResponse&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;next/server&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;EXCLUDED_PATHS&lt;/span&gt; &lt;span class="o"&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;/_next/&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;/static/&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;EXCLUDED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&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;.svg&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;.js&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;.css&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;.ico&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;.png&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;.jpg&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;.jpeg&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;.gif&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;.webp&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;.woff2&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;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&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="s2"&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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;Now, let's run our Next.js application to verify that our Middleware is functioning as expected.&lt;/p&gt;

&lt;p&gt;Start your Next.js app by running the &lt;code&gt;npm run dev&lt;/code&gt; in the terminal. Once you visit the &lt;code&gt;localhost:3000&lt;/code&gt; in the browser, check the terminal — you should see the following log there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request received: &amp;lt;http://localhost:3000/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extend Middleware with Bot Detection
&lt;/h2&gt;

&lt;p&gt;Now, let's improve our Middleware to detect bot-like User-Agents. We can do that by updating &lt;code&gt;middleware.ts&lt;/code&gt; with Bot Detection:&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;NextResponse&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;next/server&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;EXCLUDED_PATHS&lt;/span&gt; &lt;span class="o"&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;/_next/&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;/static/&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;EXCLUDED_EXTENSIONS&lt;/span&gt; &lt;span class="o"&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;.svg&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;.js&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;.css&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;.ico&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;.png&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;.jpg&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;.jpeg&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;.gif&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;.webp&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;.woff2&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;BOT_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/bot/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/crawler/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/spider/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/curl/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/wget/i&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;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EXCLUDED_PATHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;EXCLUDED_EXTENSIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ext&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BOT_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bot detected! (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) - Redirecting to /bot-detected.`&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;/bot-detected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&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;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Request received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&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="s2"&gt; | User-Agent: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userAgent&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's review what changed in this version of our Middleware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We extract the &lt;code&gt;User-Agent&lt;/code&gt; header from the request.
&lt;/li&gt;
&lt;li&gt;Check if it matches one of the bot keywords (&lt;code&gt;bot&lt;/code&gt;, &lt;code&gt;crawler&lt;/code&gt;, &lt;code&gt;spider&lt;/code&gt;, etc.).
&lt;/li&gt;
&lt;li&gt;Redirect detected bots to &lt;code&gt;/bot-detected&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Log each request’s &lt;code&gt;User-Agent&lt;/code&gt; for debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the Middleware
&lt;/h2&gt;

&lt;p&gt;Now, let's test our middleware using &lt;code&gt;curl&lt;/code&gt; and simulate a bot request. Run the following in your terminal without stopping the Next.js application locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -i -A "Googlebot" &amp;lt;http://localhost:3000&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you implemented the Middleware correctly, the following response will be visible in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 307 Temporary Redirect
location: /bot-detected
Date: Mon, 24 Feb 2025 17:00:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

/bot-detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the bot detection works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Review of Common Bot Detection Techniques
&lt;/h2&gt;

&lt;p&gt;Now that our basic bot detection Middleware works, let’s explore some common techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  User-Agent Header Checking
&lt;/h3&gt;

&lt;p&gt;This is the solution that we implemented as a simple demonstration of bot detection.&lt;/p&gt;

&lt;p&gt;It was very straightforward and was great for demo purposes. However, many bots use agents similar to real users, which makes this approach sub-optimal for real-life applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  IP Address Filtering
&lt;/h3&gt;

&lt;p&gt;This approach can be more effective in filtering and can be applied to real-life, production-ready applications. However, you can't rely on an array of addresses you manually gathered, so you must use anAPI that analyses IP.&lt;/p&gt;

&lt;p&gt;That approach offers better protection but may be ineffective for more advanced bots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-Party Bot Detection APIs
&lt;/h3&gt;

&lt;p&gt;Building on the previous approach, if we want enterprise-grade protection, a complete dedicated bot detection service like BotD or DataDome is the most solid means that guarantees high detection accuracy.&lt;/p&gt;

&lt;p&gt;While these solutions are not free, they are good enterprise-level solutions for production-ready applications.&lt;/p&gt;

&lt;p&gt;If you want to get started with one of the solutions that were mentioned above, Vercel provides starter projects that allow you to see both of them in action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/examples/tree/main/edge-middleware/bot-protection-botd" rel="noopener noreferrer"&gt;&lt;strong&gt;BotD by FingerprintJS&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/examples/tree/main/edge-middleware/bot-protection-datadome" rel="noopener noreferrer"&gt;&lt;strong&gt;DataDome&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Bots can have a huge impact on your website, from content scraping and spam to security risks and performance issues. Fortunately, &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/middleware" rel="noopener noreferrer"&gt;Next.js Middleware&lt;/a&gt; provides an efficient way to filter out unwanted traffic before it reaches your app.&lt;/p&gt;

&lt;p&gt;While basic bot detection is a great starting point, bots can bypass simple User-Agent checks. If you need stronger protection, consider third-party services like BotD or DataDome to detect even the most advanced bots.&lt;/p&gt;

&lt;p&gt;Now, it's time for you to find the optimal solution for your needs!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create a Rich Text Editor with Astro, Preact &amp; CKEditor 5</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Thu, 06 Feb 2025 12:46:47 +0000</pubDate>
      <link>https://forem.com/ckeditor/create-a-rich-text-editor-with-astro-preact-ckeditor-5-n1f</link>
      <guid>https://forem.com/ckeditor/create-a-rich-text-editor-with-astro-preact-ckeditor-5-n1f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt; is a modern framework designed for ultra-fast performance by shipping only the JavaScript that's needed. Pair that with &lt;strong&gt;Preact&lt;/strong&gt;, a lightweight alternative to React, and you’ve got the perfect recipe for building fast and efficient applications. To take things a step further, you can integrate &lt;strong&gt;CKEditor 5&lt;/strong&gt;, a powerful &lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;rich text editor&lt;/a&gt;, to add robust content editing capabilities to your app.&lt;/p&gt;

&lt;p&gt;In this article, we’ll walk you through the process of setting up an &lt;strong&gt;Astro + Preact&lt;/strong&gt; project from scratch and show you how to seamlessly integrate CKEditor 5. Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Choose Astro + Preact?&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Astro:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimal JavaScript by default&lt;/strong&gt;: Astro ships only what’s necessary to the client.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for content-focused websites&lt;/strong&gt;: Ideal for blogs, documentation sites, and other projects where performance matters but also suitable for more complex and dynamic applications.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework flexibility&lt;/strong&gt;: Supports popular frameworks like React, Preact, Vue, Svelte, and Solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Preact:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extremely lightweight&lt;/strong&gt;: Just ~3 kB gzipped, making it an excellent choice for performance-conscious projects.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React ecosystem compatibility&lt;/strong&gt;: Most React libraries work seamlessly with Preact.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast and efficient&lt;/strong&gt;: A great way to build modern, fast applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining Astro’s performance-first approach with Preact’s lightweight design, you get the best of both worlds: a highly-performant app and a pleasant developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why CKEditor?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ckeditor.com/" rel="noopener noreferrer"&gt;CKEditor&lt;/a&gt; is a powerful, flexible, rich text editor that enhances content creation and collaboration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast &amp;amp; Efficient&lt;/strong&gt;: Great writing experience with autoformatting, merge fields, and text transformation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Toolset for Collaboration&lt;/strong&gt;: Real-time co-editing, track changes, and comments.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible &amp;amp; Customizable&lt;/strong&gt;: Over 100 plugins are configurable toolbar are available with CKEditor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating CKEditor into Astro + Preact starter will allow you to build complex applications with excellent performance and feature-rich editing experience for various use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Setting Up Your Astro + Preact Project&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s how to go from zero to a fully functional &lt;strong&gt;Astro + Preact&lt;/strong&gt; project with CKEditor 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Create a New Astro Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, let’s create a fresh Astro project. Open your terminal and 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 create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the CLI’s prompts to set up your project. Astro’s installation process is straightforward and beginner-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Add Preact to Your Astro Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once your Astro project is set up, it’s time to integrate Preact. Astro provides a simple way to add Preact to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx astro add preact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command automatically updates your configuration files and installs the necessary dependencies. With just one command, Preact is ready to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Generate Your CKEditor Code&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;CKEditor 5 is a flexible and customizable rich text editor. To add it to your project, use the &lt;a href="https://ckeditor.com/ckeditor-5/builder/" rel="noopener noreferrer"&gt;CKEditor 5 Builder&lt;/a&gt;. Here’s how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Select the preset&lt;/strong&gt;: Start with the &lt;strong&gt;Classic Editor (starter)&lt;/strong&gt; for a simple yet powerful editor.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable features&lt;/strong&gt;: CKEditor offers over 100 plugins. Pick the features you need (e.g., bold, italic, link) and decide between free or premium options.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure the toolbar&lt;/strong&gt;: Customize the toolbar layout to match your requirements.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate code snippets&lt;/strong&gt;: Choose &lt;code&gt;React&lt;/code&gt; as the technology and &lt;code&gt;Cloud (CDN)&lt;/code&gt; as the integration method. If you’ve selected only free features, your code snippet will include a 24-hour evaluation key; alternatively, press the &lt;code&gt;Sign up for trial&lt;/code&gt; button to create a license key and automatically propagate it in your code snippets. If you have issues, refer to &lt;a href="https://ckeditor.com/docs/ckeditor5/latest/getting-started/licensing/license-key-and-activation.html#license-key-set-up" rel="noopener noreferrer"&gt;License key set up documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Integrate CKEditor 5 into Your Project&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now let’s integrate CKEditor 5 into your Astro + Preact project.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Install CKEditor’s React Package&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Install CKEditor’s React package using 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 &lt;span class="nb"&gt;install&lt;/span&gt; @ckeditor/ckeditor5-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2. Add CKEditor Styles&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;styles&lt;/code&gt; folder in your project and add a file named &lt;code&gt;App.css&lt;/code&gt;. Paste the styles provided by the CKEditor Builder into this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url('&amp;lt;https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&amp;amp;display=swap&amp;gt;')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;!important&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="nc"&gt;.main-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Lato'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.ck-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Lato'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;word-break&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;break-word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.editor-container_classic-editor&lt;/span&gt; &lt;span class="nc"&gt;.editor-container__editor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;795px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;795px&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;h3&gt;
  
  
  &lt;strong&gt;3. Create an Editor Component&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;editor.jsx&lt;/code&gt; in the &lt;code&gt;components&lt;/code&gt; folder. Here’s the component code, slightly modified for Preact:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/hooks&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;CKEditor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ckeditor/ckeditor5-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&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;LICENSE_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_LICENSE_KEY_HERE&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Editor&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;editorContainerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editorRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLayoutReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLayoutReady&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCKEditorCloud&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;44.1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setIsLayoutReady&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsLayoutReady&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editorConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLayoutReady&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AutoLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Autosave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Italic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Paragraph&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CKEditor&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="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;editorConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;toolbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="na"&gt;shouldNotGroupWhenFull&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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AutoLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Autosave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Essentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Italic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h2&amp;gt;Evaluation license key 🔑&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tAn evaluation key is being used in this editor. &amp;lt;a href="&amp;lt;https://portal.ckeditor.com/checkout?plan=free&amp;gt;"&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;t&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tCreate an account to use your own license keys.&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;t&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;h2&amp;gt;Congratulations on setting up CKEditor 5! 🎉&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s1"&gt;tYou&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;ve&lt;/span&gt; &lt;span class="nx"&gt;successfully&lt;/span&gt; &lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;CKEditor&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;powerful&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;twill&lt;/span&gt; &lt;span class="nx"&gt;enhance&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;enabling&lt;/span&gt; &lt;span class="nx"&gt;rich&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;editing&lt;/span&gt; &lt;span class="nx"&gt;capabilities&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;tare&lt;/span&gt; &lt;span class="nx"&gt;customizable&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;easy&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;h3&amp;gt;What&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;'s next&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Integrate&lt;/span&gt; &lt;span class="nx"&gt;into&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/strong&amp;gt;: time to bring the editing into&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tyour application. Take the code you created and add to your application.&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Explore&lt;/span&gt; &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Experiment with different plugins and\\\\n\\\\t\\\\ttoolbar options to discover what works best for your needs.\\\\n\\\\t&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="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Customize&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;:&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; Tailor the editor\\\\'s\\\\n\\\\t\\\\tconfiguration to match your application\\\\'s style and requirements. Or\\\\n\\\\t\\\\teven write your plugin!\\\\n\\\\t&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="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ol&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tKeep experimenting, and don&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;'t hesitate to push the boundaries of what you&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tcan achieve with CKEditor 5. Your feedback is invaluable to us as we strive&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;tto improve and evolve. Happy editing!&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;\\\\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Helpful&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="sr"&gt;t&amp;lt;li&amp;gt;📝 &amp;lt;a href="&amp;lt;https:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;portal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ckeditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;free&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Trial sign up&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;t&amp;lt;li&amp;gt;📕 &amp;lt;a href=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//ckeditor.com/docs/ckeditor5/latest/installation/index.html&amp;gt;"&amp;gt;Documentation&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;⭐️ &amp;lt;a href="&amp;lt;https://github.com/ckeditor/ckeditor5&amp;gt;"&amp;gt;GitHub&amp;lt;/a&amp;gt; (star us if you can!),&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;🏠 &amp;lt;a href="&amp;lt;https://ckeditor.com&amp;gt;"&amp;gt;CKEditor Homepage&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n\\\\t&amp;lt;li&amp;gt;🧑‍💻 &amp;lt;a href="&amp;lt;https://ckeditor.com/ckeditor-5/demo/&amp;gt;"&amp;gt;CKEditor 5 Demos&amp;lt;/a&amp;gt;,&amp;lt;/li&amp;gt;\\\\n&amp;lt;/ul&amp;gt;\\\\n&amp;lt;h3&amp;gt;Need help?&amp;lt;/h3&amp;gt;\\\\n&amp;lt;p&amp;gt;\\\\n\\\\tSee this text, but the editor is not starting up? Check the browser\\\\'s\\\\n\\\\tconsole for clues and guidance. It may be related to an incorrect license\\\\n\\\\tkey if you use premium features or another feature-related requirement. If\\\\n\\\\tyou cannot make it work, file a GitHub issue, and we will help as soon as\\\\n\\\\tpossible!\\\\n&amp;lt;/p&amp;gt;\\\\n',&lt;/span&gt;
                &lt;span class="na"&gt;licenseKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LICENSE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type or paste your content here!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLayoutReady&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;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"main-container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"editor-container editor-container_classic-editor"&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorContainerRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"editor-container__editor"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorRef&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;ClassicEditor&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;editorConfig&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CKEditor&lt;/span&gt; &lt;span class="na"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ClassicEditor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;editorConfig&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&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;h3&gt;
  
  
  &lt;strong&gt;Step 5: Adjust Astro for Preact Compatibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To make React libraries (like &lt;strong&gt;CKEditor 5 integration package&lt;/strong&gt;) work in a Preact environment, update your &lt;code&gt;astro.config.mjs&lt;/code&gt; file as follows:&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;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&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;preact&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@astrojs/preact&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="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;preact&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;vite&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;react&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Add the Editor to Your Page&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, import the &lt;code&gt;Editor&lt;/code&gt; component in your Astro page and render it using the &lt;code&gt;client:only="preact"&lt;/code&gt; directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Layout from '../layouts/Layout.astro';
import Editor from '../components/editor';
---

&amp;lt;Layout&amp;gt;
  &amp;lt;Editor client:only="preact" /&amp;gt;
&amp;lt;/Layout&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Once completed, you will be able to see the editor on the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u80sepvtdgn36zgztso.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u80sepvtdgn36zgztso.png" alt="Rich text editor in an Astro + Preact environment designed for fast and efficient content editing" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Wrapping Up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Congratulations! You’ve successfully set up an &lt;strong&gt;Astro + Preact&lt;/strong&gt; project and integrated &lt;strong&gt;CKEditor 5&lt;/strong&gt; into it. Here’s a quick recap of the key benefits of the created setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimal JavaScript Thanks to Preact&lt;/strong&gt;: Preact keeps your application lightweight by reducing the shipped JavaScript, resulting in faster load times and improved performance, especially on content-focused websites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful React Compatibility&lt;/strong&gt;: Since Preact is compatible with most React libraries and components, you can easily integrate React libraries while benefiting from Preact’s smaller footprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Great Developer Experience&lt;/strong&gt;: Astro’s flexibility and Preact’s simplicity allow for a scalable setup with reusable components, making development more efficient and easier to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful Editing Tool&lt;/strong&gt;: CKEditor 5 enhances content creation with rich text formatting, real-time collaboration, media embedding, and an extensive plugin ecosystem, giving users a professional-grade editing experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup offers incredible performance while maintaining a great developer experience.&lt;/p&gt;

&lt;p&gt;If you’d like to extend your CKEditor setup, check out the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/ckeditor-5/demo/" rel="noopener noreferrer"&gt;CKEditor demo pages&lt;/a&gt; to get inspiration
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/ckeditor-5/features/" rel="noopener noreferrer"&gt;CKEditor 5 features&lt;/a&gt; to get a high-level understanding of what is available with CKEditor
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ckeditor.com/docs/" rel="noopener noreferrer"&gt;CKEditor Documentation&lt;/a&gt; to go deeper and see how you can customize not only the editor setup but also the plugin's behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let us know in the comments what plugins or configurations you’ve used! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>astro</category>
    </item>
    <item>
      <title>Building a Measuring Tool with the Resize Observer API</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 20 Dec 2024 18:03:45 +0000</pubDate>
      <link>https://forem.com/ckeditor/building-a-measuring-tool-with-the-resize-observer-api-4bd8</link>
      <guid>https://forem.com/ckeditor/building-a-measuring-tool-with-the-resize-observer-api-4bd8</guid>
      <description>&lt;p&gt;Web APIs - a very interesting and rarely well explored territory. And yet, there's a large number of unique and very useful APIs that can help you to create tools for your projects.&lt;/p&gt;

&lt;p&gt;For example, tracking size changes is key to creating dynamic, responsive experiences. This is where the &lt;strong&gt;Resize Observer API&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In this article, we’ll build a measuring tool that displays the width and height of a resizable box in real-time. This is a project that demonstrates the power of the Resize Observer API and Browser APIs in general in a practical and interactive way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Resize Observer API?
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Resize Observer API&lt;/strong&gt; is a browser feature that allows you to detect changes to the size of an element. Resize Observer works on individual elements. It works out-of-the-box, and can be a great addition to your toolset for building responsive design and dynamic UIs.&lt;/p&gt;

&lt;p&gt;Here’s what makes it great:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s lightweight and easy to use&lt;/li&gt;
&lt;li&gt;You can track changes in size for specific elements, not just the entire viewport&lt;/li&gt;
&lt;li&gt;It’s perfect for building responsive components or resizable widgets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We’re Building
&lt;/h2&gt;

&lt;p&gt;We’ll create a resizable box with dimensions displayed inside of it. As the user resizes the box, the displayed dimensions will update in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;First, let’s set up the basic structure for our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resize-tool/
├── index.html
├── styles.css
├── script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The Markup
&lt;/h2&gt;

&lt;p&gt;Here’s a simple layout for our app. The resizable box includes a text span to display its dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Measuring Tool with Resize Observer API&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"styles.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"resizableBox"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"resizable"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dimensions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Width: 0px, Height: 0px&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"script.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Styling the App
&lt;/h2&gt;

&lt;p&gt;We’ll add some styles to make the resizable box more visually appealing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f0f0f0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.resizable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;#007bff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;both&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nc"&gt;.resizable&lt;/span&gt; &lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&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;
  
  
  Step 4: Adding Real-Time Resize Tracking
&lt;/h2&gt;

&lt;p&gt;Now let’s bring the project to life using the Resize Observer API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resizableBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resizableBox&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;dimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dimensions&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;resizeObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResizeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inlineSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;borderBoxSize&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Width: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;px, Height: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;px`&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;resizeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resizableBox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Testing the Tool
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;code&gt;index.html&lt;/code&gt; file in your browser.&lt;/li&gt;
&lt;li&gt;Drag the corners of the box to resize it.&lt;/li&gt;
&lt;li&gt;Watch as the dimensions update instantly!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Resize Observer API is initialized to monitor size changes for the &lt;code&gt;resizableBox&lt;/code&gt; element. It triggers a callback whenever the size of the observed element changes.&lt;/li&gt;
&lt;li&gt;The Resize Observer Entry provides updated dimensions through the &lt;code&gt;borderBoxSize&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;The updated width and height values are extracted and displayed dynamically by modifying the text content of the &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; element inside the resizable box.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In this tutorial, we built a fun and interactive &lt;strong&gt;measuring tool&lt;/strong&gt; using the &lt;strong&gt;Resize Observer API&lt;/strong&gt;. This project demonstrates how browser APIs can simplify complex tasks.&lt;br&gt;
If you try this out or extend it further, feel free to share your creations in the comments!&lt;br&gt;
Also, check out the &lt;a href="https://ckeditor.com/blog/?utm_campaign=devrel_devto_ckeditor&amp;amp;utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_term=browser-api-article" rel="noopener noreferrer"&gt;CKEditor blog&lt;/a&gt; for articles around rich-text editors, and start your journey with CKEditor 5 by signing up for a free trial today!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>📝 🚀 Creating our first documentation from scratch using Astro and Refact AI coding assistant</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Mon, 18 Sep 2023 17:36:16 +0000</pubDate>
      <link>https://forem.com/refact/creating-our-first-documentation-from-scratch-using-astro-and-refact-ai-coding-assistant-36pg</link>
      <guid>https://forem.com/refact/creating-our-first-documentation-from-scratch-using-astro-and-refact-ai-coding-assistant-36pg</guid>
      <description>&lt;p&gt;At Refact, we put performance and developer experience as our top priority. That's why we were looking for a lightweight and highly customizable template for our documentation.&lt;/p&gt;

&lt;p&gt;Previously, we used Astro for our &lt;a href="http://refact.ai/" rel="noopener noreferrer"&gt;refact.ai&lt;/a&gt; website and wanted to stay within the Astro ecosystem for the documentation.&lt;/p&gt;

&lt;p&gt;Let's see how easy it will be to build the docs using the Starlight template from Astro with the help of the Refact AI coding assistant.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Refact: Open-source AI coding assistant&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Just a quick background about us: &lt;a href="https://github.com/smallcloudai/refact" rel="noopener noreferrer"&gt;Refact&lt;/a&gt; is an open-source AI coding assistant that can significantly boost developer productivity with tools like code completion, code refactoring, and chat.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Building the Starlight docs
&lt;/h2&gt;

&lt;p&gt;Starlight is a framework-agnostic Astro template for building documentation websites. With a straightforward configuration process and the support of Markdown and MDX, you get a solid foundation for your documentation.&lt;/p&gt;

&lt;p&gt;In this article, we will follow the manual setup process to get a better understanding of all of the parts that are required for Starlight to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an Astro project
&lt;/h3&gt;

&lt;p&gt;First, we must create an Astro project as a foundation since Starlight is built on top of Astro.&lt;/p&gt;

&lt;p&gt;To do that, we need to run the following command in your terminal:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npm create astro@latest


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

&lt;/div&gt;

&lt;p&gt;You can pick the option with the sample files.&lt;/p&gt;

&lt;p&gt;Once the setup process is completed, you can run &lt;code&gt;npm run dev&lt;/code&gt; in your terminal and see a starter template in your browser.&lt;/p&gt;

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

&lt;p&gt;With Astro, you get an extremely straightforward structure of the project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/
├── public/
│   └── favicon.svg
├── src/
│   ├── components/
│   │   └── Card.astro
│   ├── layouts/
│   │   └── Layout.astro
│   └── pages/
│       └── index.astro
└── astro.config.mjs


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Adding Starlight
&lt;/h3&gt;

&lt;p&gt;Now that we have the foundation, we are ready to add Starlight to turn our website into a fully functional documentation.&lt;/p&gt;

&lt;p&gt;To do that, run the following command in your terminal:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

npx astro add starlight


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

&lt;/div&gt;

&lt;p&gt;After completing the multistep installation process in your terminal, we can see that our Astro configuration file was updated.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="p"&gt;import { defineConfig } from 'astro/config';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ import starlight from "@astrojs/starlight";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export default defineConfig({
&lt;/span&gt;&lt;span class="gi"&gt;+   integrations: [starlight()]
&lt;/span&gt;});
&lt;span class="err"&gt;

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

&lt;/div&gt;

&lt;p&gt;Perfect! Now, we have an Astro project with a Starlight integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Starlight
&lt;/h3&gt;

&lt;p&gt;Even though we installed the Starlight integration, we need to complete three more steps to see it working:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need to configure the plugin&lt;/li&gt;
&lt;li&gt;The next step will be a content collection configuration&lt;/li&gt;
&lt;li&gt;And last but not least, we need to add markdown files to see the complete documentation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process is very straightforward, but Refact will help us to boost the configuration process with the autocomplete functionality.&lt;/p&gt;

&lt;p&gt;First, let's configure the plugin. Starlight allows you to set many things in the configuration, but only one property is required for the plugin to function, which is a &lt;code&gt;title&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;astro.config.mjs&lt;/code&gt; file, you should add a &lt;code&gt;title&lt;/code&gt; inside the Starlight integration. Refact immediately starts to help you with that!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="p"&gt;import { defineConfig } from 'astro/config';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;import starlight from "@astrojs/starlight";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export default defineConfig({
&lt;/span&gt;    integrations: [starlight(
&lt;span class="gi"&gt;+       {
+           title: "My awesome documentation"
+       }
&lt;/span&gt;    )]
});
&lt;span class="err"&gt;

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

&lt;/div&gt;

&lt;p&gt;The next step is to configure the content collection. The content collection allows us to handle the dynamic generation of pages from markdown files.&lt;/p&gt;

&lt;p&gt;To configure the content collection, we need to complete the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a folder structure for the documentation&lt;/li&gt;
&lt;li&gt;Create the config file and define the collection with the docs schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For documentation to function, we need to create a &lt;code&gt;content&lt;/code&gt; folder inside the &lt;code&gt;src&lt;/code&gt; directory, which will be the root directory for the documentation-specific files.&lt;/p&gt;

&lt;p&gt;Inside that folder, we need to create the &lt;code&gt;config.ts&lt;/code&gt; file to define the collection.&lt;/p&gt;

&lt;p&gt;First, let's import two functions that we will use - &lt;code&gt;defineCollection&lt;/code&gt; and &lt;code&gt;doscSchema&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="gi"&gt;+ import { defineCollection } from 'astro:content'
+ import { docsSchema } from '@astrojs/starlight/schema'
&lt;/span&gt;&lt;span class="err"&gt;

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

&lt;/div&gt;

&lt;p&gt;Now, we are ready to define the collection. Refact will pick it up and help you to complete that logic, which speeds up the process for you and helps you with guidance on what needs to be completed&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExaHBwemJlNTFlYWhjbTZucDg2bHZ5dXp5cHlqdzF6cW50ZHBtMW1zYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/1sIGSvAjJMxgIGwT3k/giphy-downsized-large.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExaHBwemJlNTFlYWhjbTZucDg2bHZ5dXp5cHlqdzF6cW50ZHBtMW1zYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/1sIGSvAjJMxgIGwT3k/giphy-downsized-large.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the configuration process is completed, let's add content to see the documentation in action.&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;content&lt;/code&gt; folder, we need to add the &lt;code&gt;docs&lt;/code&gt; directory. There, we should add an &lt;code&gt;index.md&lt;/code&gt; file with the following structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

---
title: "My Documentation"
description: "This is a getting started guide of my documentation."
---

This is the first segment of my documentation!


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

&lt;/div&gt;

&lt;p&gt;If we start the project locally, we will see our beautiful documentation functioning as expected!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Customizing your documentation
&lt;/h2&gt;

&lt;p&gt;The results that we achieved are great, but it looks very generic. That's why Starlight allows you to customize the documentation and make it more personalized.&lt;/p&gt;

&lt;p&gt;Let's modify the frontmatter in &lt;code&gt;index.md&lt;/code&gt; file to turn it into a landing page of your documentation. Plenty of options are available for you to make a great-looking landing page. At Refact, we used the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;template - allows you to turn a regular documentation page into a landing page&lt;/li&gt;
&lt;li&gt;hero - an option with &lt;code&gt;tagline&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, and &lt;code&gt;actions&lt;/code&gt; properties to generate a hero banner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to that, Starlight comes with components that allow you to add more code to the landing page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't forget to change your index.md to index.mdx for components to work!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a final result, here's how the code for the landing page might look:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

---
title: Welcome to Refact Documentation!
description: Refact is a powerful AI coding assistant that combines completion, refactoring, chat, and more.
template: splash
hero:
  tagline: Refact is a powerful AI coding assistant that combines completion, refactoring, chat, and more.
  image:
    file: ../../assets/refact.png
  actions:
    - text: Get started
      link: /getting-started/what-is-refact/
      icon: right-arrow
      variant: primary
    - text: VS Code Extension
      link: &amp;lt;https://marketplace.visualstudio.com/items?itemName=smallcloud.codify&amp;gt;
    - text: Jetbrains Plugin
      link: &amp;lt;https://plugins.jetbrains.com/plugin/20647-codify&amp;gt;
---

import { Card, CardGrid } from '@astrojs/starlight/components';

## Features

&amp;lt;CardGrid stagger&amp;gt;
    &amp;lt;Card title="Code Completion" icon="pencil"&amp;gt;
        As you write code, Refact suggests potential code completions based on the context of your code,
    looking up and down. It can suggest whole functions, classes, commonly used programming patterns,
    libraries, and APIs usage.
    &amp;lt;/Card&amp;gt;
    &amp;lt;Card title="Improve code" icon="magnifier"&amp;gt;
        Refact can identify code that could be refactored to be more efficient or easier to understand.
    It can also detect bugs in your code and generate patches to fix them.
    &amp;lt;/Card&amp;gt;
    &amp;lt;Card title="AI Chat" icon="rocket"&amp;gt;
        Use plain language prompts in Refact chat to ask questions or get help with
    writing code without leaving your IDE.
    &amp;lt;/Card&amp;gt;
    &amp;lt;Card title="Transform and analyze code" icon="setting"&amp;gt;
        Refact can analyze the complexity of your code and explain unclear lines of code.
    It can also transform your code into a different language.
    &amp;lt;/Card&amp;gt;
&amp;lt;/CardGrid&amp;gt;


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

&lt;/div&gt;

&lt;p&gt;The following code generated a great-looking landing page with lots of helpful content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bo8cd5fk3bi83r24sge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bo8cd5fk3bi83r24sge.png" alt="Starlight docs with custom landing page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing the color theme
&lt;/h3&gt;

&lt;p&gt;As you saw in the screenshot, the color theme we use for the Refact documentation differs from the one available out of the box. Starlight makes it very straightforward to change the color scheme.&lt;/p&gt;

&lt;p&gt;Starlight allows you to add custom styles to match the documentation colors with colors that represent your brand.&lt;/p&gt;

&lt;p&gt;First, we must create a &lt;code&gt;custom.css&lt;/code&gt; file in the &lt;code&gt;src/styles&lt;/code&gt; folder. After that, we must link that file to the &lt;code&gt;astro.config.mjs&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="p"&gt;import { defineConfig } from 'astro/config';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;import starlight from "@astrojs/starlight";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;export default defineConfig({
&lt;/span&gt;    integrations: [Starlight (
        {
            title: "My awesome documentation",
&lt;span class="gi"&gt;+           customCss: [
+               './src/styles/custom.css'
+           ]
&lt;/span&gt;        }
    )
    ]
});
&lt;span class="err"&gt;

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

&lt;/div&gt;

&lt;p&gt;You can modify the color theme in your newly created CSS file. Here is an example of styles that we use at Refact to match our brand colors:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="c"&gt;/* Dark mode colors. */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent-low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#42100e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c70016&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent-high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f8b6ad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#eeeeee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c2c2c2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b8b8b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#585858&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#383838&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#272727&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#181818&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Light mode colors. */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'light'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent-low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fcc9c2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#cb0017&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-accent-high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#610b0c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#181818&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#272727&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#383838&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#585858&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b8b8b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#c2c2c2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#eeeeee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-gray-7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f6f6f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="py"&gt;--sl-color-black&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&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;If you refresh the page, you can see that the color theme was updated!&lt;/p&gt;

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

&lt;p&gt;And there we have it!&lt;br&gt;
We've explored the Starlight project - a fantastic solution for the documentation, and we used Refact - an AI coding assistant that immediately picked up our project's context and helped us get used to an unfamiliar code base a lot faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;With a solid foundation and an incredible AI ally, you can explore the Starlight project to see how to personalize and make your documentation stunning.&lt;br&gt;
In addition, I encourage you to try &lt;a href="https://refact.ai/" rel="noopener noreferrer"&gt;Refact&lt;/a&gt; and see how it will improve your productivity and help you become more efficient as an engineer.&lt;/p&gt;

&lt;p&gt;Join our &lt;a href="https://www.smallcloud.ai/discord" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; server if you need assistance from the Refact team and community to get started faster!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>astro</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Nhost CLI: From Zero to Production</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Fri, 18 Feb 2022 13:20:58 +0000</pubDate>
      <link>https://forem.com/nhost/nhost-cli-from-zero-to-production-220h</link>
      <guid>https://forem.com/nhost/nhost-cli-from-zero-to-production-220h</guid>
      <description>&lt;p&gt;In the previous tutorials, that are covered in the &lt;a href="https://docs.nhost.io/get-started" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, we tested various parts of Nhost, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;GraphQL API&lt;/li&gt;
&lt;li&gt;Permission&lt;/li&gt;
&lt;li&gt;JavaScript SDK&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All changes we did to our database and API happened directly in the production of our Nhost app.&lt;/p&gt;

&lt;p&gt;It’s not ideal for making changes in production because you might break things, which will affect all users of your app.&lt;/p&gt;

&lt;p&gt;Instead, it’s recommended to make changes and test your app locally before deploying those changes to production.&lt;/p&gt;

&lt;p&gt;To do changes locally, we need to have a complete Nhost app running locally, which the Nhost CLI does.&lt;/p&gt;

&lt;p&gt;The Nhost CLI matches your production application in a local environment, this way you can make changes and test your code before deploying your changes to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended workflow with Nhost
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Develop locally using the Nhost CLI.&lt;/li&gt;
&lt;li&gt;Push changes to GitHub.&lt;/li&gt;
&lt;li&gt;Nhost automatically applies changes to production.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What you’ll learn in this guide:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use the Nhost CLI to create a local environment&lt;/li&gt;
&lt;li&gt;Connect a GitHub repository with a Nhost app&lt;/li&gt;
&lt;li&gt;Deploy local changes to production&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup the recommended workflow with Nhost
&lt;/h2&gt;

&lt;p&gt;What follows is a detailed tutorial on how you setup Nhost for this workflow&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Nhost App
&lt;/h3&gt;

&lt;p&gt;Create a &lt;strong&gt;new Nhost app&lt;/strong&gt; for this tutorial.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s important that you create a &lt;strong&gt;new&lt;/strong&gt; Nhost app for this guide instead of reusing an old Nhost app because we want to start with a clean Nhost app.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3&gt;
  
  
  Create new GitHub Repository
&lt;/h3&gt;

&lt;p&gt;Create a new GitHub repository for your new Nhost app. The repo can be both private or public.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Connect GitHub Repository to Nhost App
&lt;/h2&gt;

&lt;p&gt;In the Nhost Console, go to the dashboard of your Nhost app and click &lt;strong&gt;Connect to GitHub&lt;/strong&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Install the Nhost CLI
&lt;/h2&gt;

&lt;p&gt;Install the Nhost CLI using the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;githubusercontent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;nhost&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;bash&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Initialize a new Nhost App locally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="n"&gt;nhost&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="nv"&gt;"nhost-example-app"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;nhost&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;And initialize the GitHub repository in the same folder:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# nhost-example-app"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; README.md
git init
git add README.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"first commit"&lt;/span&gt;
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git remote add origin https://github.com/[github-username]/nhost-example-app.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main


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

&lt;/div&gt;

&lt;p&gt;Now go back to the &lt;strong&gt;Nhost Console&lt;/strong&gt; and click &lt;strong&gt;Deployments&lt;/strong&gt;. You just made a new deployment to your Nhost app!&lt;/p&gt;

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

&lt;p&gt;If you click on the deployment you can see that nothing was really deployed. That’s because we just made a change to the README file.&lt;/p&gt;

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

&lt;p&gt;Let’s do some backend changes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Local changes
&lt;/h2&gt;

&lt;p&gt;Start Nhost locally:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="n"&gt;nhost&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;💡 Make sure you have &lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed on your computer. It’s required for Nhost to work.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;nhost dev&lt;/code&gt; command will automatically start a complete Nhost environment locally on your computer using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;li&gt;Hasura&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Serverless Functions&lt;/li&gt;
&lt;li&gt;Mailhog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You use this local environment to do changes and testing before you deploy your changes to production.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;nhost dev&lt;/code&gt; also starts the Hasura Console.&lt;/p&gt;

&lt;p&gt;💡 It’s important that you use the Hasura Console that is started automatically when you do changes. This way, changes are automatically tracked for you.&lt;/p&gt;

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

&lt;p&gt;In the Hasura Console, create a new table &lt;code&gt;customers&lt;/code&gt; with two columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;name&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq665cqg7tzunugmmmbn6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq665cqg7tzunugmmmbn6.gif" alt="Hasura Create Customers Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we created the &lt;code&gt;customers&lt;/code&gt; table there was also a migration created automatically. The migration was created at under &lt;code&gt;nhost/migrations/default&lt;/code&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;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; nhost/migrations/default
total 0
drwxr-xr-x  3 eli  staff   96 Feb  7 16:19 &lt;span class="nb"&gt;.&lt;/span&gt;
drwxr-xr-x  3 eli  staff   96 Feb  7 16:19 ..
drwxr-xr-x  4 eli  staff  128 Feb  7 16:19 1644247179684_create_table_public_customers


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

&lt;/div&gt;

&lt;p&gt;This database migration has only been applied locally, meaning, you created the &lt;code&gt;customers&lt;/code&gt; table locally but it does not (yet) exists in production.&lt;/p&gt;

&lt;p&gt;To apply the local change to production we need to commit the changes and push it to GitHub. Nhost will then automatically pick up the change in the repository and apply the changes.&lt;/p&gt;

&lt;p&gt;💡 You can commit and push files in another terminal while still having &lt;code&gt;nhost dev&lt;/code&gt; running.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="nv"&gt;"Initialized Nhost and added a customers table"&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Head over to the &lt;strong&gt;Deployments&lt;/strong&gt; tab in the &lt;strong&gt;Nhost console&lt;/strong&gt; to see the deployment.&lt;/p&gt;

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

&lt;p&gt;Once the deployment finishes the &lt;code&gt;customers&lt;/code&gt; table is created in production.&lt;/p&gt;

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

&lt;p&gt;We’ve now completed the recommended workflow with Nhost:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Develop locally using the Nhost CLI.&lt;/li&gt;
&lt;li&gt;Push changes to GitHub.&lt;/li&gt;
&lt;li&gt;Nhost deploys changes to production.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Apply metadata and Serverless Functions
&lt;/h2&gt;

&lt;p&gt;In the previous section, we only created a new table; &lt;code&gt;customers&lt;/code&gt;. Using the CLI you can also do changes to other parts of your backend.&lt;/p&gt;

&lt;p&gt;There are three things the CLI and the GitHub integration track and applies to production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database migrations&lt;/li&gt;
&lt;li&gt;Hasura Metadata&lt;/li&gt;
&lt;li&gt;Serverless Functions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this section, let’s do one change to the Hasura metadata and create one serverless function&lt;/p&gt;

&lt;h3&gt;
  
  
  Hasura Metadata
&lt;/h3&gt;

&lt;p&gt;We’ll add permissions to the &lt;code&gt;users&lt;/code&gt; table, making sure users can only see their own data. For this, go to the &lt;code&gt;auth&lt;/code&gt; schema and click on the &lt;code&gt;users&lt;/code&gt; table. then click on &lt;strong&gt;Permissions&lt;/strong&gt; and enter a new role &lt;strong&gt;user&lt;/strong&gt; and create a new &lt;strong&gt;select&lt;/strong&gt; permission for that role*&lt;em&gt;.&lt;/em&gt;*&lt;/p&gt;

&lt;p&gt;Create the permission &lt;strong&gt;with custom check&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_eq"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"X-Hasura-User-Id"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;Select the following columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;created_at&lt;/li&gt;
&lt;li&gt;display_name&lt;/li&gt;
&lt;li&gt;avatar_url&lt;/li&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then click &lt;strong&gt;Save permissions&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Now, let’s do a &lt;code&gt;git status&lt;/code&gt; again to confirm the permission changes we did were tracked locally in your git repository.&lt;/p&gt;

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

&lt;p&gt;We can now commit this change:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"added permission for uses"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now let’s create a serverless function before we push all changes to GitHub so Nhost can deploy our changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Function
&lt;/h3&gt;

&lt;p&gt;A serverless function is a piece of code written in JavaScript or TypeScript that takes an HTTP request and returns a response.&lt;/p&gt;

&lt;p&gt;Here’s an example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

import &lt;span class="o"&gt;{&lt;/span&gt; Request, Response &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'express'&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="o"&gt;(&lt;/span&gt;req: Request, res: Response&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  res.status&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;.send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;Hello &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.query.name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Serverless functions are placed in the &lt;code&gt;functions/&lt;/code&gt; folder of your repository. Every file will become its own endpoint.&lt;/p&gt;

&lt;p&gt;Before we create our serverless function we’ll install &lt;code&gt;express&lt;/code&gt;, which is a requirement for serverless functions to work.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install &lt;/span&gt;express
&lt;span class="c"&gt;# or with yarn&lt;/span&gt;
yarn add express


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

&lt;/div&gt;

&lt;p&gt;We’ll use TypeScript so we’ll install two type definitions too:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; @types/node @types/express
&lt;span class="c"&gt;# or with yarn&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @types/node @types/express


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

&lt;/div&gt;

&lt;p&gt;Then we’ll create a file &lt;code&gt;functions/time.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the file &lt;code&gt;time.ts&lt;/code&gt; we’ll add the following code to create our serverless function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

import &lt;span class="o"&gt;{&lt;/span&gt; Request, Response &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'express'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default &lt;span class="o"&gt;(&lt;/span&gt;req: Request, res: Response&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;res
    .status&lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;
    .send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;Hello &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.query.name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; It&lt;span class="s1"&gt;'s now: ${new Date().toUTCString()}`);
};


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

&lt;/div&gt;

&lt;p&gt;We can now test the function locally. Locally, the backend URL is &lt;code&gt;http://localhost:1337&lt;/code&gt;. Functions are under &lt;code&gt;/v1/functions&lt;/code&gt;. And every function’s path and filename becomes an API endpoint.&lt;/p&gt;

&lt;p&gt;This means our function &lt;code&gt;functions/time.ts&lt;/code&gt; is at &lt;code&gt;http://localhost:1337/v1/functions/time&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s use curl to test our new function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

curl http://localhost:1337/v1/functions/time
Hello undefined! It&lt;span class="s1"&gt;'s now: Sun, 06 Feb 2022 17:44:45 GMT


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

&lt;/div&gt;

&lt;p&gt;And with a query parameter with our name:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

curl http://localhost:1337/v1/functions/time&lt;span class="se"&gt;\?&lt;/span&gt;name&lt;span class="se"&gt;\=&lt;/span&gt;Johan
Hello Johan! It&lt;span class="s1"&gt;'s now: Sun, 06 Feb 2022 17:44:48 GMT


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

&lt;/div&gt;

&lt;p&gt;Again, let’s use &lt;code&gt;git status&lt;/code&gt; to see the changes we did to create our serverless function.&lt;/p&gt;

&lt;p&gt;Now let’s commit the changes and push them to GitHub.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"added serverless function"&lt;/span&gt;
git push


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

&lt;/div&gt;

&lt;p&gt;In the Nhost Console, click on the new deployment to see details&lt;/p&gt;

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

&lt;p&gt;After Nhost has finished deploying your changes we can test them in production. First let’s confirm that the user permissions are applied.&lt;/p&gt;

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

&lt;p&gt;Then, let’s confirm that the serverless function was deployed. Again, we’ll use curl:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

curl https://your-backend-url.nhost.run/v1/functions/time&lt;span class="se"&gt;\?&lt;/span&gt;name&lt;span class="se"&gt;\=&lt;/span&gt;Johan


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

&lt;/div&gt;

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

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

&lt;p&gt;In this tutorial, we have installed the Nhost CLI and created a local Nhost environment to do local development and testing.&lt;/p&gt;

&lt;p&gt;In the local environment, we’ve made changes to our database, to Hasura’s metadata, and created a serverless function.&lt;/p&gt;

&lt;p&gt;We’ve connected a GitHub repository and pushed our changes to GitHub.&lt;/p&gt;

&lt;p&gt;We’ve seen Nhost automatically deploying our changes and we’ve verified that the changes were applied.&lt;/p&gt;

&lt;p&gt;In summary, we’ve set up a productive environment using the recommended Nhost workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Develop locally using the Nhost CLI.&lt;/li&gt;
&lt;li&gt;Push changes to GitHub.&lt;/li&gt;
&lt;li&gt;Nhost deploys changes to production.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In addition to all that, the Nhost team is always happy to support you with any questions you might have on &lt;a href="https://discord.gg/dHzgYs7c97" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; and &lt;a href="https://github.com/nhost/nhost" rel="noopener noreferrer"&gt;Github&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>serverless</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hacktober 2021 results</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Mon, 01 Nov 2021 19:19:40 +0000</pubDate>
      <link>https://forem.com/medusajs/hacktober-2021-results-oi9</link>
      <guid>https://forem.com/medusajs/hacktober-2021-results-oi9</guid>
      <description>&lt;p&gt;With Hacktober coming to an end, we would like to take a moment to appreciate the overwhelming support and contributions we've seen the past month. It was our debut in this year's fest, and we feel like we've kicked the door in with our powerful community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hacktober 2021 community achievements
&lt;/h2&gt;

&lt;p&gt;We are incredibly humbled by the participation of so many new and existing community members over this past month. As a young company, it is incredible to see how old, more experienced community members helped guide new ones getting started while others teamed up to find solutions and helped extend the plugin suite of Medusa.&lt;/p&gt;

&lt;p&gt;In the end, the effort led to the production of almost 100 pull requests and three new plugins. Likewise, many new developers joined our community, and we are excited to see you build more with Medusa in the future.&lt;/p&gt;

&lt;p&gt;A quick glimpse of the results achieved during this October:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~100% new Discord members&lt;/li&gt;
&lt;li&gt;64 merged pull requests&lt;/li&gt;
&lt;li&gt;21 pull requests in review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugins in development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strapi&lt;/li&gt;
&lt;li&gt;Algolia&lt;/li&gt;
&lt;li&gt;Elastic&lt;/li&gt;
&lt;li&gt;ShipBob&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are grateful for all your contributions!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Time to give back&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Swag.&lt;/strong&gt; As described in the &lt;a href="https://dev.to/medusajs/medusa-hacktoberfest-2021-13eb"&gt;Hacktoberfest announcement article&lt;/a&gt;, every contributor will receive special Medusa swag as an appreciation gift. Our small token of gratitude to all of you that took the time to participate in the Hacktober. The send-outs will happen during November - we cannot wait to send them out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wall of appreciation.&lt;/strong&gt; Our &lt;a href="https://www.medusajs.com/" rel="noopener noreferrer"&gt;website&lt;/a&gt; will soon feature a wall of appreciation displaying all Medusa contributors. There is no fix or feature too small for this wall, so no reason to hold back if you want to see yourself in there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Community chat.&lt;/strong&gt; At last, we wanted to invite you all to an in-person Zoom chat on Friday the 5th of November. Here we will gladly welcome everyone from our community to a chat about commerce, Medusa, and all things community-related. For more info, please follow along in our &lt;a href="https://discord.gg/F87eGuwkTp" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; during the following days.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  There is still an opportunity to be involved in Medusa development
&lt;/h3&gt;

&lt;p&gt;The opportunity to contribute and get involved in the development process of Medusa is not limited to Hacktoberfest. We will keep welcoming new community members just as we've done the past month. You can follow our &lt;a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contribution guide&lt;/a&gt; to get started.&lt;/p&gt;

&lt;p&gt;We strive to always have issues that are up for grabs so that everyone can take part. If you have your ideas or feature requests, you can find the entire Medusa team on our &lt;a href="https://discord.gg/WCZFV5v3j4" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hope to see you there!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>javascript</category>
      <category>hacktoberfest</category>
      <category>webdev</category>
    </item>
    <item>
      <title>That one thing I regret about my CSS code</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Sun, 10 Oct 2021 17:58:55 +0000</pubDate>
      <link>https://forem.com/fuzzyreason/that-one-thing-i-regret-about-my-css-code-5oe</link>
      <guid>https://forem.com/fuzzyreason/that-one-thing-i-regret-about-my-css-code-5oe</guid>
      <description>&lt;p&gt;The tech world evolves in a tremendously fast manner, and so do design trends. When teams are working hard to deliver beautiful applications, they are making sure that expectations meet reality.&lt;/p&gt;

&lt;p&gt;While doing that, engineers have to work with default browser behavior like the topic of this article - &lt;code&gt;outline&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When trying to deliver a great user experience, it's easy to get distracted and start paying less attention to details and not realizing why these default elements are implemented in the first place. &lt;/p&gt;

&lt;p&gt;I was this engineer, and the goal of this article is to raise awareness around accessibility and explain that you should keep the &lt;code&gt;outline&lt;/code&gt; for your projects even if it doesn't fit the look and feel of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an &lt;code&gt;outline&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;As the name implies, the &lt;code&gt;outline&lt;/code&gt; is an outside line around the HTML element.&lt;/p&gt;

&lt;p&gt;The first look feels pretty close to a &lt;code&gt;border&lt;/code&gt; property, but there are some essential differences in technical aspects and in mindset in general.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;outline&lt;/code&gt; is not visible if the element is not focused. The idea behind this property is to indicate that the component can be focused on folks using the keyboard only to navigate through your application or have a visual impairment.&lt;/p&gt;

&lt;p&gt;Some of the technical differences between outline and border are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can not modify the edges of the &lt;code&gt;outline&lt;/code&gt; . it operates as a single unit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;outline&lt;/code&gt; does not take any space and does not affect other elements and layout in general&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information, you can check the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/outline"&gt;MDN spec about the outline&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should outline stay?
&lt;/h2&gt;

&lt;p&gt;The outline has a very simple look and doesn't fit most of the modern minimalistic design concepts. That's why you can quite often find that engineers write a set of styles to hide it and keep the indication that the element is clickable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.my-fancy-looking-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Hide the outline */&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Add the pointing cursor when hovering the element */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the first look, that might feel like a valid solution, but the reality is that it's not.&lt;/p&gt;

&lt;p&gt;When hiding the outline, you have a couple of adverse outcomes, &lt;strong&gt;the accessibility of your application gets worst&lt;/strong&gt;, and &lt;strong&gt;navigation with keyboard only is getting trickier&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility issue
&lt;/h3&gt;

&lt;p&gt;Different groups of users need the outline to understand how they can navigate through your website.&lt;/p&gt;

&lt;p&gt;We all are different, and a good project should aim to be accessible for everyone. In my honest opinion, a project with great attention to accessibility is better than a minimalistic clean-looking application that is convenient for a specific user group only.&lt;/p&gt;

&lt;p&gt;I deliberately didn't use the phrase "sacrificing the great design for the best" because making an application more accessible is never a sacrifice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Caring about people should never be a negative aspect!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard navigation issue
&lt;/h3&gt;

&lt;p&gt;Many users prefer to navigate through websites quickly. An outline is a fantastic feature that can help with that.&lt;/p&gt;

&lt;p&gt;Creators want their applications to look beautiful, but sadly, it affects how specific projects help solve some problems quickly and conveniently.&lt;/p&gt;

&lt;p&gt;Here you can find a gif that shows the navigation using the Tab key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3ECvrxpcZ0mfyql1zg/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3ECvrxpcZ0mfyql1zg/giphy.gif" width="480" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you still want to remove an outline?
&lt;/h2&gt;

&lt;p&gt;I believe that this should not be an option, but if you still decide to remove an outline, there is an accessible way of doing that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tpgi.com/how-to-remove-css-outlines-in-an-accessible-manner/"&gt;Steve Faulkner's article&lt;/a&gt; covers the way of removing the outline and keeping your application accessible.&lt;/p&gt;

&lt;p&gt;Suppose you are ready to add a third-party library to your application. In that case, you can use the &lt;a href="https://github.com/lindsayevans/outline.js"&gt;remove-focus-outline&lt;/a&gt; npm package created by Lindsay Evans, which implements the idea described in the article.&lt;/p&gt;

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

&lt;p&gt;This topic is not new, and I am happy if it is not needed since your application is accessible for different users. Sadly I still can see engineers sacrificing accessibility for an excellent design. That's why I decided to give some attention to a topic that can help you build projects that can help more users find it useful.&lt;/p&gt;

&lt;p&gt;I was an engineer who was not paying attention to this specific point, and hopefully, this article can help more people to improve their applications.&lt;/p&gt;

&lt;p&gt;Keep the great work, and stay tuned!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>programming</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Medusa Hacktoberfest 2021</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Thu, 30 Sep 2021 17:07:14 +0000</pubDate>
      <link>https://forem.com/medusajs/medusa-hacktoberfest-2021-13eb</link>
      <guid>https://forem.com/medusajs/medusa-hacktoberfest-2021-13eb</guid>
      <description>&lt;p&gt;It's that time of year, and &lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktober&lt;/a&gt; is right around the corner. This year Medusa is excited to take part in the fest for the first time.&lt;/p&gt;

&lt;p&gt;An excellent opportunity to learn more about the open-source environment, meet creators, active contributors and win a t-shirt or have a tree planted in your name!&lt;/p&gt;

&lt;p&gt;Every year Hacktober fest brings &lt;a href="https://www.digitalocean.com/blog/hacktoberfest-recap2020/"&gt;tremendous activity&lt;/a&gt; and helps the open-source ecosystem grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Hacktober?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hacktoberfest.digitalocean.com/"&gt;Hacktober fest&lt;/a&gt; is a worldwide event that Digital Ocean organizes with the primary goal of supporting the open-source culture by encouraging contributions to open-source projects participating in the event.&lt;/p&gt;

&lt;p&gt;Making four or more contributions between October 1-31 will allow you to get a memorable t-shirt as a token of appreciation, or you can choose to have a tree planted in your name and help make Hacktoberfest 2021 more carbon neutral.&lt;/p&gt;

&lt;p&gt;You can follow &lt;a href="https://hacktoberfest.digitalocean.com/resources/participation"&gt;this guide&lt;/a&gt; to get a grasp of the rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why contribute to Medusa?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.medusajs.com/"&gt;Medusa&lt;/a&gt; is a headless e-commerce engine with a mission to provide a fantastic developer experience and give merchant's full control of their ecommerce stack.&lt;/p&gt;

&lt;p&gt;By contributing to Medusa, you help push the boundaries of e-commerce and make amazing commerce experiences easier for developers to create.&lt;/p&gt;

&lt;p&gt;By actively participating in Hacktober fest, you make Medusa more sustainable and help us progress quickly. Every single contribution matters, and you'll get an appreciation gift for your time and effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One pull request → Medusa sticker&lt;/li&gt;
&lt;li&gt;Three pull requests → Medusa t-shirt&lt;/li&gt;
&lt;li&gt;Build a Plugin (issues labeled with plugin) → Complete Medusa pack (t-shirt, hoodie, sticker)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can I contribute?
&lt;/h2&gt;

&lt;p&gt;Medusa supplies developers with different open-source tools, which you can find on our &lt;a href="https://github.com/medusajs"&gt;Github profile&lt;/a&gt;. Pick the tool you like the most and follow our &lt;a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md"&gt;contribution guide&lt;/a&gt; to keep the consistency of deliveries.&lt;/p&gt;

&lt;p&gt;Contributing to an open-source project might feel intimidating at the beginning, and below, you can find a guide about the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browse the issues that are labeled hacktober&lt;/li&gt;
&lt;li&gt;After finding an issue you want to take over, you can let the community know that you are willing to provide a solution in the comments section.&lt;/li&gt;
&lt;li&gt;Fork the Medusa repo and create a branch for your solution according to the &lt;a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md#branches"&gt;workflow&lt;/a&gt; described in the contribution guide.&lt;/li&gt;
&lt;li&gt;When the functionality is reviewed, push the branch to the forked repo and submit a pull request.&lt;/li&gt;
&lt;li&gt;After that, the Medusa core team members will review the pull request and let you know if additional work is needed.&lt;/li&gt;
&lt;li&gt;Once you receive the approval from the team, the contribution is ready to be merged and taken into account for the overall Hacktober fest activity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For more detailed guidance and support, feel free to join our &lt;a href="https://discord.gg/PGhCdgEP"&gt;Discord&lt;/a&gt; to ask the engineering team directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do I find help from the Medusa community?
&lt;/h2&gt;

&lt;p&gt;The community has a massive priority for &lt;a href="https://github.com/medusajs/medusa"&gt;Medusa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Contributing to an open-source project might be confusing and complicated. That's why team members of Medusa are always ready to support you on this path.&lt;/p&gt;

&lt;p&gt;Feel free to join &lt;a href="https://discord.gg/ruGn9fmv9q"&gt;our Discord&lt;/a&gt; and chat directly with the engineering team, so you can get the warm, welcoming experience and be surrounded by passionate developers just like you!&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Integrate Segment to your Medusa project</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Tue, 21 Sep 2021 16:57:53 +0000</pubDate>
      <link>https://forem.com/medusajs/integrate-segment-to-your-medusa-project-4akf</link>
      <guid>https://forem.com/medusajs/integrate-segment-to-your-medusa-project-4akf</guid>
      <description>&lt;p&gt;Modern e-commerce businesses have to integrate with a wide spectrum of tools from marketing and personalization to analytics and business intelligence. Integrations to these tools quickly become hard to maintain and new integrations become overly complex to implement putting a stretch on an e-commerce organization's resources.&lt;/p&gt;

&lt;p&gt;The CDP (Customer Data Platform) &lt;a href="https://segment.com/"&gt;Segment&lt;/a&gt; solves this problem by allowing users to instantly integrate with +100 tools through a single unified API. &lt;/p&gt;

&lt;p&gt;Medusa has an official plugin &lt;code&gt;medusa-plugin-segment&lt;/code&gt; that instantly gives you access to all Segment integrations and comes preconfigured with powerful server-side tracking&lt;/p&gt;

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

&lt;p&gt;Segment is a powerful Customer Data Platform that allows users to collect, transform, send and archive their customer data.&lt;/p&gt;

&lt;p&gt;Segment allows users to manage different tracking and marketing tools using one API and interface, making it very simple to try out and integrate with different services in your e-commerce stack.&lt;/p&gt;

&lt;p&gt;Common integration use cases that can be implemented with Segment include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mailchimp&lt;/li&gt;
&lt;li&gt;Klaviyo&lt;/li&gt;
&lt;li&gt;Google Analytics Enhanced E-commerce tracking&lt;/li&gt;
&lt;li&gt;Data warehousing for advanced data analytics and segmentation through services like Snowflake&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding Segment to your Medusa store
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you should create a &lt;a href="https://segment.com/docs/connections/sources/catalog/libraries/server/node/quickstart/"&gt;Node.js source in Segment&lt;/a&gt; in order to obtain the write key that will be provided in the plugin options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plugins in Medusa's ecosystem come as separate npm packages, that can be installed from the npm registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add medusa-plugin-segment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation open &lt;code&gt;medusa-config.js&lt;/code&gt; to configure the Segment plugin, by adding it to your project's plugin array and providing the options required by the plugin, namely the write key obtained from the Segment dashboard.&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="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;`medusa-plugin-segment`&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;write_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SEGMENT_WRITE_KEY&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;After the plugin has been configured you will get instant access to +100 services through the Segment dashboard. This allows you to try out new services for your e-commerce stack without having to make heavy integration investments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default tracking
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;medusa-plugin-segment&lt;/code&gt; comes with prebuilt tracking for common flows for Orders, Returns, Swaps, and Claims. Where applicable the events follow the &lt;a href="https://segment.com/docs/connections/spec/ecommerce/v2/"&gt;Segment Ecommerce Spec&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Below is a list of some of the events that are tracked by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders

&lt;ul&gt;
&lt;li&gt;Order Completed&lt;/li&gt;
&lt;li&gt;Order Shipped&lt;/li&gt;
&lt;li&gt;Order Refunded ← Without returned products&lt;/li&gt;
&lt;li&gt;Order Cancelled&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Returns

&lt;ul&gt;
&lt;li&gt;Order Refunded ← With returned products&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Swaps

&lt;ul&gt;
&lt;li&gt;Swap Created&lt;/li&gt;
&lt;li&gt;Swap Confirmed&lt;/li&gt;
&lt;li&gt;Swap Shipped&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Claims

&lt;ul&gt;
&lt;li&gt;Item Claimed&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default events serve as a good foundation for e-commerce tracking, allowing you to answer questions regarding product performance, return ratios, claim statistics, and more.&lt;/p&gt;

&lt;p&gt;In many cases you will want to track other events that are specific to your store - this is also possible through the Segment plugin, as the plugin registers the &lt;code&gt;segmentService&lt;/code&gt; in your Medusa project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking custom events
&lt;/h2&gt;

&lt;p&gt;Building from the custom functionality that can be guided by &lt;a href="https://docs.medusa-commerce.com/tutorial/adding-custom-functionality"&gt;the tutorial&lt;/a&gt; in Medusa docs, imagine that you want to track all welcome opt-ins. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;segmentService&lt;/code&gt; exposes a &lt;code&gt;track&lt;/code&gt; method that wraps &lt;a href="https://segment.com/docs/connections/spec/track/"&gt;Segment's Track Spec&lt;/a&gt;, allowing you to send events to the Segment from anywhere in your Medusa project. &lt;/p&gt;

&lt;p&gt;For example, to add tracking of the opt-ins in the &lt;code&gt;POST /welcome/:cart_id&lt;/code&gt; endpoint, you could add the following code:&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;segmentService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;segmentService&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;segmentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome Opt-In Registered&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cart_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;optin&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above snippet would send an event to Segment for further processing. The event data could be used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Segmentation of a mailing list in MailChimp or Klaviyo based on whether the customer opted in for welcomes&lt;/li&gt;
&lt;li&gt;Storage in the data warehouse for later analysis to answer questions like "Are customers who opt-in for welcomes more likely to become recurring customers?"&lt;/li&gt;
&lt;li&gt;Integration to Google Analytics's events&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This article covers the introduction to one of many &lt;a href="https://github.com/medusajs/medusa"&gt;Medusa&lt;/a&gt; plugins and an explanation of how you can enhance your e-commerce stack, which will help you to build a successful e-commerce project. &lt;/p&gt;

&lt;p&gt;Not sure where to start? We are happy to help and talk to you at our &lt;a href="https://discord.gg/ruGn9fmv9q"&gt;Discord&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>twilio</category>
    </item>
    <item>
      <title>Announcing Create Medusa App: One command for a full-stack headless e-commerce setup</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Wed, 15 Sep 2021 11:28:24 +0000</pubDate>
      <link>https://forem.com/medusajs/announcing-create-medusa-app-one-command-for-a-full-stack-headless-e-commerce-setup-28mo</link>
      <guid>https://forem.com/medusajs/announcing-create-medusa-app-one-command-for-a-full-stack-headless-e-commerce-setup-28mo</guid>
      <description>&lt;p&gt;With the new &lt;code&gt;create-medusa-app&lt;/code&gt; tool you will get your &lt;a href="https://github.com/medusajs/medusa"&gt;Medusa&lt;/a&gt; development environment ready within a couple of minutes. After completion, you will have a Medusa backend, a Gatsby or Next.js storefront, and an admin dashboard up and running on your local machine.&lt;/p&gt;

&lt;p&gt;Starting a new e-commerce project just got easier, now with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with &lt;code&gt;create-medusa-app&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;create-medusa-app&lt;/code&gt; with your preferred package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create medusa-app

npx create-medusa-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, &lt;code&gt;create-medusa-app&lt;/code&gt; is populating your database with some initial set of mock data, which helps to interact with Medusa setup intuitively straight away. &lt;/p&gt;

&lt;p&gt;Right after hitting one of those commands, the multistep installation process will be initiated, so the starter can be shaped right for the specific needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Destination folder
&lt;/h3&gt;

&lt;p&gt;Enter the path to the directory that will become the root of your Medusa project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;? Where should your project be installed? › my-medusa-store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pick the starter you prefer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;? Which Medusa starter would you like to &lt;span class="nb"&gt;install&lt;/span&gt;? …
❯ medusa-starter-default
  medusa-starter-contentful
  Other
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be presented with three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;medusa-starter-default&lt;/code&gt; is the most lightweight version of a Medusa project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;medusa-starter-contentful&lt;/code&gt; almost like the default starter, but with &lt;code&gt;medusa-plugin-contentful&lt;/code&gt; preinstalled&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Other&lt;/code&gt; if you have a different starter that you would wish to install from &lt;code&gt;Other&lt;/code&gt; will give you the option of providing a URL to that starter. An additional question will be asked if you choose this option:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Where is the starter located? &lt;span class="o"&gt;(&lt;/span&gt;URL or path&lt;span class="o"&gt;)&lt;/span&gt; › https://github.com/somecoolusername/my-custom-medusa-starter
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the walkthrough purposes, we assume that the selected starter is &lt;code&gt;medusa-starter-default&lt;/code&gt; and proceed to the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selecting a Storefront
&lt;/h3&gt;

&lt;p&gt;After selecting your Medusa starter you will be given the option to install one of our storefront starters. At the moment we have starters for Gatsby and Next.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Which storefront starter would you like to &lt;span class="nb"&gt;install&lt;/span&gt;? …
❯ Gatsby Starter
  Next.js Starter
  None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also select &lt;code&gt;None&lt;/code&gt; if the choice is to craft a custom storefront for your product. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;create-medusa-app&lt;/code&gt; now has all of the info necessary for the installation to begin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Creating new project from git: https://github.com/medusajs/medusa-starter-default.git
✔ Created starter directory layout
Installing packages...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation has been completed you will have a Medusa backend, a demo storefront, and an admin dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;p&gt;Inside the root folder which was specified at the beginning of the installation process the following structure could be found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/my-medusa-store
  /storefront // Medusa storefront starter
  /backend // Medusa starter as a backend option 
  /admin // Medusa admin panel 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;create-medusa-app&lt;/code&gt; prints out the commands that are available to you after installation. When each project is started you can visit your storefront, complete the order, and view the order in Medusa admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⠴ Installing packages...
✔ Packages installed
Initialising git &lt;span class="k"&gt;in &lt;/span&gt;my-medusa-store/admin
Create initial git commit &lt;span class="k"&gt;in &lt;/span&gt;my-medusa-store/admin

  Your project is ready 🚀. The available commands are:

    Medusa API
    &lt;span class="nb"&gt;cd &lt;/span&gt;my-medusa-store/backend
    yarn start

    Admin
    &lt;span class="nb"&gt;cd &lt;/span&gt;my-medusa-store/admin
    yarn start

    Storefront
    &lt;span class="nb"&gt;cd &lt;/span&gt;my-medusa-store/storefront
    yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;What's next?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To learn more about Medusa to go through our docs to get some inspiration and guidance for the next steps and further development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/how-to/headless-ecommerce-store-with-gatsby-contentful-medusa/"&gt;Find out how to set up a Medusa project with Gatsby and Contentful&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/tutorial/adding-custom-functionality/"&gt;Move your Medusa setup to the next level with some custom functionality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.medusajs.com/guides/plugins"&gt;Create your own Medusa plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any follow-up questions or want to chat directly with our engineering team we are always happy to meet you at our &lt;a href="https://discord.gg/ruGn9fmv9q"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>jamstack</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Setting up a Next.js storefront for your Medusa project</title>
      <dc:creator>Vadim Smirnov</dc:creator>
      <pubDate>Mon, 06 Sep 2021 18:35:52 +0000</pubDate>
      <link>https://forem.com/medusajs/setting-up-a-next-js-storefront-for-your-medusa-project-3j3k</link>
      <guid>https://forem.com/medusajs/setting-up-a-next-js-storefront-for-your-medusa-project-3j3k</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1repyme0xw6h3yj1ibw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1repyme0xw6h3yj1ibw.gif" alt="Medusa storefront"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; is a headless open source commerce platform giving engineers the foundation for building unique and scaleable digital commerce projects through our API-first engine. &lt;/p&gt;

&lt;p&gt;Being headless, our starters serve as a good foundation for you to get coupled with a frontend in a matter of minutes.&lt;/p&gt;

&lt;p&gt;This article assumes you already have the Medusa project created and ready to be linked to your Next.js starter.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;In order to get started let's open the terminal and use the following command to create an instance of your storefront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app &lt;span class="nt"&gt;-e&lt;/span&gt; https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a storefront codebase that is ready to be used with our Medusa server.&lt;/p&gt;

&lt;p&gt;Next, we have to complete two steps to make our new shiny storefront to speak with our server: &lt;strong&gt;link storefront to a server&lt;/strong&gt; and &lt;strong&gt;update the &lt;code&gt;STORE_CORS&lt;/code&gt; variable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's jump to these two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Link storefront to a server
&lt;/h2&gt;

&lt;p&gt;For this part, we should navigate to a &lt;code&gt;client.js&lt;/code&gt; file which you can find in the utils folder.&lt;/p&gt;

&lt;p&gt;We don't need to do much in here, but to make sure that our storefront is pointing to the port, where the server is running&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;Medusa&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;@medusajs/medusa-js&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;BACKEND_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GATSBY_STORE_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;http://localhost:9000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--- That is the line we are looking for&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;createClient&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Medusa&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BACKEND_URL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default the Medusa server is running at port 9000, so if you didn't change that we are good to go to our next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update the &lt;code&gt;STORE_CORS&lt;/code&gt; variable
&lt;/h2&gt;

&lt;p&gt;Here let's navigate to your Medusa server and open &lt;code&gt;medusa-config.js&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Let's locate the &lt;code&gt;STORE_CORS&lt;/code&gt; variable and make sure it's the right port (which is 3000 by default for Next.js projects)&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="cm"&gt;/* 
 * CORS to avoid issues when consuming Medusa from a client.
 * Should be pointing to the port where the storefront is running.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STORE_CORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STORE_CORS&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a storefront that interacts with our Medusa server and with that we have a sweet and complete e-commerce setup with a Next.js storefront.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;If you want to discover the endless possibilities of Medusa's ecosystem you can find a lot more useful resources on &lt;a href="https://docs.medusa-commerce.com/" rel="noopener noreferrer"&gt;our docs page&lt;/a&gt; which can help you to build your awesome commerce project!&lt;/p&gt;

&lt;p&gt;If you need help or have questions about how to use Medusa feel free to join our &lt;a href="https://discord.gg/ruGn9fmv9q" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; to get direct access to our engineering team.&lt;/p&gt;

</description>
      <category>jamstack</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
