<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ User Interface - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ User Interface - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 26 Jul 2026 16:37:58 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/user-interface/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Responsive UIs in Flutter ]]>
                </title>
                <description>
                    <![CDATA[ Building responsive UIs in Flutter can be challenging, especially when you want your app to look great on phones, tablets, and desktops without maintaining multiple layouts. Fortunately, Flutter provides powerful tools like MediaQuery, LayoutBuilder,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-responsive-uis-in-flutter/</link>
                <guid isPermaLink="false">690108bed60a2bc774c2f435</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Tue, 28 Oct 2025 18:17:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761675310940/332589a7-55e0-4cb2-935e-aa1011709e2e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building responsive UIs in Flutter can be challenging, especially when you want your app to look great on phones, tablets, and desktops without maintaining multiple layouts. Fortunately, Flutter provides powerful tools like <code>MediaQuery</code>, <code>LayoutBuilder</code>, and the <code>flutter_screenutil</code> package to make this process seamless.</p>
<p>In this article, we’ll walk through a complete sample responsive screen, explaining each part of the code step by step. You’ll learn not only how to make your layout adapt to different screen sizes and orientations but also how to use scaling utilities to keep your text and spacing consistent across all devices.</p>
<p>By the end, you’ll understand how to structure a Flutter app that automatically adjusts its layout and typography based on the available screen real estate, a must-know skill for any developer targeting multiple platforms.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-responsive-vs-adaptive-design">Understanding Responsive vs Adaptive Design</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-core-flutter-layout-widgets-for-responsive-ui">Core Flutter Layout Widgets for Responsive UI</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-containersizedbox">Container/SizedBox</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-overly-fixed-sizing-not-responsive">Overly Fixed Sizing (Not Responsive)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-responsive-flexible-sizing">Responsive / Flexible Sizing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sizedbox-for-spacing-or-constraints">SizedBox for Spacing or Constraints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-row-column">Row, Column</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-expanded-and-flexible">Expanded and Flexible</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-layoutbuilder">LayoutBuilder</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mediaquery-and-screen-information">MediaQuery and Screen Information</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-breakpoints-orientation-and-large-screen-adaptation">Breakpoints, Orientation and Large-Screen Adaptation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-responsive-typography-images-and-assets">Responsive Typography, Images and Assets</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-flexible-layouts-expanded-flexible-fractionallysizedbox">Flexible Layouts: Expanded, Flexible, FractionallySizedBox</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-tools-and-packages">Advanced Tools and Packages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-safe-areas-notches-and-insets">Handling Safe Areas, Notches and Insets</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adaptive-ui-for-tabletsdesktop-and-multi-window">Adaptive UI for Tablets/Desktop and Multi-Window</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-and-performance-considerations">Best Practices and Performance Considerations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-and-debugging-responsive-layouts">Testing and Debugging Responsive Layouts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-reusable-responsive-widgetscustom-widgets">Building Reusable Responsive Widgets/Custom Widgets</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-example-of-a-complete-sample-responsive-screen">Example of A Complete Sample Responsive Screen</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-setup-and-imports">Project Setup and Imports</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-app-entry-point-the-myapp-class">App Entry Point: The MyApp Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-responsive-home-screen-myhomepage">The Responsive Home Screen (MyHomePage)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-core-content">Building the Core Content</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-full-walkthrough-code-explained-in-depth">Full Walkthrough: Code Explained in Depth</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-imports-and-app-entry-point">Imports and App Entry Point</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-app-root-the-myapp-widget">App Root: The MyApp Widget</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-home-screen-layout-myhomepage">The Home Screen Layout MyHomePage</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-scaffold-and-appbar">The Scaffold and AppBar</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-safearea-layoutbuilder">SafeArea + LayoutBuilder</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-large-and-small-screens">Handling Large and Small Screens</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-the-core-content-1">Building the Core Content</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-works">Why This Works</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-key-takeaways">Key Takeaways</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving in, ensure you have:</p>
<ul>
<li><p>A working Flutter environment (SDK, IDE, emulator or device).</p>
</li>
<li><p>Basic proficiency in Flutter: Knowledge of widgets, stateless/stateful, Row/Column, Scaffold, and so on.</p>
</li>
<li><p>Familiarity with Dart basics and how layout works in Flutter (constraints, sizing).</p>
</li>
<li><p>(Optional but helpful) A design/mockup (for example from Figma) with defined design size or target screen.</p>
</li>
<li><p>Understanding that creating truly adaptive/responsive UIs means accommodating <strong>different screen sizes</strong>, <strong>orientations</strong>, <strong>aspect-ratios</strong>, and <strong>platforms</strong> (mobile/tablet/web/desktop).</p>
</li>
</ul>
<h2 id="heading-understanding-responsive-vs-adaptive-design">Understanding Responsive vs Adaptive Design</h2>
<p>It’s helpful to clarify terminology:</p>
<ul>
<li><p><strong>Responsive design</strong> is about <em>fitting</em> the UI into the available space: The layout scales, reflows, rearranges as the screen size or orientation changes.</p>
</li>
<li><p><strong>Adaptive design</strong> is about selecting different UI patterns depending on the device/screen. For example, using a side-panel on desktop, bottom navigation on mobile. The UI adapts to the context of use.</p>
</li>
<li><p>In practice with Flutter, you often do <em>both</em>: Responsive (scaling/reflow) and adaptive (choosing variant layouts).</p>
</li>
<li><p>According to the official documentation:</p>
<blockquote>
<p>“Responsive design is about fitting the UI into the space. Adaptive design is about the UI being usable in the space.” (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive">Flutter Docs</a>)</p>
</blockquote>
</li>
<li><p>Also, some best practices to follow: don’t assume device type (phone/tablet) based on screen size, don’t lock orientation, and don’t rely solely on <code>MediaQuery.orientation</code>. (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive/best-practices">Flutter Docs</a>)</p>
</li>
</ul>
<h2 id="heading-core-flutter-layout-widgets-for-responsive-ui">Core Flutter Layout Widgets for Responsive UI</h2>
<p>Flutter provides many fundamental widgets for layout – when used well, they form the backbone of responsive UIs.</p>
<ol>
<li><h3 id="heading-containersizedbox">Container/SizedBox</h3>
</li>
</ol>
<p><code>Container</code>, <code>SizedBox</code> let you size widgets explicitly or via constraints.</p>
<p>Use cautiously: Overly fixed sizes can hamper responsiveness (for example, a Container(width: 300) may overflow on small screens).</p>
<p>Better to use relative sizing or allow flexibility.</p>
<p>Here are clear, practical Flutter code examples that illustrate <strong>how to use</strong> <code>Container</code> and <code>SizedBox</code>, including both <strong>bad (fixed)</strong> and <strong>good (responsive/flexible)</strong> sizing approaches:</p>
<h3 id="heading-overly-fixed-sizing-not-responsive">Overly Fixed Sizing (Not Responsive)</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FixedContainerExample</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: Center(
        <span class="hljs-comment">// This might overflow on small screens!</span>
        child: Container(
          width: <span class="hljs-number">300</span>,
          height: <span class="hljs-number">200</span>,
          color: Colors.blue,
          child: <span class="hljs-keyword">const</span> Center(
            child: Text(
              <span class="hljs-string">'Fixed Size Container'</span>,
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
    );
  }
}
</code></pre>
<p><strong>Problem:</strong><br>If the screen width is less than 300px (like on small mobile devices), this widget may overflow or get cut off.</p>
<h3 id="heading-responsive-flexible-sizing">Responsive / Flexible Sizing</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ResponsiveContainerExample</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> screenWidth = MediaQuery.of(context).size.width;

    <span class="hljs-keyword">return</span> Scaffold(
      body: Center(
        <span class="hljs-comment">// Use relative width and height</span>
        child: Container(
          width: screenWidth * <span class="hljs-number">0.8</span>, <span class="hljs-comment">// 80% of screen width</span>
          height: <span class="hljs-number">200</span>,
          decoration: BoxDecoration(
            color: Colors.blue,
            borderRadius: BorderRadius.circular(<span class="hljs-number">12</span>),
          ),
          child: <span class="hljs-keyword">const</span> Center(
            child: Text(
              <span class="hljs-string">'Responsive Container (80% width)'</span>,
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
    );
  }
}
</code></pre>
<p><strong>Why better:</strong><br>It adjusts automatically to different screen sizes.</p>
<h3 id="heading-sizedbox-for-spacing-or-constraints">SizedBox for Spacing or Constraints</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SizedBoxExample</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Above Spacer'</span>),
            <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">16</span>), <span class="hljs-comment">// Adds spacing</span>
            Container(
              width: <span class="hljs-number">200</span>,
              height: <span class="hljs-number">100</span>,
              color: Colors.green,
              child: <span class="hljs-keyword">const</span> Center(child: Text(<span class="hljs-string">'Sized Container'</span>)),
            ),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p><code>SizedBox</code> is lightweight and great for adding <strong>fixed spacing</strong> or defining <strong>simple dimensions</strong> without needing a full <code>Container</code>.</p>
<ol start="2">
<li><h3 id="heading-row-column">Row, Column</h3>
</li>
</ol>
<p><code>Row</code> and <code>Column</code> arrange children horizontally or vertically.</p>
<p><strong>Note</strong>: <code>Row</code> gives infinite horizontal space (subject to parent constraints), so children must handle sizing or else overflow.</p>
<p>When you use a <strong>Row</strong> or <strong>Column</strong>, Flutter tries to give them as much space as possible <strong>along their main axis</strong> (horizontal for Row, vertical for Column).<br>If the children inside don’t know how much space to take, <strong>they can overflow</strong> or not display as intended.</p>
<p>That’s why we use <code>Expanded</code>, <code>Flexible</code>, or <code>SizedBox</code> to tell Flutter <strong>how each child should use the available space</strong>.</p>
<p>Example:</p>
<pre><code class="lang-dart">Row(
  children: [
    Expanded(
      child: Container(
        <span class="hljs-comment">// Your widget content here</span>
      ),
    ),
    <span class="hljs-comment">// Other widgets...</span>
  ],
)
</code></pre>
<p>Here <code>Expanded</code> says: “take up remaining space proportionally”.</p>
<h3 id="heading-expanded-and-flexible">Expanded and Flexible</h3>
<p><code>Expanded</code> forces its child to fill remaining space in a <code>Row</code> or <code>Column</code>.</p>
<p><code>Flexible</code> gives its child flexibility: it may shrink or grow but not forcibly fill.</p>
<p>Example:</p>
<pre><code class="lang-dart">Row(
  children: [
    Flexible(
      child: Container(
        <span class="hljs-comment">// Widget content</span>
      ),
    ),
    <span class="hljs-comment">// Other widgets...</span>
  ],
)
</code></pre>
<p>Using <code>Flexible</code>/<code>Expanded</code> helps distribute space dynamically and naturally adapt to varying screen sizes.</p>
<ol start="3">
<li><h3 id="heading-layoutbuilder">LayoutBuilder</h3>
</li>
</ol>
<p><code>LayoutBuilder</code> gives you the parent widget’s constraints (<code>maxWidth</code>, <code>maxHeight</code>) and allows you to re-build UI accordingly.</p>
<pre><code class="lang-dart">LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    <span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">600</span>) {
      <span class="hljs-comment">// Large screen layout</span>
      <span class="hljs-keyword">return</span> LargeScreenWidget();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// Small screen layout</span>
      <span class="hljs-keyword">return</span> SmallScreenWidget();
    }
  },
)
</code></pre>
<p>This is often more reliable than just checking <code>MediaQuery.orientation</code> or <code>MediaQuery.size</code>, especially in multi-window/foldable devices. (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive/best-practices?utm_source=chatgpt.com">Flutter Docs</a>)</p>
<ol start="4">
<li><h4 id="heading-fractionallysizedbox-and-aspectratio">FractionallySizedBox and AspectRatio</h4>
</li>
</ol>
<p><code>FractionallySizedBox</code>: Sizes its child as a fraction of the parent’s size (for example, <code>widthFactor: 0.5</code>).</p>
<p><code>AspectRatio</code>: Maintains a fixed aspect ratio (width/height), useful for images or containers.</p>
<pre><code class="lang-dart">AspectRatio(
  aspectRatio: <span class="hljs-number">16</span> / <span class="hljs-number">9</span>,
  child: YourWidget(),
)
</code></pre>
<p>These help maintain proportionally consistent layouts across screen sizes.</p>
<h2 id="heading-mediaquery-and-screen-information">MediaQuery and Screen Information</h2>
<p>Understanding screen size, orientation, padding, device pixel ratio and so on, is essential.</p>
<h4 id="heading-using-mediaquery">Using MediaQuery</h4>
<pre><code class="lang-dart"><span class="hljs-built_in">double</span> screenWidth  = MediaQuery.of(context).size.width;
<span class="hljs-built_in">double</span> screenHeight = MediaQuery.of(context).size.height;
Orientation orientation = MediaQuery.of(context).orientation;
<span class="hljs-built_in">double</span> devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
EdgeInsets padding = MediaQuery.of(context).padding;
</code></pre>
<p><code>size</code> gives the logical pixel width/height, <code>orientation</code> informs if the device is in portrait or landscape, <code>devicePixelRatio</code> shows how many physical pixels per logical pixel, helpful for image scaling, and <code>padding</code> gives system UI insets (notches, status bar, navigation bar).</p>
<p>Use these values to tailor your UI: for example, adjust font sizes, container widths, or layout decisions.</p>
<h4 id="heading-example-responsive-typography">Example: Responsive Typography</h4>
<pre><code class="lang-dart">Text(
  <span class="hljs-string">'Your text here'</span>,
  style: TextStyle(
    fontSize: screenWidth * <span class="hljs-number">0.04</span>, <span class="hljs-comment">// 4% of screen width</span>
  ),
)
</code></pre>
<p>Approach: Compute font size relative to screen width or height. But be cautious, text readability and accessibility (for example, system font size changes) should be considered, see best practices below.</p>
<h3 id="heading-breakpoints-orientation-and-large-screen-adaptation">Breakpoints, Orientation and Large-Screen Adaptation</h3>
<p>To create UIs that look great on tablets/desktops as well as phones:</p>
<h4 id="heading-orientation">Orientation</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (MediaQuery.of(context).orientation == Orientation.portrait) {
  <span class="hljs-comment">// Portrait layout</span>
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// Landscape layout</span>
}
</code></pre>
<p>Works for basic cases, but beware: orientation alone doesn’t capture window size (especially in desktop/multi-window environments), prefer checking constraints or size. (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive/best-practices?utm_source=chatgpt.com">Flutter Docs</a>)</p>
<h4 id="heading-breakpoints-and-adaptive-layouts">Breakpoints and Adaptive Layouts</h4>
<p>Define custom breakpoints based on width (or other metrics) to trigger different layouts.</p>
<p>Example:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (screenWidth &gt; <span class="hljs-number">600</span>) {
  <span class="hljs-comment">// Tablet/large-screen UI</span>
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// Phone UI</span>
}
</code></pre>
<p>Some sources propose standard breakpoints, for example, compact (&lt;600), medium (600-840), large (&gt;840). With <code>LayoutBuilder</code> you can detect parent constraint width instead of global screen width, which is more robust.</p>
<h4 id="heading-large-screens-and-safe-use-of-space">Large Screens and Safe Use of Space</h4>
<p>On very wide screens, filling full width may hurt readability. The official Flutter docs recommend limiting content width (for example, using <code>ConstrainedBox</code> + <code>Center</code>) for large screens so that lines of text aren't excessively long. (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive/best-practices?utm_source=chatgpt.com">Flutter Docs</a>)</p>
<p>Example:</p>
<pre><code class="lang-dart">Center(
  child: ConstrainedBox(
    constraints: BoxConstraints(maxWidth: <span class="hljs-number">800</span>),
    child: YourContent(),
  ),
)
</code></pre>
<h3 id="heading-responsive-typography-images-and-assets">Responsive Typography, Images and Assets</h3>
<h4 id="heading-typography">Typography</h4>
<p>Use scalable units whenever possible (see packages later). Wrap text in <code>Flexible</code>/<code>Expanded</code> if inside <code>Row</code>/<code>Column</code> to avoid overflow, and consider system font scale factor: You can use <code>MediaQuery.of(context).textScaleFactor</code> to adapt fonts for accessibility.</p>
<h4 id="heading-images-and-boxfit">Images and BoxFit</h4>
<pre><code class="lang-dart">Image.asset(
  <span class="hljs-string">'assets/your_image.png'</span>,
  fit: BoxFit.cover,  <span class="hljs-comment">// or BoxFit.contain, BoxFit.fitWidth etc.</span>
  width: someWidth,   <span class="hljs-comment">// responsive width</span>
  height: someHeight, <span class="hljs-comment">// responsive height</span>
)
</code></pre>
<p><code>BoxFit.cover</code> allows image to fill container while maintaining aspect ratio.</p>
<p>Use <code>AspectRatio</code> or <code>FractionallySizedBox</code> to keep images proportional.</p>
<h4 id="heading-asset-density-and-pixel-ratio">Asset Density and Pixel Ratio</h4>
<p>For high resolution devices (high <code>devicePixelRatio</code>), provide higher resolution assets (2x/3x) so they appear crisp.</p>
<p>Flutter handles asset variants (<code>asset@2x.png</code> and so on) automatically, but in very large screen layouts you may want extra details.</p>
<h3 id="heading-flexible-layouts-expanded-flexible-fractionallysizedbox">Flexible Layouts: Expanded, Flexible, FractionallySizedBox</h3>
<p>We discussed these partially above, but here are deeper guidelines.</p>
<h4 id="heading-expanded-and-flexible-1">Expanded and Flexible</h4>
<p>Use inside <code>Row</code> or <code>Column</code> to distribute space.</p>
<p>Example:</p>
<pre><code class="lang-dart">Row(
  children: [
    Expanded(flex: <span class="hljs-number">2</span>, child: Container(color: Colors.red)),
    SizedBox(width: <span class="hljs-number">8</span>),
    Expanded(flex: <span class="hljs-number">1</span>, child: Container(color: Colors.blue)),
  ],
)
</code></pre>
<p>The red container takes twice the width of the blue. Using <code>Flexible</code> allows its child to expand or shrink, but doesn’t force full space.</p>
<h4 id="heading-fractionallysizedbox">FractionallySizedBox</h4>
<p>Example:</p>
<pre><code class="lang-dart">FractionallySizedBox(
  widthFactor: <span class="hljs-number">0.8</span>,  <span class="hljs-comment">// 80% of parent width</span>
  child: SomeWidget(),
)
</code></pre>
<p>Useful when you want a widget to occupy a fraction of available space without using exact pixel values.</p>
<h4 id="heading-aspectratio">AspectRatio</h4>
<pre><code class="lang-dart">AspectRatio(
  aspectRatio: <span class="hljs-number">16</span>/<span class="hljs-number">9</span>,
  child: Container(color: Colors.green),
)
</code></pre>
<p>Ensures the container maintains 16:9 ratio regardless of parent size.</p>
<h3 id="heading-advanced-tools-and-packages">Advanced Tools and Packages</h3>
<p>Using packages can simplify many repetitive tasks. Here are some highly used ones.</p>
<h4 id="heading-flutterscreenutil">flutter_screenutil</h4>
<p>Helps scale widths, heights, font sizes based on a design size you specify.</p>
<p>Example initialization:</p>
<pre><code class="lang-dart">ScreenUtilInit(
  designSize: Size(<span class="hljs-number">360</span>, <span class="hljs-number">690</span>), <span class="hljs-comment">// your base design size</span>
  builder: () =&gt; MaterialApp(
    home: MyHomePage(),
  ),
);
</code></pre>
<p>Usage:</p>
<pre><code class="lang-dart">width: <span class="hljs-number">200.</span>w,         <span class="hljs-comment">// scaled width</span>
height: <span class="hljs-number">150.</span>h,        <span class="hljs-comment">// scaled height</span>
fontSize: <span class="hljs-number">16.</span>sp,      <span class="hljs-comment">// scaled font size</span>
radius: <span class="hljs-number">12.</span>r,         <span class="hljs-comment">// scaled radius</span>
</code></pre>
<p>Advantages: Easy scaling of many sizes. But you must use <code>.w</code>, <code>.h</code>, <code>.sp</code>, <code>.r</code> consistently.</p>
<h4 id="heading-responsivebuilder">responsive_builder</h4>
<p>Helps produce layouts for different device screen types (mobile/tablet/desktop).</p>
<p>Example:</p>
<pre><code class="lang-dart">ResponsiveBuilder(
  builder: (context, sizingInformation) {
    <span class="hljs-keyword">if</span> (sizingInformation.deviceScreenType == DeviceScreenType.mobile) {
      <span class="hljs-keyword">return</span> MobileLayout();
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (sizingInformation.deviceScreenType == DeviceScreenType.tablet) {
      <span class="hljs-keyword">return</span> TabletLayout();
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> DesktopLayout();
    }
  },
);
</code></pre>
<p>Useful for switching entire widget trees based on device type. (<a target="_blank" href="https://medium.com/%40ravipatel84184/mastering-responsive-ui-in-flutter-a-comprehensive-guide-49c4ba9902af?utm_source=chatgpt.com">Medium</a>)</p>
<h4 id="heading-others">Others</h4>
<ol>
<li><p><code>responsive_framework</code>, <code>adaptive_breakpoints</code>, and so on.</p>
</li>
<li><p>When choosing a package, check maintenance, popularity, compatibility with your Flutter version.</p>
</li>
</ol>
<h3 id="heading-handling-safe-areas-notches-and-insets">Handling Safe Areas, Notches and Insets</h3>
<p>Modern devices have notches, status bars, navigation bars, foldables and so on. Use widgets and APIs to handle these.</p>
<ol>
<li><p>Wrap main content in <code>SafeArea</code> to avoid system UI intrusions.</p>
</li>
<li><p>Use <code>MediaQuery.of(context).padding</code> to detect safe padding (top, bottom, left, right).</p>
</li>
</ol>
<p>Example:</p>
<pre><code class="lang-dart">Padding(
  padding: MediaQuery.of(context).padding,
  child: YourContent(),
)
</code></pre>
<p>On Android you can also set UI mode:</p>
<pre><code class="lang-dart">SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
</code></pre>
<p>and update styles so status/navigation bar become transparent, helps full-screen UIs.</p>
<h3 id="heading-adaptive-ui-for-tabletsdesktop-and-multi-window">Adaptive UI for Tablets/Desktop and Multi-Window</h3>
<p>As your app runs on larger screens or in multi-window environments (foldables, desktops, web), consider:</p>
<ol>
<li><p>Switching from bottom navigation (mobile) to a navigation rail or side‐panel (tablet/desktop).</p>
</li>
<li><p>Using <code>ConstrainedBox</code> to limit content width on wide screens for readability.</p>
</li>
<li><p>Layout changes: Instead of single column scroll, you may display side-by-side panels or grid layouts.</p>
</li>
<li><p>Use <code>LayoutBuilder</code> or packages to detect width/constraints and choose the appropriate layout.</p>
</li>
<li><p>Avoid device type checking (for example, “if tablet”), instead base on window size.</p>
<p> Example:</p>
<pre><code class="lang-dart"> LayoutBuilder(
   builder: (context, constraints) {
     <span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">1024</span>) {
       <span class="hljs-keyword">return</span> DesktopScaffold();
     } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">600</span>) {
       <span class="hljs-keyword">return</span> TabletScaffold();
     } <span class="hljs-keyword">else</span> {
       <span class="hljs-keyword">return</span> MobileScaffold();
     }
   },
 );
</code></pre>
</li>
</ol>
<h2 id="heading-best-practices-and-performance-considerations">Best Practices and Performance Considerations</h2>
<p>Summarizing key best practices:</p>
<ol>
<li><p><strong>Break widgets into smaller reusable widgets</strong> improves maintainability and reuse.</p>
</li>
<li><p><strong>Start building from the inside out</strong>. Start with smallest components and move outward, rather than imposing outer container constraints first.</p>
</li>
<li><p><strong>Ensure defined constraints</strong> to avoid infinite bounds and overflow by using <code>Expanded</code>, <code>Flexible</code>, and so on.</p>
</li>
<li><p><strong>Avoid fixed sizes where unnecessary</strong>. Relative sizing or flex layouts adapt better.</p>
</li>
<li><p><strong>Avoid relying solely on screen size or orientation</strong>, use constraints instead of device type checks.</p>
</li>
<li><p><strong>Performance matters</strong>, avoid over-nesting heavy layouts, avoid rebuilding large subtrees when not needed. Use <code>const</code> widgets and keep build method lean.</p>
</li>
<li><p><strong>Accessibility</strong> account for large font scaling (<code>MediaQuery.textScaleFactor</code>), screen readers, keyboard/mouse input on larger screens.</p>
</li>
<li><p><strong>Test on multiple screen sizes/orientations</strong> actual devices/emulators/display sizes.</p>
</li>
<li><p><strong>Font/text overflow</strong> wrap text in <code>Flexible</code>, set <code>overflow</code>, <code>softWrap</code>, and test for dynamic content.</p>
</li>
</ol>
<h2 id="heading-testing-and-debugging-responsive-layouts">Testing and Debugging Responsive Layouts</h2>
<ol>
<li><p>Use device emulators/simulators with different screen sizes (phones, tablets, desktops).</p>
</li>
<li><p>For web/desktop, resize browser window to see how layout adapts.</p>
</li>
<li><p>Use <code>device_preview</code> package or built-in Flutter tools to simulate various devices.</p>
</li>
<li><p>Use Flutter DevTools’ <strong>Widget Inspector</strong> and <strong>Layout Explorer</strong> to understand how widgets are sized and laid out.</p>
</li>
<li><p>Test orientation changes, multi-window, split view (for example, Android foldables).</p>
</li>
<li><p>Check for overflow errors (yellow/black stripes) or unexpected scroll behavior.</p>
</li>
<li><p>Check accessibility: Scale fonts up/down, test with screen reader, check keyboard navigation in desktop mode.</p>
</li>
</ol>
<h2 id="heading-building-reusable-responsive-widgetscustom-widgets">Building Reusable Responsive Widgets/Custom Widgets</h2>
<p>One of the keys to scalable responsive UI is creating reusable widgets that encapsulate responsive behavior.</p>
<h4 id="heading-example-responsivetext-widget">Example: ResponsiveText Widget</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_screenutil/flutter_screenutil.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ResponsiveText</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> text;
  <span class="hljs-keyword">final</span> FontWeight fontWeight;
  <span class="hljs-keyword">final</span> Color color;

  <span class="hljs-keyword">const</span> ResponsiveText(
    <span class="hljs-keyword">this</span>.text, {
    Key? key,
    <span class="hljs-keyword">this</span>.fontWeight = FontWeight.normal,
    <span class="hljs-keyword">this</span>.color = Colors.black,
  }) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Text(
      text,
      style: TextStyle(
        fontSize: <span class="hljs-number">16.</span>sp,         <span class="hljs-comment">// scaled font size</span>
        fontWeight: fontWeight,
        color: color,
      ),
    );
  }
}
</code></pre>
<p>Usage: Instead of manually specifying font size every time, you use <code>ResponsiveText</code>. Similarly, you can build <code>ResponsiveContainer</code>, <code>ResponsivePadding</code>, and so on. Encapsulating responsive logic in widgets improves code reuse and consistency.</p>
<h4 id="heading-example-breakpointawarelayout-widget">Example: BreakpointAwareLayout Widget</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BreakpointAwareLayout</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">final</span> Widget mobile;
  <span class="hljs-keyword">final</span> Widget tablet;
  <span class="hljs-keyword">final</span> Widget desktop;

  <span class="hljs-keyword">const</span> BreakpointAwareLayout({
    Key? key,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.mobile,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.tablet,
    <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.desktop,
  }) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> LayoutBuilder(
      builder: (context, constraints) {
        <span class="hljs-keyword">if</span> (constraints.maxWidth &gt;= <span class="hljs-number">1024</span>) {
          <span class="hljs-keyword">return</span> desktop;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (constraints.maxWidth &gt;= <span class="hljs-number">600</span>) {
          <span class="hljs-keyword">return</span> tablet;
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> mobile;
        }
      },
    );
  }
}
</code></pre>
<p>Usage: You supply three versions of your UI and widget will choose based on width.</p>
<h2 id="heading-example-of-a-complete-sample-responsive-screen">Example of A Complete Sample Responsive Screen</h2>
<p>We’ll divide the example into these parts:</p>
<ol>
<li><p><strong>Project Setup and Imports</strong></p>
</li>
<li><p><strong>App Entry Point (</strong><code>main()</code> and <code>MyApp</code>)</p>
</li>
<li><p><strong>ScreenUtil Initialization</strong></p>
</li>
<li><p><strong>Main Page Layout (</strong><code>MyHomePage</code>)</p>
</li>
<li><p><strong>Adaptive Layout with</strong> <code>LayoutBuilder</code></p>
</li>
<li><p><strong>Main Content Builder (</strong><code>_buildMainContent</code>)</p>
</li>
<li><p><strong>Responsive Styling with</strong> <code>flutter_screenutil</code></p>
</li>
</ol>
<h3 id="heading-project-setup-and-imports"><strong>Project Setup and Imports</strong></h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_screenutil/flutter_screenutil.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(MyApp());
}
</code></pre>
<h3 id="heading-app-entry-point-the-myapp-class"><strong>App Entry Point: The</strong> <code>MyApp</code> Class</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-comment">// Design size corresponds to the reference design (e.g., iPhone 6/7/8)</span>
  <span class="hljs-keyword">final</span> Size designSize = <span class="hljs-keyword">const</span> Size(<span class="hljs-number">360</span>, <span class="hljs-number">690</span>);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> ScreenUtilInit(
      designSize: designSize,
      minTextAdapt: <span class="hljs-keyword">true</span>,       <span class="hljs-comment">// adapts font size</span>
      splitScreenMode: <span class="hljs-keyword">true</span>,    <span class="hljs-comment">// supports split-screen</span>
      builder: (context, child) {
        <span class="hljs-keyword">return</span> MaterialApp(
          title: <span class="hljs-string">'Responsive Flutter Example'</span>,
          home: MyHomePage(),
        );
      },
    );
  }
}
</code></pre>
<h3 id="heading-the-responsive-home-screen-myhomepage"><strong>The Responsive Home Screen (</strong><code>MyHomePage</code>)</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyHomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyHomePage({Key? key}) : <span class="hljs-keyword">super</span>(key: key);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> screenWidth  = MediaQuery.of(context).size.width;
    <span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> screenHeight = MediaQuery.of(context).size.height;
    <span class="hljs-keyword">final</span> Orientation orientation = MediaQuery.of(context).orientation;

    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: Text(
          <span class="hljs-string">'Responsive UI'</span>,
          style: TextStyle(fontSize: <span class="hljs-number">20.</span>sp),
        ),
      ),
      body: SafeArea(
        child: LayoutBuilder(
          builder: (context, constraints) {
            <span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">600</span>) {
              <span class="hljs-comment">// Tablet/Desktop layout</span>
              <span class="hljs-keyword">return</span> Row(
                children: [
                  Expanded(
                    flex: <span class="hljs-number">2</span>,
                    child: Container(
                      color: Colors.blueGrey[<span class="hljs-number">50</span>],
                      child: Center(
                        child: Text(
                          <span class="hljs-string">'Sidebar Panel'</span>,
                          style: TextStyle(fontSize: <span class="hljs-number">18.</span>sp),
                        ),
                      ),
                    ),
                  ),
                  Expanded(
                    flex: <span class="hljs-number">5</span>,
                    child: Container(
                      padding: EdgeInsets.all(<span class="hljs-number">16.</span>w),
                      child: _buildMainContent(context),
                    ),
                  ),
                ],
              );
            } <span class="hljs-keyword">else</span> {
              <span class="hljs-comment">// Mobile layout</span>
              <span class="hljs-keyword">return</span> SingleChildScrollView(
                child: Padding(
                  padding: EdgeInsets.symmetric(horizontal: <span class="hljs-number">16.</span>w, vertical: <span class="hljs-number">24.</span>h),
                  child: _buildMainContent(context),
                ),
              );
            }
          },
        ),
      ),
    );
  }
}
</code></pre>
<h3 id="heading-building-the-core-content"><strong>Building the Core Content</strong></h3>
<pre><code class="lang-dart">Widget _buildMainContent(BuildContext context) {
  <span class="hljs-keyword">return</span> Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        <span class="hljs-string">'Welcome to the Responsive App'</span>,
        style: TextStyle(fontSize: <span class="hljs-number">24.</span>sp, fontWeight: FontWeight.bold),
      ),
      SizedBox(height: <span class="hljs-number">12.</span>h),
      Text(
        <span class="hljs-string">'This UI adapts to different screen sizes automatically. Try resizing your window or changing the orientation.'</span>,
        style: TextStyle(fontSize: <span class="hljs-number">16.</span>sp),
      ),
      SizedBox(height: <span class="hljs-number">24.</span>h),
      Row(
        children: [
          Expanded(
            child: Image.asset(
              <span class="hljs-string">'assets/sample.jpg'</span>,
              width: <span class="hljs-built_in">double</span>.infinity,
              height: <span class="hljs-number">200.</span>h,
              fit: BoxFit.cover,
            ),
          ),
        ],
      ),
      SizedBox(height: <span class="hljs-number">24.</span>h),
      Row(
        children: [
          Expanded(
            child: ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: <span class="hljs-number">14.</span>h),
                textStyle: TextStyle(fontSize: <span class="hljs-number">16.</span>sp),
              ),
              child: Text(<span class="hljs-string">'Get Started'</span>),
            ),
          ),
        ],
      ),
    ],
  );
}
</code></pre>
<h2 id="heading-full-walkthrough-code-explained-in-depth">Full Walkthrough: Code Explained in Depth</h2>
<p>Let’s now go through a <strong>deep explanation</strong> of the full sample responsive Flutter screen, line by line and block by block.</p>
<h3 id="heading-imports-and-app-entry-point"><strong>Imports and App Entry Point</strong></h3>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter_screenutil/flutter_screenutil.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  runApp(MyApp());
}
</code></pre>
<h4 id="heading-explanation">Explanation:</h4>
<p><code>import 'package:flutter/material.dart';</code> brings in Flutter’s Material Design library, which contains essential UI widgets such as <code>Scaffold</code>, <code>AppBar</code>, <code>Text</code>, and <code>Column</code>. <code>import 'package:flutter_screenutil/flutter_screenutil.dart';</code> imports the <code>flutter_screenutil</code> package, providing scaling utilities like <code>.sp</code>, <code>.h</code>, <code>.w</code>, and <code>.r</code> that automatically adjust UI elements based on the device’s screen size. The <code>void main()</code> function serves as the entry point of the Flutter app, calling <code>runApp(MyApp())</code> to render the <code>MyApp</code> widget as the root of the application.</p>
<h3 id="heading-app-root-the-myapp-widget"><strong>App Root: The</strong> <code>MyApp</code> Widget</h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-comment">// Design size corresponds to the reference design (e.g., iPhone 6/7/8)</span>
  <span class="hljs-keyword">final</span> Size designSize = <span class="hljs-keyword">const</span> Size(<span class="hljs-number">360</span>, <span class="hljs-number">690</span>);

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> ScreenUtilInit(
      designSize: designSize,
      minTextAdapt: <span class="hljs-keyword">true</span>,       <span class="hljs-comment">// adapts font size</span>
      splitScreenMode: <span class="hljs-keyword">true</span>,    <span class="hljs-comment">// supports split-screen</span>
      builder: (context, child) {
        <span class="hljs-keyword">return</span> MaterialApp(
          title: <span class="hljs-string">'Responsive Flutter Example'</span>,
          home: MyHomePage(),
        );
      },
    );
  }
}
</code></pre>
<h4 id="heading-explanation-1">Explanation:</h4>
<p><code>MyApp</code> extends <code>StatelessWidget</code>, meaning it doesn’t maintain any internal state and simply builds the widget tree. The <code>designSize</code> defines the base design resolution used in your design mockups (in this case, 360x690, typical of an iPhone 8). <code>ScreenUtilInit</code> initializes the <code>flutter_screenutil</code> package, and its parameters configure how responsiveness works: <code>designSize: Size(360, 690)</code> sets the reference design size for all scaling calculations, <code>minTextAdapt: true</code> ensures text automatically scales on smaller screens without clipping, and <code>splitScreenMode: true</code> maintains proper layout behavior in split-screen mode on devices like Android tablets. Inside its builder, it returns a <code>MaterialApp</code>, which defines the app’s title and sets <code>home: MyHomePage()</code>, the main screen displayed when the app launches.</p>
<p>So far, this part sets up the environment for responsive scaling.</p>
<h3 id="heading-the-home-screen-layout-myhomepage"><strong>The Home Screen Layout</strong> <code>MyHomePage</code></h3>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyHomePage</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-keyword">const</span> MyHomePage({Key? key}) : <span class="hljs-keyword">super</span>(key: key);
</code></pre>
<p>The widget is <strong>stateless</strong>, meaning its layout does not depend on mutable state.</p>
<p>Inside <code>build(BuildContext context)</code>:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> screenWidth  = MediaQuery.of(context).size.width;
<span class="hljs-keyword">final</span> <span class="hljs-built_in">double</span> screenHeight = MediaQuery.of(context).size.height;
<span class="hljs-keyword">final</span> Orientation orientation = MediaQuery.of(context).orientation;
</code></pre>
<p>These three lines access <code>MediaQuery</code>, a Flutter API that provides information about the screen’s size, orientation, and other layout properties, where <code>screenWidth</code> represents the device’s current screen width, <code>screenHeight</code> represents its height, and <code>orientation</code> indicates whether the device is in portrait or landscape mode.</p>
<p>You’ll use these values to dynamically adjust your layout.</p>
<h3 id="heading-the-scaffold-and-appbar"><strong>The Scaffold and AppBar</strong></h3>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> Scaffold(
  appBar: AppBar(
    title: Text(
      <span class="hljs-string">'Responsive UI'</span>,
      style: TextStyle(fontSize: <span class="hljs-number">20.</span>sp),
    ),
  ),
</code></pre>
<h4 id="heading-explanation-2">Explanation:</h4>
<p><code>Scaffold</code> provides the basic structure of the page, including the app bar, body, and optional elements like a floating action button or drawer. Inside the <code>AppBar</code>, the title text uses <code>20.sp</code> instead of a fixed pixel value, <code>.sp</code> (scaled pixels) from <code>flutter_screenutil</code> ensures that the font size automatically adjusts to the screen’s pixel density and resolution, meaning a <code>20.sp</code> text appears smaller on compact screens and scales up proportionally on larger devices like tablets.</p>
<h3 id="heading-safearea-layoutbuilder"><strong>SafeArea + LayoutBuilder</strong></h3>
<pre><code class="lang-dart">body: SafeArea(
  child: LayoutBuilder(
    builder: (context, constraints) {
</code></pre>
<h4 id="heading-explanation-3">Explanation:</h4>
<p><code>SafeArea</code> ensures that content does not overlap with system UI areas such as the notch, status bar, or navigation gestures, while <code>LayoutBuilder</code> provides the constraints (maximum width and height) of the available space inside its parent, enabling the creation of responsive layouts that adapt dynamically to the screen’s actual size at runtime.</p>
<h3 id="heading-handling-large-and-small-screens"><strong>Handling Large and Small Screens</strong></h3>
<pre><code class="lang-dart"><span class="hljs-keyword">if</span> (constraints.maxWidth &gt; <span class="hljs-number">600</span>) {
  <span class="hljs-comment">// Tablet/Desktop layout</span>
  <span class="hljs-keyword">return</span> Row(
    children: [
      Expanded(
        flex: <span class="hljs-number">2</span>,
        child: Container(
          color: Colors.blueGrey[<span class="hljs-number">50</span>],
          child: Center(
            child: Text(
              <span class="hljs-string">'Sidebar Panel'</span>,
              style: TextStyle(fontSize: <span class="hljs-number">18.</span>sp),
            ),
          ),
        ),
      ),
      Expanded(
        flex: <span class="hljs-number">5</span>,
        child: Container(
          padding: EdgeInsets.all(<span class="hljs-number">16.</span>w),
          child: _buildMainContent(context),
        ),
      ),
    ],
  );
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">// Mobile layout</span>
  <span class="hljs-keyword">return</span> SingleChildScrollView(
    child: Padding(
      padding: EdgeInsets.symmetric(horizontal: <span class="hljs-number">16.</span>w, vertical: <span class="hljs-number">24.</span>h),
      child: _buildMainContent(context),
    ),
  );
}
</code></pre>
<h4 id="heading-explanation-4">Explanation:</h4>
<p>This block handles responsive breakpoints by determining how the UI adapts to different screen widths. If <code>constraints.maxWidth &gt; 600</code>, the layout is treated as a tablet or desktop view. In this case, the UI uses a <code>Row</code> to divide the screen horizontally, with <code>Expanded(flex: 2)</code> creating a sidebar that takes two parts of the available width, and <code>Expanded(flex: 5)</code> creating the main content area that takes five parts, maintaining a 2:5 ratio. The sidebar displays “Sidebar Panel,” representing where side navigation or additional panels can appear on larger screens. For smaller screens (phones), the layout switches to a vertical arrangement using <code>SingleChildScrollView</code> to enable scrolling, with padding applied through scaled spacing (<code>16.w</code> horizontally and <code>24.h</code> vertically). The <code>_buildMainContent()</code> function is reused to keep content logic consistent across all layouts.</p>
<h3 id="heading-building-the-core-content-1"><strong>Building the Core Content</strong></h3>
<pre><code class="lang-dart">Widget _buildMainContent(BuildContext context) {
  <span class="hljs-keyword">return</span> Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        <span class="hljs-string">'Welcome to the Responsive App'</span>,
        style: TextStyle(fontSize: <span class="hljs-number">24.</span>sp, fontWeight: FontWeight.bold),
      ),
      SizedBox(height: <span class="hljs-number">12.</span>h),
      Text(
        <span class="hljs-string">'This UI adapts to different screen sizes automatically. Try resizing your window or changing the orientation.'</span>,
        style: TextStyle(fontSize: <span class="hljs-number">16.</span>sp),
      ),
      SizedBox(height: <span class="hljs-number">24.</span>h),
      Row(
        children: [
          Expanded(
            child: Image.asset(
              <span class="hljs-string">'assets/sample.jpg'</span>,
              width: <span class="hljs-built_in">double</span>.infinity,
              height: <span class="hljs-number">200.</span>h,
              fit: BoxFit.cover,
            ),
          ),
        ],
      ),
      SizedBox(height: <span class="hljs-number">24.</span>h),
      Row(
        children: [
          Expanded(
            child: ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: <span class="hljs-number">14.</span>h),
                textStyle: TextStyle(fontSize: <span class="hljs-number">16.</span>sp),
              ),
              child: Text(<span class="hljs-string">'Get Started'</span>),
            ),
          ),
        ],
      ),
    ],
  );
}
</code></pre>
<h4 id="heading-explanation-5">Explanation:</h4>
<p>This method builds the actual visible content of the app. It uses a <code>Column</code> to stack widgets vertically, with <code>crossAxisAlignment: CrossAxisAlignment.start</code> aligning all child widgets to the start of the horizontal axis (left side in LTR layouts). Text widgets use <code>.sp</code> for responsive scaling, applying a larger font size (24.sp) for the title and a smaller, comfortable one (16.sp) for the body. <code>SizedBox</code> provides vertical spacing (<code>12.h</code> or <code>24.h</code>), where <code>.h</code> scales proportionally to the screen height. A <code>Row</code> containing <code>Image.asset</code> uses <code>Expanded</code> to make the image fill the available width, with <code>fit: BoxFit.cover</code> ensuring the image fills its container correctly and <code>height: 200.h</code> allowing dynamic height scaling. Another <code>Row</code> contains an <code>ElevatedButton</code> that spans the available width through <code>Expanded</code>, with padding and font size scaling via <code>EdgeInsets.symmetric(vertical: 14.h)</code> and <code>TextStyle(fontSize: 16.sp)</code>. Overall, the <code>_buildMainContent()</code> method produces a clean, scalable, and responsive layout that maintains visual consistency across all devices.</p>
<h3 id="heading-why-this-works"><strong>Why This Works</strong></h3>
<p><code>LayoutBuilder</code> gives the parent constraints, letting you conditionally render different layouts for mobile and large screens. <code>ScreenUtil</code> ensures that all elements (text, padding, sizes) adapt proportionally. <code>MediaQuery</code> can be used if you need real-time adaptation based on device rotation, size, or padding. <code>SafeArea</code> prevents clipping under system UI areas.</p>
<p>Together, these techniques combine <strong>Flutter’s native flexibility</strong> with <strong>ScreenUtil’s scaling power</strong>, making the UI dynamic, elegant, and consistent across all platforms.</p>
<h2 id="heading-key-takeaways"><strong>Key Takeaways</strong></h2>
<ol>
<li><p>Always design with a <strong>reference size</strong> and scale up/down with <code>flutter_screenutil</code>.</p>
</li>
<li><p>Use <code>LayoutBuilder</code> or <code>MediaQuery</code> for breakpoints.</p>
</li>
<li><p>Test layouts in <strong>portrait/landscape</strong> and across <strong>small/large screens</strong>.</p>
</li>
<li><p>Avoid hardcoded pixel values, use <code>.sp</code>, <code>.w</code>, <code>.h</code> instead.</p>
</li>
<li><p>Keep your content modular with helper functions like <code>_buildMainContent()</code>.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating responsive and adaptive UIs in Flutter is not just about making things “look okay” on different devices, it’s about crafting <strong>fluid, consistent</strong>, and <strong>usable</strong> experiences regardless of screen size, orientation or platform. By leveraging Flutter’s built-in layout widgets (<code>Row</code>, <code>Column</code>, <code>Flexible</code>, <code>Expanded</code>, <code>LayoutBuilder</code>), and coupling them with screen-aware APIs (<code>MediaQuery</code>, <code>SafeArea</code>, etc) and specialized packages (like <code>flutter_screenutil</code>), you can build apps that scale beautifully.</p>
<p>Key takeaways:</p>
<ul>
<li><p>Think <strong>flexibility</strong> first: avoid rigid sizing.</p>
</li>
<li><p>Use screen size/constraints to decide layout variations (instead of device type).</p>
</li>
<li><p>Scale typography, images, paddings to maintain usability and aesthetics.</p>
</li>
<li><p>Always test on multiple devices/sizes/orientations.</p>
</li>
<li><p>Encapsulate responsive logic in reusable widgets to keep your code clean and maintainable.</p>
</li>
</ul>
<p>With these practices in hand, you’ll be well-equipped to deliver visually stunning, well-performing, and truly responsive Flutter applications for 2024 and beyond.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p>“Adaptive and responsive design in Flutter”, Flutter Docs. (<a target="_blank" href="https://docs.flutter.dev/ui/adaptive-responsive">Flutter Docs</a>)</p>
</li>
<li><p>“Building Responsive UIs in Flutter: Tips and Best Practices”. TheOneTechnologies blog. (<a target="_blank" href="https://theonetechnologies.com/blog/post/building-responsive-ui-in-flutter-tips-and-best-practices">TheOneTechnologies</a>)</p>
</li>
<li><p>“Best strategy to implement responsive design : r/FlutterDev”, Reddit discussion. (<a target="_blank" href="https://www.reddit.com/r/FlutterDev/comments/192sqg5/best_strategy_to_implement_responsive_design">Reddit</a>)</p>
</li>
<li><p>“How to Make a Flutter App Responsive for Different Mobile Screen Sizes”, StackOverflow Q&amp;A. (<a target="_blank" href="https://stackoverflow.com/questions/79539122/how-to-make-a-flutter-app-responsive-for-different-mobile-screen-sizes">Stack Overflow</a>)</p>
</li>
<li><p>“5 Best Practices to Build Robust and Responsive UIs in Flutter”, Somnio Software blog. (<a target="_blank" href="https://somniosoftware.com/blog/5-best-practices-to-build-robust-and-responsive-uis-in-flutter">somniosoftware.com</a>)</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Tables Using the React-Data-Table-Component Library in React & TypeScript ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, I'll teach you how to use the react-data-table-component library in your React projects. You'll learn how to render a table that has features such as pagination, searching/filtering, and sorting.  I'll walk you through each step, fr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-tables-using-the-react-datatable-component-library/</link>
                <guid isPermaLink="false">66ba5222256e9dbeab31aa92</guid>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Losalini Rokocakau ]]>
                </dc:creator>
                <pubDate>Wed, 27 Mar 2024 15:40:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/cover-image-21.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, I'll teach you how to use the <em>react-data-table-component</em> library in your React projects. You'll learn how to render a table that has features such as pagination, searching/filtering, and sorting. </p>
<p>I'll walk you through each step, from setting up a React and TypeScript project with Vite to using the <em>react-data-table-component</em> library to render a table.</p>
<p>To follow along with this tutorial, there are a few prerequisites:</p>
<ol>
<li>A basic understanding of React and TypeScript</li>
<li>Basic knowledge of Bootstrap, which we'll use for styling</li>
<li>For Windows OS users, know how to use the PowerShell terminal (as you'll need an interactive terminal to create the project with Vite)</li>
<li>Node v20.11.1 installed</li>
<li>npm v10.2.4 installed</li>
<li>A code editor such as Visual Studio Code (VS Code) or Atom</li>
</ol>
<p>Let's start creating the project!</p>
<h3 id="heading-what-will-we-build">What will we build</h3>
<p>By the end of this tutorial, you will have built a table that displays a person's ID, name, height, and eye color.</p>
<p>The table will also have a search bar where users can search for a person based on the value of any of the four properties mentioned above.</p>
<p>Each row in the table will be selectable and each column will be sortable when the column header is clicked by a user.</p>
<h2 id="heading-1-create-a-react-and-typescript-project">1. Create a React and TypeScript Project</h2>
<p>In the command line, create the project with the command below:</p>
<pre><code>npm create vite@latest
</code></pre><p>Name the project <code>react-data-table-tutorial</code> .</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/fig-1-0-1.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 1.0</strong> Creating the React &amp; TypeScript project with Vite on PowerShell command line. Naming the project react-data-table-tutorial.</em></p>
<p>Navigate to React with the up and down keys to select React. Choose React as the library you're going to use.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/fig-1-1-1.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 1.1</strong> Selecting React as our framework by navigating to it on the list with up &amp; down arrow keys on the keyboard.</em></p>
<p>Similar to the step above, navigate to TypeScript and choose it as the language to use.</p>
<p>The next step is to change directories into the project's folder. Once you've done that, open the project in your code editor as seen in Figure 1.2 below. I'll be using the VS Code editor in this tutorial.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/fig-1-2-2.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 1.2</strong> Changing the folder into the react-data-table-tutorial directory and opening the contents of the folder into a code editor.</em></p>
<h2 id="heading-2-remove-the-default-code-set-by-react">2. Remove the Default Code Set by React</h2>
<p>This step is just to remove any default code and stylings in the files found in the <em>src</em> folder. </p>
<p>First, remove all default styling in the <em>App.css</em> file and replace it with the stylings below.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">160px</span> <span class="hljs-number">500px</span>;
}
</code></pre>
<p>Then remove the following from the <em>App.tsx</em> file:</p>
<ul>
<li>the imported statements of the <code>useState</code> hook, <code>reactLogo</code>, and the <code>viteLogo</code><em>.</em></li>
<li>The destructured array and <code>useState</code> hook for the count variable and <code>setCount</code> function.</li>
<li>The returned JSX wrapped within the fragment of the <code>App</code> component.</li>
</ul>
<p>The <code>App</code> component should look like the code-block below after all of those changes:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-3-install-the-libraries-well-need">3. Install the Libraries We'll Need</h2>
<p>In the project, you will need to install a few libraries:</p>
<ol>
<li><em>styled-components</em> v3.23+</li>
<li><em>react-data-table-component</em> v16.8.0+</li>
<li><em>Bootstrap</em> v5.3.3</li>
</ol>
<p>You will need to install the <em>styled-components</em> library to use with the <em>react-data-table-component</em> library.</p>
<p>In the command line, install all of these by using the commands below:</p>
<pre><code>npm install styled-components
</code></pre><pre><code>npm install react-data-table-components
</code></pre><pre><code>npm install bootstrap@<span class="hljs-number">5.3</span><span class="hljs-number">.3</span>
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/03/fig-3-0.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 3.0</strong> Installing the libraries on Visual Studio Code's built-in terminal.</em></p>
<p>The installed libraries are listed under dependencies in the <em>package.json</em> file.</p>
<pre><code class="lang-json">{
   <span class="hljs-comment">// rest of the code in the file</span>
    <span class="hljs-attr">"dependencies"</span>: {
        <span class="hljs-attr">"boostrap"</span>: <span class="hljs-string">"^5.3.3"</span>,
        <span class="hljs-attr">"react"</span>: <span class="hljs-string">"^18.2.0"</span>,
        <span class="hljs-attr">"react-data-table-component"</span>: <span class="hljs-string">"^7.6.2"</span>,
        <span class="hljs-attr">"react-dom"</span>: <span class="hljs-string">"^18.2.0"</span>,
        <span class="hljs-attr">"styled-components"</span>: <span class="hljs-string">"^6.1.8"</span>,
    }
    <span class="hljs-comment">// rest of the code in the file</span>
}
</code></pre>
<h2 id="heading-4-import-bootstrap-in-the-app-component">4. Import Bootstrap in the App Component</h2>
<p>In the <em>App.tsx</em> file, import the Bootstrap library at the top of the file.</p>
<p>This will allow us to use Bootstrap stylings throughout the project.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"bootstrap/dist/js/bootstrap.bundle.min.js"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>)</span>{
   <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-5-create-a-table-component">5. Create a Table Component</h2>
<p>In the <em>src</em> folder, create a sub-directory and name it <em>components</em>. By convention, this is to hold all of the components of the project.</p>
<p>In the <em>components</em> folder, create a file called <em>Table.tsx</em>. This is for our <code>Table</code> component.</p>
<p>Create a function component called <code>Table</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/fig-4-0.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 5.0</strong> Create the Table component in the components sub-directory within the src folder.</em></p>
<h2 id="heading-6-import-the-react-data-table-component-library-to-start-using-it">6. Import the react-data-table-component Library to Start Using it</h2>
<p>Import the <em>react-data-table-component</em> library into the <code>Table</code> component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> DataTable <span class="hljs-keyword">from</span> <span class="hljs-string">"react-data-table-component"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Table</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;&gt;</span><span class="hljs-tag">&lt;/&gt;</span></span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Table;
</code></pre>
<h2 id="heading-7-create-the-table-in-the-table-component">7. Create the Table in the Table Component</h2>
<p>Create a <code>&lt;div&gt;</code> container within the fragment and add Bootstrap styling of <code>container</code> and <code>my-5</code> to place the table in the center of the page.</p>
<p>Add the <code>DataTable</code> component as the child of the <code>&lt;div&gt;</code> container.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> DataTable <span class="hljs-keyword">from</span> <span class="hljs-string">'react-data-table-component'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Table</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container my-5"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">DataTable</span> /&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    );
}
</code></pre>
<p>Pass the columns and rows of the table as props to the <code>DataTable</code> component.</p>
<p>The column headers of the table are objects that will be stored in an array. The rows of the table will also be stored in a similar way. The array of objects for both will have these structures:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> columns = [
    {
        <span class="hljs-attr">name</span>: <span class="hljs-string">"ID"</span>,
        <span class="hljs-attr">selector</span>: <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row.id
    },
    {
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Full Name"</span>,
        <span class="hljs-attr">selector</span>: <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row.fullName
    },
    {
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Height"</span>,
        <span class="hljs-attr">selector</span>: <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row.height
    },
    {
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Weight"</span>,
        <span class="hljs-attr">selector</span>: <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row.weight
    },
];
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> rows = [
    {
        <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>,
        <span class="hljs-attr">fullName</span>: <span class="hljs-string">"John Doe"</span>,
        <span class="hljs-attr">height</span>: <span class="hljs-string">"1.75m"</span>,
        <span class="hljs-attr">weight</span>: <span class="hljs-string">"89kg"</span>,
    },
    {
        <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>,
        <span class="hljs-attr">fullName</span>: <span class="hljs-string">"Jane Doe"</span>,
        <span class="hljs-attr">height</span>: <span class="hljs-string">"1.64m"</span>,
        <span class="hljs-attr">weight</span>: <span class="hljs-string">"55kg"</span>,
    },
    {
        <span class="hljs-attr">id</span>: <span class="hljs-number">3</span>,
        <span class="hljs-attr">fullName</span>: <span class="hljs-string">"Sheera Maine"</span>,
        <span class="hljs-attr">height</span>: <span class="hljs-string">"1.69m"</span>,
        <span class="hljs-attr">weight</span>: <span class="hljs-string">"74kg"</span>,
    },
];
</code></pre>
<p>These constants are then passed into the <code>DataTable</code> component as such:</p>
<pre><code class="lang-jsx">&lt;DataTable columns={columns} data={rows} /&gt;
</code></pre>
<p>Create a <code>columns</code> constant that is an array of objects that has four columns headers for <code>personID</code>, <code>fullName</code>, <code>height</code>, and <code>eyeColor</code>.</p>
<p>Create a <code>rows</code> constant that is an array of objects that has 15 objects which is equivalent to data for 15 people.</p>
<p>Pass the two constants into their respective props in the <code>DataTable</code> component as seen in the next code-block.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Table</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> columns = [
        {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"ID"</span>,
            <span class="hljs-attr">selector</span>: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.personID,
        },
        {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"Full Name"</span>,
            <span class="hljs-attr">selector</span>: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.fullName,
        },
        {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"Height"</span>,
            <span class="hljs-attr">selector</span>: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.height,
        },
        {
            <span class="hljs-attr">name</span>: <span class="hljs-string">"eyeColor"</span>,
            <span class="hljs-attr">selector</span>: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.eyeColor,
        },
    ];

    <span class="hljs-keyword">const</span> rows = [
        {
           <span class="hljs-attr">personID</span>: <span class="hljs-number">1</span>,
           <span class="hljs-attr">fullName</span>: <span class="hljs-string">"Kate Shein"</span>,
           <span class="hljs-attr">height</span>: <span class="hljs-string">"1.79m"</span>,
           <span class="hljs-attr">eyeColor</span>: <span class="hljs-string">"blue"</span>,
        },
        <span class="hljs-comment">//....remaining objects for the 2nd to 14th person</span>
        {
           <span class="hljs-attr">personID</span>: <span class="hljs-number">15</span>,
           <span class="hljs-attr">fullName</span>: <span class="hljs-string">"Isabella Thompson"</span>,
           <span class="hljs-attr">height</span>: <span class="hljs-string">"1.79m"</span>,
           <span class="hljs-attr">eyeColor</span>: <span class="hljs-string">"blue"</span>,
        },
    ];
}
</code></pre>
<p>You will also add the <code>fixedHeader</code> prop to the <code>DataTable</code> component to keep the column header fixed when a user scrolls down the table that has more than 10 records.</p>
<p>Give the table a title by passing the <code>title</code> prop to the component and its value is whatever you would like to call your table.</p>
<pre><code class="lang-javascript">&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"container"</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DataTable</span> 
        <span class="hljs-attr">columns</span>=<span class="hljs-string">{columns}</span> 
        <span class="hljs-attr">data</span>=<span class="hljs-string">{rows}</span> 
        <span class="hljs-attr">fixedHeader</span>
        <span class="hljs-attr">title</span>=<span class="hljs-string">"React-Data-Table-Component Tutorial."</span>
     /&gt;</span></span>
&lt;/div&gt;
</code></pre>
<p>Back in the <code>App</code> component, import the <code>Table</code> component and place it within the fragment.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"bootstrap/dist/js/bootstrap.bundle.min.js"</span>;
<span class="hljs-keyword">import</span> Table <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Table"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">Table</span> /&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h2 id="heading-8-add-pagination-and-sorting-and-make-each-row-selectable">8. Add Pagination and Sorting, and Make Each Row Selectable</h2>
<h3 id="heading-pagination-and-selectablerows">Pagination and <code>selectableRows</code></h3>
<p>Add the <code>pagination</code> and <code>selectableRows</code> props to the <code>DataTable</code> component.</p>
<p>By default, the first page has the first 10 records. If there needs to be API calls from the server side for custom pagination, then you can use the <code>paginationServer</code> property along with the <code>paginationTotalRows</code><em>,</em> <code>onChangeRowsPerPage</code> and <code>onChangePage</code> properties which work in conjunction with a few other things.</p>
<p>But for now, let's stick with the <code>pagination</code> property.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">//...rest of the code in the function component</span>
&lt;div className=<span class="hljs-string">"container d-flex justify-content-center my-5"</span>&gt;
   <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DataTable</span>
        <span class="hljs-attr">columns</span>=<span class="hljs-string">{columns}</span>
        <span class="hljs-attr">data</span>=<span class="hljs-string">{rows}</span>
        <span class="hljs-attr">fixedHeader</span>
        <span class="hljs-attr">title</span>=<span class="hljs-string">"React-Data-Table-Component Tutorial"</span>
        <span class="hljs-attr">pagination</span>
        <span class="hljs-attr">selectableRows</span>
    /&gt;</span></span>
&lt;/div&gt;
<span class="hljs-comment">//...rest of the code in the function component</span>
</code></pre>
<h3 id="heading-sorting">Sorting</h3>
<p>In the <code>DataTable</code> component, add the <code>sortable</code> property to each object in the <code>columns</code> constant. Give it a boolean value of <code>true</code> so that sorting is applied to each column when a user clicks on the column headers.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//...rest of the code in the function component</span>
<span class="hljs-keyword">const</span> columns = [
    {
        name: <span class="hljs-string">'ID'</span>,
        selector: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.personID,
        sortable: <span class="hljs-literal">true</span>,
    },
    {
        name: <span class="hljs-string">'Full Name'</span>,
        selector: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.fullName,
        sortable: <span class="hljs-literal">true</span>,
    },
    {
        name: <span class="hljs-string">'Height'</span>,
        selector: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.height,
        sortable: <span class="hljs-literal">true</span>,
    },
    {
        name: <span class="hljs-string">'Eye Color'</span>,
        selector: <span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> row.eyeColor,
        sortable: <span class="hljs-literal">true</span>,
    },
];
<span class="hljs-comment">//...rest of the code in the function component</span>
</code></pre>
<h2 id="heading-9-add-searching-and-filtering">9. Add Searching and Filtering</h2>
<p>Add a <code>&lt;div&gt;</code> container above the <code>DataTable</code> component in the returned JSX. Also add the bootstrap <code>input-group</code> class to the <code>&lt;div&gt;</code>.</p>
<p>The child element of this new container will be the <code>&lt;input /&gt;</code> of type <code>search</code> and we'll use Bootstraps' stylings for it. Use the code below:</p>
<pre><code class="lang-jsx">&lt;input
    type=<span class="hljs-string">"search"</span>
    className=<span class="hljs-string">"form-control-sm border ps-3"</span>
    placeholder=<span class="hljs-string">"Search"</span>
/&gt;
</code></pre>
<p>An optional step is to add a search icon or use <a target="_blank" href="https://getbootstrap.com/docs/5.3/forms/input-group/">Bootstraps' default styling</a> for a search bar. However, we'll leave it out for now so we can focus solely on the searching functionality.</p>
<p>Now, let's import the <code>useState</code> hook into the <em>Table.tsx</em> file.</p>
<p>Use the hook and pass in the <code>rows</code> constant as the default value of our state variable. Within the destructured array will be our state variable called <code>data</code> and the setter function called <code>setData</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [data, setData] = useState(rows);
</code></pre>
<p>Create a function called <code>handleSearch</code> that will be called when the <code>onChange</code> event listener is used on the search bar. </p>
<p>Pass in the event object, <code>e</code>, as its argument. Using type annotation, set the type of the event object to <code>React.ChangeEvent&lt;HTMLInputElement&gt;</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleSearch = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
   <span class="hljs-comment">// the rest of the code will be defined in here</span>
};
</code></pre>
<p>Declare 5 variables of Boolean type as seen below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleSearch = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
   <span class="hljs-keyword">let</span> searchValue: <span class="hljs-built_in">Boolean</span>;
   <span class="hljs-keyword">let</span> personIDValue: <span class="hljs-built_in">Boolean</span>;
   <span class="hljs-keyword">let</span> fullNameValue: <span class="hljs-built_in">Boolean</span>;
   <span class="hljs-keyword">let</span> heightValue: <span class="hljs-built_in">Boolean</span>;
   <span class="hljs-keyword">let</span> eyeColorValue: <span class="hljs-built_in">Boolean</span>;
};
</code></pre>
<p>Declare a new local constant called <code>newRows</code><em>.</em> In this constant, filter out and return the rows/data in the <em>rows</em> constant where the search input's value is the same as the value of the rows' <code>fullName</code> or <code>height</code> or <code>eyeColor</code> property.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> newRows = rows.filter(<span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> {
      personIDValue = row.personID
        .toString()
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      fullNameValue = row.fullName
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      heightValue = row.height
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      eyeColorValue = row.eyeColor
        .toLowerCase()
        .includes(e.target.value.toLowerCase());

      <span class="hljs-keyword">if</span> (personIDValue) {
        searchValue = personIDValue;
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (fullNameValue) {
        searchValue = fullNameValue;
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (heightValue) {
        searchValue = heightValue;
      } <span class="hljs-keyword">else</span> {
        searchValue = eyeColorValue;
      }

      <span class="hljs-keyword">return</span> searchValue;
});
</code></pre>
<p>Pass <code>newRows</code> into the setter function, <code>setData</code><em>.</em></p>
<pre><code class="lang-typescript">setData(newRows);
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// definition of the columns constant</span>

<span class="hljs-comment">// definition of the rows constant</span>

<span class="hljs-keyword">const</span> [data, setData] = useState(rows);

<span class="hljs-comment">// Handle Search</span>
<span class="hljs-keyword">const</span> handleSearch = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> {
    <span class="hljs-keyword">let</span> searchValue: <span class="hljs-built_in">Boolean</span>;
    <span class="hljs-keyword">let</span> personIDValue: <span class="hljs-built_in">Boolean</span>;
    <span class="hljs-keyword">let</span> fullNameValue: <span class="hljs-built_in">Boolean</span>;
    <span class="hljs-keyword">let</span> heightValue: <span class="hljs-built_in">Boolean</span>;
    <span class="hljs-keyword">let</span> eyeColorValue: <span class="hljs-built_in">Boolean</span>;

    <span class="hljs-keyword">const</span> newRows = rows.filter(<span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> {
      personIDValue = row.personID
        .toString()
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      fullNameValue = row.fullName
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      heightValue = row.height
        .toLowerCase()
        .includes(e.target.value.toLowerCase());
      eyeColorValue = row.eyeColor
        .toLowerCase()
        .includes(e.target.value.toLowerCase());

      <span class="hljs-keyword">if</span> (personIDValue) {
        searchValue = personIDValue;
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (fullNameValue) {
        searchValue = fullNameValue;
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (heightValue) {
        searchValue = heightValue;
      } <span class="hljs-keyword">else</span> {
        searchValue = eyeColorValue;
      }

      <span class="hljs-keyword">return</span> searchValue;
    });

    setData(newRows);
};
</code></pre>
<p>Next step is to pass (not call the function) the <code>handleSearch</code> function into the <code>onChange</code> event listener. </p>
<pre><code class="lang-jsx">&lt;input
   type=<span class="hljs-string">"search"</span>
   className=<span class="hljs-string">"form-control-sm border ps-3"</span>
   placeholder=<span class="hljs-string">"Search"</span>
   onChange={handleSearch}
/&gt;
</code></pre>
<p>The value of the data property in the <code>DataTable</code> component will now be the state variable, <code>data</code> instead of <code>rows</code> that was initially passed in.</p>
<pre><code class="lang-jsx">&lt;DataTable
    columns={columns}
    data={data}
    fixedHeader
    title=<span class="hljs-string">"React-Data-Table-Component Tutorial."</span>
    pagination
    selectableRows
/&gt;
</code></pre>
<h2 id="heading-10-view-the-table">10. View the Table</h2>
<p>Run the project in the command line and view the table in a browser:</p>
<p><code>command line
npm run dev</code></p>
<p>The table should look something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/final-result-table.png" alt="Image" width="600" height="400" loading="lazy">
<em><strong>Figure 11.0</strong> Preview of the resulting table created.</em></p>
<p>You can read through the <a target="_blank" href="https://react-data-table-component.netlify.app/?path=/docs/getting-started-intro--docs">documentation</a> for <em>react-data-table-component</em> as it has more in-depth coverage of the library's usage that goes beyond the scope of this article. </p>
<p>Don't fret if there's a difference in your table from the expected result. The source code in <a target="_blank" href="https://github.com/chelmerrox/react-data-table-tutorial">this repository</a> will guide you.</p>
<p>Happy coding and may your code run smoothly!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Shadcn with Next.js 14 ]]>
                </title>
                <description>
                    <![CDATA[ Shadcn is a collection of beautifully designed, accessible, and customizable React components that you can use to build modern web applications with Next.js. With Shadcn, you can quickly and easily create user interfaces that are both stylish and fun... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/shadcn-with-next-js-14/</link>
                <guid isPermaLink="false">66bb882d6b3bd8d6bf25ae2f</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sahil ]]>
                </dc:creator>
                <pubDate>Thu, 01 Feb 2024 15:21:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Neon-Green-Bold-Quote-Motivational-Tweet-Instagram-Post-3-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Shadcn is a collection of beautifully designed, accessible, and customizable React components that you can use to build modern web applications with Next.js. With Shadcn, you can quickly and easily create user interfaces that are both stylish and functional.</p>
<p>If you are looking for a way to build modern, stylish, and accessible web applications with Next.js, then Shadcn is a great option.</p>
<p>In this guide, you'll learn how to install and use Shadcn in your Next.js projects, how to style elements, and how to customize Shadcn components.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-shadcn">What is Shadcn?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-nextjs-and-shadcn">How to Install Next.js and Shadcn</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-nextjs">How to Install Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-shadcn">How to Install Shadcn</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-shadcn-in-nextjs">How to Use Shadcn in Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-style-a-button-the-hard-way-and-the-shadcn-way">How to Style a Button The Hard Way and The Shadcn Way</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-a-component-from-shadcn">How to Add a Component from Shadcn</a></li>
<li><a class="post-section-overview" href="#heading-styling-a-button-the-hard-way-using-tailwind">Styling a Button The Hard Way</a></li>
<li><a class="post-section-overview" href="#heading-styling-a-button-the-shadcn-way">Styling a Button The Shadcn Way</a></li>
<li><a class="post-section-overview" href="#heading-how-to-customize-shadcn-components">How to Customize Shadcn Components</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-what-is-shadcn">What is Shadcn?</h2>
<p>Shadcn UI is not specifically a component library or UI framework. As stated in the documentation, it is described as "a compilation of reusable components that can be easily copied and pasted into our applications."</p>
<p>Shadcn leverages Tailwind CSS and Radix UI as its foundation. It presently offers compatibility with Next.js, Gatsby, Remix, Astro, Laravel, and Vite. There's a <a target="_blank" href="https://ui.shadcn.com/docs/installation/manual">manual integration guide</a> that can help you incorporate it with other technologies.</p>
<p>You can get the complete source code for this tutorial <a target="_blank" href="https://github.com/dotslashbit/fcc-article-resources/tree/main/nextjs-shadcn/my-app">here</a>.</p>
<h2 id="heading-how-to-install-nextjs-and-shadcn">How to Install Next.js and Shadcn</h2>
<p>I'll follow the instructions in the <a target="_blank" href="https://ui.shadcn.com/docs/installation">Shadcn docs</a>, so you can follow along if you like.</p>
<p>First, you have to choose what framework you are currently using. This article and the code in my repo above was written with Next.js in mind, so select the Next.js option.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-5.23.21-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Choose a framework to set up Shadcn</em></p>
<p>Now, you need to install and configure a new Next.js project.</p>
<h3 id="heading-how-to-install-nextjs">How to Install Next.js</h3>
<p>I typically use npm, but if you use a different package manager, feel free to copy a command for your respective package manager below.</p>
<p>To install Next.js with npm:</p>
<pre><code class="lang-bash">npx create-next-app@latest my-app --typescript --tailwind --eslint
</code></pre>
<p>To install Next.js with yarn:</p>
<pre><code class="lang-bash">yarn create next-app@latest my-app --typescript --tailwind --eslint
</code></pre>
<p>To install Next.js with pnpm:</p>
<pre><code class="lang-bash">pnpm create next-app@latest my-app --typescript --tailwind --eslint
</code></pre>
<p>To install Next.js with bun:</p>
<pre><code class="lang-bash">bunx --bun create-next-app@latest my-app --typescript --tailwind --eslint
</code></pre>
<p>After running one of those commands, you'll see the following configuration questions:</p>
<pre><code class="lang-bash">Need to install the following packages:
create-next-app@14.1.0
Ok to proceed? (y) y
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import <span class="hljs-built_in">alias</span> (@/*)? … No / Yes
</code></pre>
<p>I selected "No" for the first question as I don't want to have a <code>src</code> directory inside my <code>app</code> directory.</p>
<p>I selected "Yes" for the second question because I want to use app router, which is the recommended way to go for Next.js 14.</p>
<p>Finally, I selected "No" for the third question because I like the way import works with <code>@</code> in Next.js 14.</p>
<h3 id="heading-how-to-install-shadcn">How to Install Shadcn</h3>
<p>Before continuing, remember to go into the <code>my-app</code> directory in your terminal:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-app
</code></pre>
<p>To initialize Shadcn with npm:</p>
<pre><code class="lang-bash">npx shadcn-ui@latest init
</code></pre>
<p>To initialize Shadcn with yarn:</p>
<pre><code class="lang-bash">npx shadcn-ui@latest init
</code></pre>
<p>To initialize Shadcn with pnpm:</p>
<pre><code class="lang-bash">pnpm dlx shadcn-ui@latest init
</code></pre>
<p>To initialize Shadcn with bun:</p>
<pre><code class="lang-bash">bunx --bun shadcn-ui@latest init
</code></pre>
<p>After running one of those commands you'll see some configuration questions again, but this time they're for Shadcn:</p>
<pre><code class="lang-bash">✔ Which style would you like to use? › New York
✔ Which color would you like to use as base color? › Slate
✔ Would you like to use CSS variables <span class="hljs-keyword">for</span> colors? › yes
</code></pre>
<p>You can see the questions and my responses in the code above.</p>
<p>For the first question, there are two options, <code>default</code> and <code>New York</code>. I chose the "New York" style.</p>
<p>For the second question, there are five options <code>Slate</code>, <code>Gray</code>, <code>Zinc</code>, <code>Neutral</code>, <code>Stone</code>. I chose <code>Slate</code> because I like a minimalist black and white styling.</p>
<p>For the third question, I there are two options <code>Yes</code> and <code>No</code>. I chose yes, as I prefer to have CSS variables enabled for styling (although we won't be using this in this article).</p>
<p>You can read more about the <code>style</code> options, <code>base</code> options, and this configuration setup <a target="_blank" href="https://ui.shadcn.com/docs/components-json">here</a>.</p>
<p>And with that, you've set up a new Next.js 14 project with Shadcn.</p>
<p>Now you can see two new directories that have been added to your project directory, <code>components</code> and <code>lib</code>.</p>
<p>Notice that the <code>components</code> directory is currently empty, which means that Shadcn doesn't load up your project with any components you might not use. This gives you a lot of flexibility and keeps your project lightweight (so you can just add the components you need).</p>
<h2 id="heading-how-to-use-shadcn-in-nextjs">How to Use Shadcn in Next.js</h2>
<p>First, let's remove all the boilerplate code from the <code>my-app</code> project's <code>page.tsx</code> file. (You only need to remove the boilerplate code from <code>my-app/page.tsx</code>.)</p>
<p>After removing all the boilerplate code from the <code>page.tsx</code> file inside the <code>app</code> directory, I'll add a simple hello world text.</p>
<p>Here's the updated <code>page.tsx</code> code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello World&lt;/h1&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p>And here's what your project should look like in the browser:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-30-at-5.49.27-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Hello World setup for the Next.js project</em></p>
<h3 id="heading-how-to-style-a-button-the-hard-way-and-the-shadcn-way">How to Style a Button the Hard Way and the Shadcn Way</h3>
<p>Now, lets add two buttons to the page. One button will be simple with no styling, and the other will be a component from Shadcn.</p>
<h4 id="heading-how-to-add-a-component-from-shadcn">How To Add a Component from Shadcn</h4>
<p>Before we add our buttons, though, we have to learn how to add a component from Shadcn into our project. Here's how to do that:</p>
<ol>
<li>Go to the <a target="_blank" href="https://ui.shadcn.com/docs">Shadcn docs</a>.</li>
<li>Click on whatever component you want to use.</li>
<li>Then you'll see the command you should run to add that component to your project.</li>
<li>Finally, import that component into your project and start using it.</li>
</ol>
<p>To add the <code>Button</code> component from Shadcn, follow these steps:</p>
<ol>
<li>Go to the <a target="_blank" href="https://ui.shadcn.com/docs/components/button">Button component page</a>.</li>
<li>Then run this command in your terminal: <code>npx shadcn-ui@latest add button</code>. Note: This command is for npm – if you use a different package manager, you'll need to modify the command slightly.</li>
<li>Finally, import the button component in the file where you want to use it.</li>
</ol>
<p>Now we'll see examples of adding a button in two different ways: using Tailwind CSS, and using Shadcn.</p>
<h3 id="heading-styling-a-button-the-hard-way-using-tailwind">Styling a Button the Hard Way (Using Tailwind)</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;button className=<span class="hljs-string">"p-2 bg-orange-400"</span>&gt;Click me&lt;/button&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-31-at-9.11.58-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Custom button with orange background</em></p>
<p>I have created a button with an orange background having a padding of 2 units. You can see that it looks ugly and also doesn't have any hover effects by default.</p>
<h3 id="heading-styling-a-button-the-shadcn-way">Styling a Button the Shadcn Way</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/button"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;Button variant=<span class="hljs-string">"outline"</span>&gt;Button&lt;/Button&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-31-at-9.16.22-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Shadcn default button</em></p>
<p>To use the Shadcn button, first import the <code>Button</code> component to the file where you want to use it – you don't need to add any styling to it (You can customize it, which you'll learn how to do in the next section). By default it looks good and it has hover effects. So just import the component and then you can use it.</p>
<p>You can also play around with the different options that Shadcn components give you. Either go to the docs page of that component and take a look at the options or open up the component's source code that is inside the <code>components/ui</code> directory.</p>
<p>Let's take a look at <code>button.tsx</code> source code to check how many variants of buttons you can create.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Slot } <span class="hljs-keyword">from</span> <span class="hljs-string">"@radix-ui/react-slot"</span>;
<span class="hljs-keyword">import</span> { cva, <span class="hljs-keyword">type</span> VariantProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"class-variance-authority"</span>;

<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>;

<span class="hljs-keyword">const</span> buttonVariants = cva(
  <span class="hljs-string">"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"</span>,
  {
    variants: {
      variant: {
        <span class="hljs-keyword">default</span>:
          <span class="hljs-string">"bg-primary text-primary-foreground shadow hover:bg-primary/90"</span>,
        destructive:
          <span class="hljs-string">"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90"</span>,
        outline:
          <span class="hljs-string">"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground"</span>,
        secondary:
          <span class="hljs-string">"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80"</span>,
        ghost: <span class="hljs-string">"hover:bg-accent hover:text-accent-foreground"</span>,
        link: <span class="hljs-string">"text-primary underline-offset-4 hover:underline"</span>,
      },
      size: {
        <span class="hljs-keyword">default</span>: <span class="hljs-string">"h-9 px-4 py-2"</span>,
        sm: <span class="hljs-string">"h-8 rounded-md px-3 text-xs"</span>,
        lg: <span class="hljs-string">"h-10 rounded-md px-8"</span>,
        icon: <span class="hljs-string">"h-9 w-9"</span>,
      },
    },
    defaultVariants: {
      variant: <span class="hljs-string">"default"</span>,
      size: <span class="hljs-string">"default"</span>,
    },
  }
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ButtonProps
  <span class="hljs-keyword">extends</span> React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;,
    VariantProps&lt;typeof buttonVariants&gt; {
  asChild?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">const</span> Button = React.forwardRef&lt;HTMLButtonElement, ButtonProps&gt;(
  <span class="hljs-function">(<span class="hljs-params">{ className, variant, size, asChild = <span class="hljs-literal">false</span>, ...props }, ref</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> Comp = asChild ? Slot : <span class="hljs-string">"button"</span>;
    <span class="hljs-keyword">return</span> (
      &lt;Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      /&gt;
    );
  }
);
Button.displayName = <span class="hljs-string">"Button"</span>;

<span class="hljs-keyword">export</span> { Button, buttonVariants };
</code></pre>
<p>You can see that there's a <code>variants</code> object, and inside that there are multiple variants to choose from. Notice that there's a <code>size</code> object that allows you to choose different sizes for the button as well.</p>
<p>You can also see that there's a <code>defaultVariants</code> object that stores the default variant and the default size of the button.</p>
<h2 id="heading-how-to-customize-shadcn-components">How to Customize Shadcn Components</h2>
<p>Remember that after installing Shadcn, the <code>components</code> directory was empty. But after adding the <code>Button</code> component, you can see that there's a <code>ui</code> directory inside <code>components</code> directory. And inside the <code>ui</code> directory you'll have the <code>Button.tsx</code> file, which is the code for the <code>Button</code> component.</p>
<p>If you open the <code>Button.tsx</code> file, you'll see that there are multiple variants of the <code>Button</code> component like <code>default</code>, <code>destructive</code>, <code>outline</code>, <code>secondary</code>, <code>ghost</code>, and <code>link</code>. There's also a default size, and other sizes to choose from:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> { Slot } <span class="hljs-keyword">from</span> <span class="hljs-string">"@radix-ui/react-slot"</span>
<span class="hljs-keyword">import</span> { cva, <span class="hljs-keyword">type</span> VariantProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"class-variance-authority"</span>

<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>

<span class="hljs-keyword">const</span> buttonVariants = cva(
  <span class="hljs-string">"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"</span>,
  {
    variants: {
      variant: {
        <span class="hljs-keyword">default</span>:
          <span class="hljs-string">"bg-primary text-primary-foreground shadow hover:bg-primary/90"</span>,
        destructive:
          <span class="hljs-string">"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90"</span>,
        outline:
          <span class="hljs-string">"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground"</span>,
        secondary:
          <span class="hljs-string">"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80"</span>,
        ghost: <span class="hljs-string">"hover:bg-accent hover:text-accent-foreground"</span>,
        link: <span class="hljs-string">"text-primary underline-offset-4 hover:underline"</span>,
      },
      size: {
        <span class="hljs-keyword">default</span>: <span class="hljs-string">"h-9 px-4 py-2"</span>,
        sm: <span class="hljs-string">"h-8 rounded-md px-3 text-xs"</span>,
        lg: <span class="hljs-string">"h-10 rounded-md px-8"</span>,
        icon: <span class="hljs-string">"h-9 w-9"</span>,
      },
    },
    defaultVariants: {
      variant: <span class="hljs-string">"default"</span>,
      size: <span class="hljs-string">"default"</span>,
    },
  }
)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ButtonProps
  <span class="hljs-keyword">extends</span> React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;,
    VariantProps&lt;typeof buttonVariants&gt; {
  asChild?: <span class="hljs-built_in">boolean</span>
}

<span class="hljs-keyword">const</span> Button = React.forwardRef&lt;HTMLButtonElement, ButtonProps&gt;(
  <span class="hljs-function">(<span class="hljs-params">{ className, variant, size, asChild = <span class="hljs-literal">false</span>, ...props }, ref</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> Comp = asChild ? Slot : <span class="hljs-string">"button"</span>
    <span class="hljs-keyword">return</span> (
      &lt;Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      /&gt;
    )
  }
)
Button.displayName = <span class="hljs-string">"Button"</span>

<span class="hljs-keyword">export</span> { Button, buttonVariants }
</code></pre>
<p>One of the best parts about Shadcn components is that you can customize them and remove variants that you won't use. You can also add your own variants to the component.</p>
<p>For example, say that you want to add the custom button we coded first as a Shadcn <code>Button</code> component variant. You can do that like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Slot } <span class="hljs-keyword">from</span> <span class="hljs-string">"@radix-ui/react-slot"</span>;
<span class="hljs-keyword">import</span> { cva, <span class="hljs-keyword">type</span> VariantProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"class-variance-authority"</span>;

<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>;

<span class="hljs-keyword">const</span> buttonVariants = cva(
  <span class="hljs-string">"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"</span>,
  {
    variants: {
      variant: {
        <span class="hljs-keyword">default</span>:
          <span class="hljs-string">"bg-primary text-primary-foreground shadow hover:bg-primary/90"</span>,
        destructive:
          <span class="hljs-string">"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90"</span>,
        outline:
          <span class="hljs-string">"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground"</span>,
        secondary:
          <span class="hljs-string">"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80"</span>,
        ghost: <span class="hljs-string">"hover:bg-accent hover:text-accent-foreground"</span>,
        link: <span class="hljs-string">"text-primary underline-offset-4 hover:underline"</span>,
        myButton: <span class="hljs-string">"p-2 bg-orange-400"</span>,
      },
      size: {
        <span class="hljs-keyword">default</span>: <span class="hljs-string">"h-9 px-4 py-2"</span>,
        sm: <span class="hljs-string">"h-8 rounded-md px-3 text-xs"</span>,
        lg: <span class="hljs-string">"h-10 rounded-md px-8"</span>,
        icon: <span class="hljs-string">"h-9 w-9"</span>,
      },
    },
    defaultVariants: {
      variant: <span class="hljs-string">"default"</span>,
      size: <span class="hljs-string">"default"</span>,
    },
  }
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ButtonProps
  <span class="hljs-keyword">extends</span> React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;,
    VariantProps&lt;typeof buttonVariants&gt; {
  asChild?: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">const</span> Button = React.forwardRef&lt;HTMLButtonElement, ButtonProps&gt;(
  <span class="hljs-function">(<span class="hljs-params">{ className, variant, size, asChild = <span class="hljs-literal">false</span>, ...props }, ref</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> Comp = asChild ? Slot : <span class="hljs-string">"button"</span>;
    <span class="hljs-keyword">return</span> (
      &lt;Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      /&gt;
    );
  }
);
Button.displayName = <span class="hljs-string">"Button"</span>;

<span class="hljs-keyword">export</span> { Button, buttonVariants };
</code></pre>
<p>Notice, I have added a new variant <code>myButton</code> and then simply pasted the styling that we used for the custom button. That's it and now you have your own custom button inside the Shadcn <code>Button</code> component.</p>
<p>Notice that the code now includes a new variant named <code>myButton</code>, and I simply pasted in the styling for the custom button from our ealier example. And that's it! Now you have your own custom Shadcn <code>Button</code> component variant.</p>
<p>Here's how you can use the custom <code>myButton</code> variant in your project:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/button"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"flex justify-center items-center flex-col gap-10"</span>&gt;
      &lt;Button variant=<span class="hljs-string">"outline"</span> size=<span class="hljs-string">"sm"</span>&gt;
        sm button
      &lt;/Button&gt;
      &lt;Button variant=<span class="hljs-string">"destructive"</span> size=<span class="hljs-string">"lg"</span>&gt;
        large button
      &lt;/Button&gt;
      &lt;Button variant=<span class="hljs-string">"ghost"</span> size=<span class="hljs-string">"lg"</span>&gt;
        ghost button
      &lt;/Button&gt;
      &lt;Button variant=<span class="hljs-string">"link"</span> size=<span class="hljs-string">"lg"</span>&gt;
        link button
      &lt;/Button&gt;
      &lt;Button variant=<span class="hljs-string">"myButton"</span>&gt;My Button&lt;/Button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-31-at-9.33.59-AM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Shadcn multiple variants buttons</em></p>
<p>Note that I've added more variants of the <code>Button</code> component, just to show you that you can create multiple types of buttons with different sizes very easily.</p>
<p>The last button in the example above has the variant <code>myButton</code>, which is the custom variant you added to the <code>Button.tsx</code> file. But that just affects the styling, and you can add your own custom size if you'd like.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide you learned how to integrate Shadcn in your Next.js projects. You can dive further into Shadcn's extensive component library and use them as-is, or you can customize them to your heart's content. The power to create your next app awaits – go build it!</p>
<p>If you have any feedback on this article, then please DM me on <a target="_blank" href="https://twitter.com/introvertedbot">Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add Custom Buttons to a Date Picker in Flatpickr ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever had to use a date picker in a web frontend project, chances are that you've used flatpickr.  For the unacquainted, flatpickr is one of the most popular date picker libraries in the open source-verse. It's framework-agnostic, highly cus... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-custom-buttons-to-flatpickr/</link>
                <guid isPermaLink="false">66ba2c9bab41bfc0b9b131da</guid>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Idris Abdul-Lateef ]]>
                </dc:creator>
                <pubDate>Wed, 17 Jan 2024 16:34:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/b01117c062c14a0d7f54f256baf09808482b6505_2_1024x512.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever had to use a date picker in a web frontend project, chances are that you've used flatpickr. </p>
<p>For the unacquainted, <a target="_blank" href="https://flatpickr.js.org/">flatpickr</a> is one of the most popular date picker libraries in the open source-verse. It's framework-agnostic, highly customizable, and lightweight.</p>
<p>I recently had to use it in a Next.js codebase and there was a peculiar use case I had. The built-in behavior is that dates are applied immediately when selected, and thereafter the date picker disappears. </p>
<p>What I really wanted, however, was to be able to pick dates and not have the modal disappear the moment I did so. I wanted to be able to keep picking dates and only have it applied when I clicked on an "Apply" button. I also wanted a "Clear" button to clear applied dates.</p>
<p>Ordinarily, flatpickr's API has methods you can work with to get these functionalities, but you won't get buttons on the date picker itself. </p>
<p>Even while there is a <a target="_blank" href="https://flatpickr.js.org/plugins/#confirmdate">plugin</a> that adds a button to the date picker to manually apply dates, it doesn't work for date ranges (which was what I needed it for) and the button's look didn't really blend with the overall theme of the app I was building. </p>
<p>I had no other option but to come up with my own solution. But how do you get your own buttons to appear on flatpickr's date picker?</p>
<h2 id="heading-how-to-set-up-flatpickr">How to Set Up flatpickr</h2>
<p>First things first, install the package:</p>
<pre><code class="lang-bash">npm install flatpickr
</code></pre>
<p>Next, you need a <code>DatePicker</code> component that will encapsulate the custom date picker and make it reusable. This component should have an <code>onChange</code> prop which will be passed a callback that will be called whenever a date is applied:</p>
<pre><code class="lang-tsx">// DatePicker.tsx

const DatePicker: React.FC&lt;DatePickerProps&gt; = ({ onChange }) =&gt; {
  return &lt;div&gt;&lt;/div&gt;
}

export default DatePicker

interface DatePickerProps {
  onChange: (date: Date[]) =&gt; void
}
</code></pre>
<p>Next, you'll need to set up flatpickr itself. You're going to bring in some exports from the flatpickr package. In the markup, you'll add a text input element, which will be the date picker input, and pass it an element ref variable that'll be used to instantiate the flatpickr instance with a couple of configuration options:</p>
<pre><code class="lang-tsx">// DatePicker.tsx

import { ElementRef, useEffect, useRef, useState } from "react"
import flatpickr from "flatpickr"

import { Instance as Flatpickr } from "flatpickr/dist/types/instance"
import "flatpickr/dist/flatpickr.min.css"

const DatePicker: React.FC&lt;DatePickerProps&gt; = ({ onChange }) =&gt; {
  const [flatpickrInstance, setFlatpickrInstance] = useState&lt;Flatpickr&gt;()
  const datePickerRef = useRef&lt;ElementRef&lt;"input"&gt;&gt;(null)

  useEffect(() =&gt; {
    if (datePickerRef.current) {
      const flatpickrInstance = flatpickr(datePickerRef.current, {
        static: true,
        closeOnSelect: false,
      })

      setFlatpickrInstance(flatpickrInstance)
    }

    return () =&gt; flatpickrInstance?.destroy()
  }, [])

  return (
    &lt;div&gt;
      &lt;input ref={datePickerRef} type="text" placeholder="Select date..." /&gt;
    &lt;/div&gt;
  )
}
...
</code></pre>
<p>For the configuration options, <code>static</code> is set to true so the date picker modal is anchored to the date picker input and <code>closeOnSelect</code> is false so the date picker modal doesn't disappear when a date is selected. </p>
<p>This is how the date picker looks so far:
<img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-14-153953.png" alt="Screenshot-2024-01-14-153953" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-the-buttons">How to Add the Buttons</h2>
<p>We've arrive at the main attraction of the day. From the snippet below, you'll notice two new imports: <code>createPortal</code> from react-dom and a pre-styled <code>Button</code> component. <code>createPortal</code> is going to play a very special role as you'll see.</p>
<pre><code class="lang-tsx">// DatePicker.tsx

import { ElementRef, useEffect, useRef, useState } from "react"
import { createPortal } from "react-dom"
import flatpickr from "flatpickr"
import Button from "./Button"

import { Instance as Flatpickr } from "flatpickr/dist/types/instance"
import "flatpickr/dist/flatpickr.min.css"

...
</code></pre>
<p>Moving on to the body of the <code>DatePicker</code> component, you'll notice some changes too. </p>
<p>There's <code>dates</code> which will store selected date(s) (or an empty array if no date is selected) and <code>applyDate</code> which will hold the callback to call when the 'Apply' button is clicked. </p>
<p>In the configuration object, you'll find two flatpickr hooks: <code>onChange</code> that gets triggered whenever a date is picked and receives an array of the currently selected dates, and <code>onClose</code> that gets triggered when the date picker modal is closed. </p>
<p>Within the body of these hooks are implementations that specify the date picker's behavior.</p>
<pre><code class="lang-tsx">// DatePicker.tsx

...
const DatePicker: React.FC&lt;DatePickerProps&gt; = ({ onChange }) =&gt; {
  const [flatpickrInstance, setFlatpickrInstance] = useState&lt;Flatpickr&gt;()
  const datePickerRef = useRef&lt;ElementRef&lt;"input"&gt;&gt;(null)
  const dates = useRef&lt;Date[]&gt;([])
  const [applyDate, setApplyDate] = useState(() =&gt; () =&gt; {})

  useEffect(() =&gt; {
    if (datePickerRef.current) {
      const flatpickrInstance = flatpickr(datePickerRef.current, {
        static: true,
        closeOnSelect: false,
        onChange: (selectedDates) =&gt; {
          if (selectedDates.length === 0) {
            onChange([])
            dates.current = []
          }

          setApplyDate(() =&gt; {
            return () =&gt; {
              dates.current = selectedDates
              onChange(selectedDates)
              flatpickrInstance.close()
            }
          })
        },
        onClose: () =&gt; {
          flatpickrInstance.setDate(dates.current)
        },
      })

      setFlatpickrInstance(flatpickrInstance)
    }

    return () =&gt; flatpickrInstance?.destroy()
  }, [])


  return (
    &lt;div&gt;  
...
</code></pre>
<p>We've gotten to the part where you finally put the custom buttons inside the date picker. This is where <code>createPortal</code> comes to shine. </p>
<p>The <code>flatpickrInstance</code> has a property, <code>calendarContainer</code>, which holds a reference to the date picker's containing <code>div</code> element. This is where you'll project the custom buttons, just beneath the calendar part of the date picker, using <a target="_blank" href="https://react.dev/reference/react-dom/createPortal">react portal</a>.</p>
<pre><code class="lang-tsx">// DatePicker.tsx

...
  return (
    &lt;div&gt;
      &lt;input ref={datePickerRef} type="text" placeholder="Select date..." /&gt;
      {flatpickrInstance &amp;&amp;
        createPortal(
          &lt;div className="flex justify-center gap-3 py-2"&gt;
            &lt;Button
              text="Clear"
              bgColor="#F8F8F8"
              textColor="#292A2E"
              onClick={() =&gt; {
                flatpickrInstance.clear(true)
                flatpickrInstance.close()
              }}
            /&gt;
            &lt;Button
              text="Apply"
              bgColor="#569ff7"
              textColor="#FFF"
              onClick={applyDate}
            /&gt;
          &lt;/div&gt;,
          flatpickrInstance.calendarContainer
        )}
    &lt;/div&gt;
  )
}

export default DatePicker
...
</code></pre>
<p>Put together, this is the final code output:</p>
<pre><code class="lang-tsx">// DatePicker.tsx

import { ElementRef, useEffect, useRef, useState } from "react"
import { createPortal } from "react-dom"
import flatpickr from "flatpickr"
import Button from "./Button"

import { Instance as Flatpickr } from "flatpickr/dist/types/instance"
import "flatpickr/dist/flatpickr.min.css"

const DatePicker: React.FC&lt;DatePickerProps&gt; = ({ onChange }) =&gt; {
  const [flatpickrInstance, setFlatpickrInstance] = useState&lt;Flatpickr&gt;()
  const datePickerRef = useRef&lt;ElementRef&lt;"input"&gt;&gt;(null)
  const dates = useRef&lt;Date[]&gt;([])
  const [applyDate, setApplyDate] = useState(() =&gt; () =&gt; {})

  useEffect(() =&gt; {
    if (datePickerRef.current) {
      const flatpickrInstance = flatpickr(datePickerRef.current, {
        static: true,
        closeOnSelect: false,
        onChange: (selectedDates) =&gt; {
          if (selectedDates.length === 0) {
            onChange([])
            dates.current = []
          }

          setApplyDate(() =&gt; {
            return () =&gt; {
              dates.current = selectedDates
              onChange(selectedDates)
              flatpickrInstance.close()
            }
          })
        },
        onClose: () =&gt; {
          flatpickrInstance.setDate(dates.current)
        },
      })

      setFlatpickrInstance(flatpickrInstance)
    }

    return () =&gt; flatpickrInstance?.destroy()
  }, [])

  return (
    &lt;div&gt;
      &lt;input ref={datePickerRef} type="text" placeholder="Select date..." /&gt;
      {flatpickrInstance &amp;&amp;
        createPortal(
          &lt;div className="flex justify-center gap-3 py-2"&gt;
            &lt;Button
              text="Clear"
              bgColor="#F8F8F8"
              textColor="#292A2E"
              onClick={() =&gt; {
                flatpickrInstance.clear(true)
                flatpickrInstance.close()
              }}
            /&gt;
            &lt;Button
              text="Apply"
              bgColor="#569ff7"
              textColor="#FFF"
              onClick={applyDate}
            /&gt;
          &lt;/div&gt;,
          flatpickrInstance.calendarContainer
        )}
    &lt;/div&gt;
  )
}

export default DatePicker

interface DatePickerProps {
  onChange: (date: Date[]) =&gt; void
}
</code></pre>
<p>And this is how the date picker now looks with the custom buttons:
<img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-16-085906.png" alt="Screenshot-2024-01-16-085906" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The date picker is now fully functional and can work for all <code>mode</code> options. There are tons of potential customizations you could possibly do using this technique. Hopefully, this article has done a good job of showing how flatpickr customization can be achieved.</p>
<h3 id="heading-enjoyed-this-article">❤️Enjoyed this article?</h3>
<p><strong>See more of my content!</strong> You can find more articles on my <a target="_blank" href="https://blog.eedris.dev">blog</a>.</p>
<p><strong>Want to connect?</strong> Link up with me on <a target="_blank" href="https://twitter.com/eedrxs">Twitter</a> or <a target="_blank" href="https://linkedin.com/in/eedris">LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Un-Styled UI Components – Guide for Front-End Developers ]]>
                </title>
                <description>
                    <![CDATA[ Imagine this situation: you are working on a time-sensitive project, and you need to implement some specific UI components that comply with accessibility best practices.  You may know about some libraries that are full of pre-designed UI elements, ea... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/unstyled-ui-components-for-front-end-developers/</link>
                <guid isPermaLink="false">66ba0d5ff602a81788fe2182</guid>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chinenye Anikwenze ]]>
                </dc:creator>
                <pubDate>Tue, 29 Aug 2023 15:56:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/article---unstyled-ui-components-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine this situation: you are working on a time-sensitive project, and you need to implement some specific UI components that comply with accessibility best practices. </p>
<p>You may know about some libraries that are full of pre-designed UI elements, each with their own style. But here's the catch – you might only need a handful of these components. So why make your application bulkier? In reality, a significant portion of the bundled styles might go unused. </p>
<p>Well don't worry – that's where <strong>un-styled</strong> UI components come in.</p>
<h2 id="heading-what-are-un-styled-ui-components">What are Un-styled UI Components?</h2>
<p>Un-styled UI components are accessible UI building blocks without pre-set styles. This grants you unmatched authority over the visual presentation and behavior of these interfaces. </p>
<p>The term "<strong>un-styled</strong>" doesn't signify a lack of design – rather, it refers to a blank slate that lets you infuse your creativity and branding without the limitations that come with traditional UI elements. </p>
<p>This enables you to tailor the components to your application's distinct needs, while also prioritizing aesthetics and accessibility.</p>
<h2 id="heading-benefits-of-un-styled-ui-components">Benefits of Un-styled UI Components</h2>
<p>Let's delve into the key features that set un-styled components apart and explore why they may be a must-have for your upcoming projects.</p>
<h3 id="heading-control-over-visual-appearance-and-functionality">Control Over Visual Appearance and Functionality:</h3>
<p>Conventional UI libraries have preset styles that might not align with your design or aesthetics. </p>
<p>Let's use this scenario, for instance: you're working on an e-commerce platform that needs a product catalog. With unstyled UI components, you can tweak each product card's look, hover effects, and interactions to mirror your brand's identity. </p>
<p>This freedom extends to colors, typography, and layout choices, resulting in a coherent, captivating experience unique to your app.</p>
<h3 id="heading-reduced-overhead-and-enhanced-performance">Reduced Overhead and Enhanced Performance:</h3>
<p>Often, in personal projects, we only require a handful of components from extensive libraries. But incorporating these libraries can introduce unnecessary styles and bulk that slow things down. </p>
<p>Un-styled components tackle this issue by letting you include only what you need. This speeds up your app, making it leaner and more efficient, without unnecessary dependencies.</p>
<h3 id="heading-flexibility-for-customization">Flexibility for Customization</h3>
<p>Un-styled components empower you to craft elements seamlessly tailored to your project's demands. This covers their appearance, movements, and reactions to user interactions. </p>
<p>Whether you're building a simple form or a complex interactive feature, un-styled components give you the freedom to mold every detail to align with your design and purpose, harmonizing with your app's overall style.</p>
<h3 id="heading-accessibility-and-user-friendliness">Accessibility and User-Friendliness</h3>
<p>Crafting accessible interfaces that adhere to <a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/aria/">WAI-ARIA design</a> principles is important for developers. Unstyled UI components, often designed with accessibility in focus, offer enhanced usability for a broader user spectrum. This abstraction eliminates manual implementation.</p>
<h3 id="heading-future-ready-development">Future-Ready Development</h3>
<p>Technology evolves, and so do design trends and user expectations. Un-styled components ensure you're primed for what lies ahead. Integrating them empowers you to adapt your app's look and feel as trends shift, without being constrained by outdated styles.</p>
<h3 id="heading-modular-and-scalable-architecture">Modular and Scalable Architecture</h3>
<p>Un-styled components resemble building blocks that seamlessly fit into your project. This streamlines your code and makes it easier to manage. It also lets you reuse these blocks across different app sections, saving time while preserving consistency as your project expands.</p>
<h2 id="heading-exploring-popular-un-styled-ui-components">Exploring Popular Un-styled UI Components</h2>
<p>To give you more context into how un-styled components work, we'll now compare modal components from five common un-styled UI component libraries. </p>
<p>For most of these components, the Dialog components facilitate the creation of interactive, visually pleasing modal boxes for actions like profile editing, confirming decisions, or displaying extra information without navigating from the main content. </p>
<p>I'll also outline the overall features of these component libraries so you'll be able to integrate them into your future projects.</p>
<p>Let's now dive into a selection of the most notable un-styled UI component libraries and examine their dialog components.</p>
<h3 id="heading-radix-ui">Radix UI</h3>
<p>Radix UI's Dialog component, which you can find in the <code>@radix-ui/react-dialog</code> package, is a versatile tool that helps you build popup windows in your React apps. It allows you to interact with certain areas of your program without leaving the current screen.</p>
<p>You can create many types of popup windows, including those that stop the flow (modal) and those that do not (non-modal). </p>
<p>For designing comprehensive dialogs, you have numerous building pieces including <code>Trigger</code>, <code>Portal</code>, <code>Overlay</code>, <code>Content</code>, <code>Title</code>, <code>Description</code>, and <code>Close</code>. You can style it to match your design. It also adheres to accessibility requirements so that everyone can use it.</p>
<p>When you press the <code>Escape</code> key or click the <code>close</code> button, the dialog closes. For screen reader users, special components such as <code>Title</code> and <code>Description</code> are available.</p>
<p>The documentation is extensive and provides examples for several scenarios and It works well with the React ecosystem.</p>
<h4 id="heading-sample-code-from-radix">Sample code from Radix:</h4>
<p>To get started with Radix, you can run this command on your terminal:</p>
<p><code>npm install @radix-ui/react-popover@latest -E</code>  </p>
<p>To use the dialog component, you can import the components like so:</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Dialog <span class="hljs-keyword">from</span> <span class="hljs-string">'@radix-ui/react-dialog'</span>;

<span class="hljs-keyword">const</span> DialogComponent = <span class="hljs-function">() =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Root</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Trigger</span> <span class="hljs-attr">asChild</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Open profile<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Trigger</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Portal</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Overlay</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"DialogOverlay"</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Content</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"DialogContent"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Title</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"DialogTitle"</span>&gt;</span>Edit profile<span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Description</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"DialogDescription"</span>&gt;</span>
          Make changes to your profile here. Click save when you're done.
        <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Description</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Label"</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"name"</span>&gt;</span>
            Name
          <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Input"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">"Chinenye A"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Fieldset"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Label"</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">"username"</span>&gt;</span>
            Username
          <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Input"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"username"</span> <span class="hljs-attr">defaultValue</span>=<span class="hljs-string">"@chinenye"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">display:</span> '<span class="hljs-attr">flex</span>', <span class="hljs-attr">marginTop:</span> <span class="hljs-attr">25</span>, <span class="hljs-attr">justifyContent:</span> '<span class="hljs-attr">flex-end</span>' }}&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Close</span> <span class="hljs-attr">asChild</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"Button green"</span>&gt;</span>Save changes<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Close</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Close</span> <span class="hljs-attr">asChild</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"IconButton"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Close"</span>&gt;</span>
         Button
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Close</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Content</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Portal</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Root</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DialogComponent;
</code></pre><p>Here's what it looks like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/radix-modal.jpg" alt="The image shows a pop up modal with a form. The modal is a card placed on the center of the screen" width="600" height="400" loading="lazy">
<em>Modal screen of the dialog component from Radix UI</em></p>
<p>To learn more about Radix and its other components, <a target="_blank" href="https://www.radix-ui.com/primitives/docs/components/dialog">you can check out their documentation here.</a></p>
<h3 id="heading-shadcns-ui-component">shadcn's UI Component</h3>
<p>shadcn UI components, which are based on Radix UI and Tailwind CSS, deliver a seamless and integrated user experience. </p>
<p>shadcn is efficient and lightweight because you can install only the components that you require. It supports two kinds of popup windows: </p>
<ol>
<li>Those that require a response before closing <code>Alert Dialog</code>  </li>
<li>Those that don't (which is useful for displaying messages)</li>
</ol>
<p>The shadcn UI component is versatile since you can overlay content on the main screen or another popup. It's simple to use for keyboard and screen reader users as well.</p>
<p>To make your popups more visually appealing, you can apply effects like fading, sliding, or zooming. You can combine this with menus such as Context Menus or Dropdown Menus (also part of the Shadcn components) to increase functionality.</p>
<p>The components for the dialog are divided into sections like <code>DialogTrigger</code>, <code>DialogContent</code>, <code>DialogHeader</code>, among others for simpler implementation.</p>
<h4 id="heading-sample-code-from-shadcn">Sample code from shadcn:</h4>
<p>To get started, you can install the shadcn dialog component by running<br><code>npx shadcn-ui@latest add dialog</code>.  </p>
<p>To use the component on your projects, you can import them like this:</p>
<pre><code><span class="hljs-keyword">import</span> {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/dialog"</span>

&lt;Dialog&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DialogTrigger</span>&gt;</span>Open<span class="hljs-tag">&lt;/<span class="hljs-name">DialogTrigger</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DialogContent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DialogHeader</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">DialogTitle</span>&gt;</span>Are you sure absolutely sure?<span class="hljs-tag">&lt;/<span class="hljs-name">DialogTitle</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">DialogDescription</span>&gt;</span>
        This action cannot be undone. This will permanently delete your account
        and remove your data from our servers.
      <span class="hljs-tag">&lt;/<span class="hljs-name">DialogDescription</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">DialogHeader</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">DialogContent</span>&gt;</span></span>
&lt;/Dialog&gt;
</code></pre><p>And here's the result of the above code:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/screencapture-ui-shadcn-docs-components-dialog-2023-08-26-07_47_54.png" alt="This screen shows a modal " width="600" height="400" loading="lazy">
<em>Shadcn dialog display</em></p>
<p><a target="_blank" href="https://ui.shadcn.com/docs/components/dialog">You can read their documentation for other use cases</a>.</p>
<h3 id="heading-headless-ui-library">Headless UI Library:</h3>
<p>Headless UI Library provides a fully managed, flexible approach to constructing modal windows. You can use it in both React and Vue applications. </p>
<p>The library lets you customize popup windows to match the look and feel of your program. It also has built-in accessibility features, making it usable by keyboard and screen reader users.</p>
<p>To fit the appearance of your application, you can style the <code>Dialog</code> and <code>Dialog.Panel</code> components with <code>className</code> or <code>style props</code>. The <code>Dialog.Panel</code> can have an overlay or backdrop that can be animated individually to grab attention. It enables you to use CSS to build scrollable dialogs.</p>
<p>The library provides seamless transitions while opening and dismissing popups, which improves the visual experience.</p>
<h4 id="heading-code-sample-from-headless-ui">Code Sample from Headless UI:</h4>
<p>To get started with this library, you can run this command to install it:<br><code>npm install @headlessui/react</code>.  </p>
<p>You can use the dialog components as depicted below:</p>
<pre><code><span class="hljs-keyword">import</span> { Dialog, Transition } <span class="hljs-keyword">from</span> <span class="hljs-string">'@headlessui/react'</span>
<span class="hljs-keyword">import</span> { Fragment, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyModal</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> [isOpen, setIsOpen] = useState(<span class="hljs-literal">true</span>)

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">closeModal</span>(<span class="hljs-params"></span>) </span>{
    setIsOpen(<span class="hljs-literal">false</span>)
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openModal</span>(<span class="hljs-params"></span>) </span>{
    setIsOpen(<span class="hljs-literal">true</span>)
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"fixed inset-0 flex items-center justify-center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{openModal}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"rounded-md bg-black bg-opacity-20 px-4 py-2 text-sm font-medium text-white hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"</span>
        &gt;</span>
          Open dialog
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">Transition</span> <span class="hljs-attr">appear</span> <span class="hljs-attr">show</span>=<span class="hljs-string">{isOpen}</span> <span class="hljs-attr">as</span>=<span class="hljs-string">{Fragment}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Dialog</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"div"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative z-10"</span> <span class="hljs-attr">onClose</span>=<span class="hljs-string">{closeModal}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Transition.Child</span>
            <span class="hljs-attr">as</span>=<span class="hljs-string">{Fragment}</span>
            <span class="hljs-attr">enter</span>=<span class="hljs-string">"ease-out duration-300"</span>
            <span class="hljs-attr">enterFrom</span>=<span class="hljs-string">"opacity-0"</span>
            <span class="hljs-attr">enterTo</span>=<span class="hljs-string">"opacity-100"</span>
            <span class="hljs-attr">leave</span>=<span class="hljs-string">"ease-in duration-200"</span>
            <span class="hljs-attr">leaveFrom</span>=<span class="hljs-string">"opacity-100"</span>
            <span class="hljs-attr">leaveTo</span>=<span class="hljs-string">"opacity-0"</span>
          &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"fixed inset-0 bg-black bg-opacity-25"</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">Transition.Child</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"fixed inset-0 overflow-y-auto"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex min-h-full items-center justify-center p-4 text-center"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">Transition.Child</span>
                <span class="hljs-attr">as</span>=<span class="hljs-string">{Fragment}</span>
                <span class="hljs-attr">enter</span>=<span class="hljs-string">"ease-out duration-300"</span>
                <span class="hljs-attr">enterFrom</span>=<span class="hljs-string">"opacity-0 scale-95"</span>
                <span class="hljs-attr">enterTo</span>=<span class="hljs-string">"opacity-100 scale-100"</span>
                <span class="hljs-attr">leave</span>=<span class="hljs-string">"ease-in duration-200"</span>
                <span class="hljs-attr">leaveFrom</span>=<span class="hljs-string">"opacity-100 scale-100"</span>
                <span class="hljs-attr">leaveTo</span>=<span class="hljs-string">"opacity-0 scale-95"</span>
              &gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Panel</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">Dialog.Title</span>
                    <span class="hljs-attr">as</span>=<span class="hljs-string">"h3"</span>
                    <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg font-medium leading-6 text-gray-900"</span>
                  &gt;</span>
                    Payment successful
                  <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Title</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-2"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-500"</span>&gt;</span>
                      Your payment has been successfully submitted. We’ve sent
                      you an email with all of the details of your order.
                    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-4"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                      <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
                      <span class="hljs-attr">className</span>=<span class="hljs-string">"inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"</span>
                      <span class="hljs-attr">onClick</span>=<span class="hljs-string">{closeModal}</span>
                    &gt;</span>
                      Got it, thanks!
                    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog.Panel</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">Transition.Child</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Transition</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  )
}
</code></pre><p>Here's what that looks like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/screencapture-headlessui-react-dialog-2023-08-27-16_28_15.png" alt="A modal postioned at the center with a blue and purple gradient overlay to show how the headless ui components work" width="600" height="400" loading="lazy">
<em>Sample modal made with Headless UI</em></p>
<p>For more details about this component, <a target="_blank" href="https://headlessui.com/react/dialog">you can check out their documentation</a>.</p>
<h3 id="heading-primereact-ui">PrimeReact UI</h3>
<p>PrimeReact UI has a flexible <code>Dialog</code> component that lets you show content in overlay windows.</p>
<p>The dialog's appearance and disappearance times can be changed. It includes tools like resizing and drag-and-drop as well.</p>
<p>It automatically inserts a scrollbar for easy navigation if the material is too long. Based on various screen widths, you can adjust the dialog's width. The <code>position</code> setting allows you to place the dialog in different screen corners.</p>
<p>It is compatible with screen readers and keyboard navigation because it was created with accessibility in mind. Keyboard keys allow users to traverse the dialog. It complies with integration best practices and works nicely with other libraries.</p>
<h4 id="heading-code-sample-from-primereact-ui">Code sample from PrimeReact UI:</h4>
<p>To get started, run this command on your terminal: </p>
<p><code>npm install primereact</code></p>
<p>Here is a code snippet of the dialog component:</p>
<pre><code><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Button } <span class="hljs-keyword">from</span> <span class="hljs-string">'primereact/button'</span>;
<span class="hljs-keyword">import</span> { Dialog } <span class="hljs-keyword">from</span> <span class="hljs-string">'primereact/dialog'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">BasicDemo</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [visible, setVisible] = useState(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card flex justify-content-center"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Button</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Show"</span> <span class="hljs-attr">icon</span>=<span class="hljs-string">"pi pi-external-link"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setVisible(true)} /&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">Dialog</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"Header"</span> <span class="hljs-attr">visible</span>=<span class="hljs-string">{visible}</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">width:</span> '<span class="hljs-attr">50vw</span>' }} <span class="hljs-attr">onHide</span>=<span class="hljs-string">{()</span> =&gt;</span> setVisible(false)}&gt;
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"m-0"</span>&gt;</span>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
                    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
                    consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
                    Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    )
}
</code></pre><p>Here's what it looks like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/screencapture-primereact-org-dialog-2023-08-26-08_09_58.png" alt="This dialog component from primereact is positioned at the right of the screen and has a gray overlay" width="600" height="400" loading="lazy">
<em>Position of dialog component on center-right of the screen</em></p>
<p>For more information about the library, <a target="_blank" href="https://primereact.org/dialog/">visit their documentation here</a>.</p>
<h3 id="heading-reach-ui-dialog-component">Reach UI Dialog Component:</h3>
<p>The Reach UI Dialog component is a great option for designing user friendly and accessible popup windows.</p>
<p>It provides accurate control and is simple to use. It functions flawlessly on a variety of gadgets. It has usability enhancing accessibility features, and it's simple to make alert popups.</p>
<p>It also supports pinch zooming and is compatible with iPads and iPhones.</p>
<h4 id="heading-code-sample-from-reach-ui">Code Sample from Reach UI:</h4>
<p>To get started, you can run this command on your terminal to install the components: <code>npm install @reach/dialog</code>.</p>
<p>To use the components within your application, you can import them like this:</p>
<pre><code><span class="hljs-keyword">import</span> { Dialog, DialogOverlay, DialogContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"@reach/dialog"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@reach/dialog/styles.css"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dialog</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [showDialog, setShowDialog] = React.useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> open = <span class="hljs-function">() =&gt;</span> setShowDialog(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> close = <span class="hljs-function">() =&gt;</span> setShowDialog(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{open}</span>&gt;</span>Open Dialog<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">Dialog</span> <span class="hljs-attr">isOpen</span>=<span class="hljs-string">{showDialog}</span> <span class="hljs-attr">onDismiss</span>=<span class="hljs-string">{close}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"close-button"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{close}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">VisuallyHidden</span>&gt;</span>Close<span class="hljs-tag">&lt;/<span class="hljs-name">VisuallyHidden</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>&gt;</span>×<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello there. I am a dialog<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Dialog</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre><p>Here's what they look like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/screencapture-reach-tech-dialog-2023-08-26-08_37_03.png" alt="The dialog component from the reach ui is placed at the center of the screen" width="600" height="400" loading="lazy">
<em>Dialog component from reach ui</em></p>
<p>To learn more about this library and its components, <a target="_blank" href="https://reach.tech/dialog/">you can check out their documentation here</a>.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>This article highlighted several popular un-styled component libraries, including Radix UI, shadcn's UI Component, Headless UI Library, PrimeReact UI, and Reach UI Dialog Component. </p>
<p>To sum up, using un-styled UI components lets you customize your components according to your needs. It also brings performance efficiency and future adaptability. </p>
<p>These components offer reduced overhead, as they let you use only the components you need. This results in a more efficient application. </p>
<p>The flexibility of un-styled components also lets you tailor elements for specific project requirements, while prioritizing accessibility and user-friendliness. Integrating un-styled UI components elevates your ability to create accessible, visually appealing, and efficient user interfaces.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Astro UI Framework [Full Book] ]]>
                </title>
                <description>
                    <![CDATA[ By Emmanuel Ohans Astro is a new UI framework that's designed for speed. And if you want to learn how to use it, you've come to the right place.  Table of Contents Introduction  Chapter 1: Build your first Astro Application  Chapter 2: Astro Compone... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-astro-ui-framework/</link>
                <guid isPermaLink="false">66d45e54bc9760a197a1039c</guid>
                
                    <category>
                        <![CDATA[ book ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 12 Jul 2023 20:58:29 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/large-heading.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Emmanuel Ohans</p>
<p>Astro is a new UI framework that's designed for speed. And if you want to learn how to use it, you've come to the right place. </p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-introduction">Introduction</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-1-build-your-first-astro-application">Chapter 1: Build your first Astro Application</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-2-astro-components-in-depth">Chapter 2: Astro Components In-depth</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-3-build-your-own-component-island">Chapter 3: Build Your Own Component Island</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-4-the-secret-life-of-astro-component-islands">Chapter 4: The Secret Life of Astro Component Islands</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-5-oh-my-react-how-to-build-a-react-documentation-site-clone">Chapter 5: Oh my React! (How to Build a React Documentation Site Clone)</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-6-server-side-rendering-ssr-in-astro">Chapter 6: Server-side Rendering (SSR) in Astro</a> </li>
<li><a class="post-section-overview" href="#heading-chapter-7-be-audible-how-to-build-a-fullstack-astro-project">Chapter 7: Be Audible! (How to Build a Fullstack Astro Project)</a></li>
<li><a class="post-section-overview" href="#heading-chapter-8-build-your-own-astro-integrations">Chapter 8: Build Your Own Astro Integrations</a> </li>
<li><a class="post-section-overview" href="#conclusion-6">Conclusion</a> </li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-132.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h1 id="heading-introduction">Introduction</h1>
<p>I'm not one of those bandwagon-jumping folks who drool over every shiny new library or framework that hits the scene just because it's trending. I'm more of a "wait-and-see" kinda person.</p>
<p>So, you're probably wondering why I wrote a book about the reasonably new UI framework, Astro.</p>
<p>Well, let me tell you.</p>
<p>I’ve been in this game for almost a decade now, and I've seen frameworks come and go like a bad case of indigestion. And Astro may not live forever, either.</p>
<p>But here's the thing: when you use a new UI framework, it's not just about getting stuff to work and slapping some apps together willy-nilly. No, no, no. The real magic lies in understanding the principles and concepts behind the framework's creation. And that's exactly the mindset I had when I wrote this book.</p>
<p>You’ve got to ask yourself: what makes this framework so unique? How is it different from all the other fluff out there? How can you apply its mental model to the bigger picture of developing applications for the web? Plus, what framework-agnostic principles can you pick up along the way?</p>
<p>The good news is I've got answers to all these burning questions sprinkled throughout the book like confetti.</p>
<p>Now, let's talk about performance, shall we? Of course, that’s a whole different ballgame depending on what kind of application you're dealing with. But for specific applications, for example content-focused applications, Astro is a total game-changer. Its performance defaults are off the charts.</p>
<p>The more I researched Astro, the more I was fascinated to write this book.</p>
<p>And here's the kicker: this book goes beyond just Astro. In specific chapters, we will discuss concepts you can apply to whatever framework you work with. And that's not just cool – that’s downright practical.</p>
<p>Astro is paving the way for a new architecture on the web: the component island architecture. And my goal is to help you understand it well enough to build some seriously robust production applications.</p>
<p>So, don't just scratch the surface. Instead, let’s dive deep and get to know this framework.</p>
<p>This is why I am writing this book. And hey, six months in, and I’m still loving it.</p>
<p>So, what are you waiting for? Grab your favourite drink (tea over coffee, here), dig in, and let's get building!</p>
<p>Cheers 🥂</p>
<h2 id="heading-a-note-about-this-book">A Note About This Book</h2>
<p>Okay, if you haven’t already noticed, I write like I speak. I use plain language and analogies that even my nan could (potentially) understand — when I do it right.</p>
<p>This book does not read in a typical technical documentation style—sorry, fellow nerds.</p>
<p>In my opinion, technical books should be easy on the eyes and a breeze to read. And why not have a bit of a laugh while we're at it?</p>
<p>If you're up for a good time while you learn a thing or two (well, a lot more), then let's get cracking!</p>
<h2 id="heading-this-book-vs-the-official-documentation">This Book vs the Official Documentation</h2>
<p>Some resources just parrot the official documentation. But I don't find these very helpful.</p>
<p>As such, this book differs from the official documentation in a couple of ways:</p>
<ul>
<li><strong>The tone of writing</strong>: this book adopts a non-technical documentation writing style for ease of understanding. Whether you appreciate this or not is left to your taste.</li>
<li><strong>Doesn’t follow the Diataxis framework</strong>: the Astro technical documentation is written following the <a target="_blank" href="https://diataxis.fr/">Diataxis</a> framework. The framework suggests structuring content around four distinct types: tutorial, how-to-guide, explanation, and reference.<br>This book breaks out of this strict structure to emphasise understanding and practical learning. This book is not a reference and doesn’t aim to replace the official Astro references. In the Diataxis lingo, understanding Astro may be defined as a mix of how-to guides and a careful blend of tutorials with elaborate explanations interwoven.</li>
<li><strong>Advanced usage</strong>: some advanced Astro uses are tucked away in the official references – without explanations or practical examples. This is perfectly fine for a documentation site. Experienced engineers can spend time digging into these. However, this book bridges the gap.<br>For example, consider building custom Astro integrations. You will not find a better (practical) resource than this book.</li>
<li><strong>Real-world applications</strong>: sometimes, to piece together a puzzle, it’s essential to see it at play in near real-world examples. This book explains important concepts and goes beyond that to put them to practice in comparative real-world examples.</li>
<li><strong>Saves time</strong>: This book will save you countless hours tinkering with references and code samples as a by-product of the above distinctions. Yes, you can spend hours digging deep into the docs or Astro source code, but I’ve spent hours (months, actually) doing so! So I can present the learnings without you doing as much of the work. But don’t be fooled – you still have to do the work of reading the book.</li>
</ul>
<p>Consider reading (or skimming) the official documentation after reading this book or using it as a reference. This book complements the official docs, it does not replace them.</p>
<h2 id="heading-how-the-book-is-structured">How the Book is Structured</h2>
<p>Every chapter in this book is one of the following:</p>
<ol>
<li>A concept chapter</li>
<li>A project chapter</li>
<li>A project and concept chapter</li>
</ol>
<p>The mix of these different chapter types will keep you engaged and make your learning effective. Remember, the goal is proper understanding.</p>
<h3 id="heading-concept-chapters">Concept chapters</h3>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/05/concept@2x.png" alt="Concept chapters are the foundational chapters for the rest of the book." width="600" height="400" loading="lazy">
<em>Concept chapters are the foundational chapters for the rest of the book.</em></p>
<p>In concept chapters, we’ll learn the core concepts of Astro. These chapters will include code examples and throwaway applications. We will build no real-world projects in these chapters.</p>
<h3 id="heading-project-chapters">Project chapters</h3>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/05/build.png" alt="Showtime! Bring together what we've learned to build a real-world project. " width="600" height="400" loading="lazy">
<em>Showtime! Bring together what we've learned to build a real-world project.</em></p>
<p>In project chapters, we’ll apply previous concepts we’ve learned towards building a near real-world project.</p>
<h3 id="heading-concept-and-project-chapters">Concept and project chapters</h3>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/05/concept-and-build.png" alt="Bring together the best of the worlds. Build and learn new concepts along the way." width="600" height="400" loading="lazy">
<em>Bring together the best of the worlds. Build and learn new concepts along the way.</em></p>
<p>A project and concept chapter focuses on building a real-world application while introducing new concepts.</p>
<h2 id="heading-chapters-overview">Chapters Overview</h2>
<p>Below’s a summary of the chapters of the book:</p>
<h3 id="heading-chapter-1-build-your-first-application-with-astro">Chapter 1: Build your first application with Astro</h3>
<p>The book begins hands-on with a project and concept chapter.</p>
<p>In this chapter, we’ll learn the basics of Astro while building a feature-rich personal website.</p>
<h3 id="heading-chapter-2-astro-components-in-depth">Chapter 2: Astro components in-depth</h3>
<p>This is a concept chapter that goes in-depth into Astro components. We will go beyond the basics and master (arguably) the essential Astro entity.</p>
<p>We will start by exploring an argument to ditch the JavaScript runtime overhead where appropriate. We will then study the behaviour of Astro component markup, styles and scripts, and the powerful template syntax.</p>
<h3 id="heading-chapter-3-build-your-own-component-island">Chapter 3: Build your own component island</h3>
<p>This project chapter moves away from Astro and considers the component island architecture in isolation.</p>
<p>We will consider an overview of application rendering, comprehend the island architecture from the ground up, and build our own implementation from scratch.</p>
<p>This chapter will solidify your fundamental knowledge of the new web performance-focused architecture pattern.</p>
<h3 id="heading-chapter-4-the-secret-life-of-astro-component-islands">Chapter 4: The Secret Life of Astro Component Islands</h3>
<p>This is a concept chapter where we’ll get hands-on experience working with framework components in Astro. I’ll introduce you to responsible hydration and why it matters.</p>
<p>We will build many throwaway applications to explore how component islands work in Astro and why they are significant.</p>
<h3 id="heading-chapter-5-oh-my-react-the-react-documentation-site-clone">Chapter 5: Oh My React! (The React Documentation Site Clone)</h3>
<p>In this project and concept chapter, we will explore techniques for handling large amounts of content within an Astro application. Additionally, we will examine real-world use cases to provide practical examples.</p>
<p>This chapter will solidify the previous concepts learned and introduce some new ones while we build out a clone of the React documentation site with production best practices.</p>
<h3 id="heading-chapter-6-server-side-rendering-ssr-in-astro">Chapter 6: Server-side rendering (SSR) in Astro</h3>
<p>This concept chapter will explore server-side rendering and the new features unlocked in an Astro server-side rendered application. We will explore dynamic routing, API endpoints, Server streaming, and much more.</p>
<h3 id="heading-chapter-7-be-audible-full-stack-astro-project">Chapter 7: Be Audible! (Full stack Astro Project)</h3>
<p>This project chapter will take you beyond static sites into building full stack applications with Astro. In this chapter, I’ll argue that if you can build the app as an MPA and leverage component islands, you can build it with Astro.</p>
<h3 id="heading-chapter-8-build-your-own-astro-integrations">Chapter 8: Build your own Astro integrations</h3>
<p>This is a project and concept chapter where we’ll answer the question, what happens when you want a feature outside what Astro provides by default?</p>
<p>We will leverage hooks into Astro’s build process to build custom functionalities. These are called Astro integrations.</p>
<h3 id="heading-chapter-9-conclusion">Chapter 9: Conclusion</h3>
<p>Here, we will step back and appreciate how far we’ve come. Then we will reiterate the features that make Astro stand out. Features you’ve already seen in practice!</p>
<p>This is where our journey likely ends, and your journey into the world of Astro begins.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>I tried to make this book “work for everyone”, but that’s incredibly difficult.</p>
<p>So, to make the best out of this book:</p>
<ul>
<li>You should already know some HTML, CSS and JS: this is not a web development beginner guide.</li>
<li>You should already know the basics of TypeScript: I don’t expect you to be a TypeScript champion, but surface-level understanding will prepare you for all the TypeScript in this book.</li>
</ul>
<p>I wrote this book specifically for mid, senior, and senior+ engineers, and the book contains chapters of varying technical difficulty. But I’ve done my best to explain these clearly and visually to satisfy different skill levels.</p>
<h2 id="heading-typographic-conventions">Typographic Conventions</h2>
<p>When text is written in a monospaced font, it typically represents code samples. These samples may be self-contained fragments or refer to a specific section of an application's code.</p>
<p>Below’s an example:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> { author } = Astro.props;
<span class="hljs-keyword">const</span> book = <span class="hljs-string">"Understanding Astro.js"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">data-name</span>=<span class="hljs-string">{book}</span>&gt;</span>A new book<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>Sometimes, to show the source of the code, I added a comment to the file path at the top of the code block, as shown below:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** 📂 src/pages/index.astro **/</span>}
---
<span class="hljs-keyword">const</span> { author } = Astro.props;
<span class="hljs-keyword">const</span> book = <span class="hljs-string">"Understanding Astro.js"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">data-name</span>=<span class="hljs-string">{book}</span>&gt;</span>A new book<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>With code fragments referring to changes in a nearby application code, you’ll find an ellipsis to signify no code changes in the previous code, like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
&lt;h1 data-name={book}&gt;A changed book name&lt;/h1&gt;
</code></pre>
<p>The code above suggests the previous code block remains the same, except for the new <code>&lt;h1&gt;</code> with <code>A changed book name</code>.</p>
<p>Finally, the book uses the <code>npm</code> package manager. For example, the code to install a package will be described as shown below:</p>
<pre><code class="lang-bash">npm install some-package
</code></pre>
<p>You can use the associated commands for other package managers, such as <code>yarn</code> or <code>pnpm</code>.</p>
<p>Phew! That’s enough housekeeping. Now, let’s dive into Astro!</p>
<h2 id="heading-want-to-get-the-ebook">Want to get the eBook?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/book-cover-transparent-1.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/understanding-astro/understanding-astro-book">Download ebook on Github</a></em></p>
<ul>
<li>500+ pages of value</li>
<li>4+ practical project chapters</li>
<li>100+ carefully crafted illustrations and images</li>
<li>Learn techniques to build faster applications </li>
<li><strong>Integrate React, Svelte, Vue, Tailwind</strong> and more into an Astro project </li>
<li>Learn to build your own <strong>component islands implementation</strong> from scratch</li>
<li>Learn to <strong>build full stack applications with Astro</strong> (without sacrificing performance) </li>
<li>Go <strong>beyond the basics</strong> and parse Astro code into ASTs and build custom project features </li>
</ul>
<p><a target="_blank" href="https://ohans.me/ua-github">Download the free ebook on GitHub.</a> </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-133.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter one.</em></p>
<h2 id="heading-chapter-1-build-your-first-astro-application">Chapter 1: Build your first Astro Application</h2>
<blockquote>
<p>"Long is the road to learning by precepts, but short and successful by examples." – Seneca the Younger.</p>
</blockquote>
<p>This essay will get started with the basics of Astro by building a practical application: a personal website. To view the complete application, see the <a target="_blank" href="https://github.com/understanding-astro/astro-beginner-project">GitHub repo</a>. </p>
<h2 id="heading-what-youll-learn">What you’ll learn</h2>
<ul>
<li>Build a personal website with Astro.</li>
<li>Set up a local development environment for Astro.</li>
<li>Familiarity with Astro components, layouts, and pages.</li>
<li>A working knowledge of styles and scripts in Astro.</li>
<li>Theming Astro sites via CSS variables.</li>
<li>Leveraging markdown pages for ease.</li>
<li>Deployment of a static Astro application.</li>
</ul>
<h2 id="heading-project-overview">Project Overview</h2>
<p>I remember my first commercial web development project. In retrospect, it was a disaster. One built by a passionate self-taught engineer, but a disaster still.</p>
<p>Let’s make your first Astro project one you’ll remember for good.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/rshhXm2Q1V0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-getting-started">Getting started</h2>
<p><strong>Astro is a web framework designed for speed</strong>. Before we get to the good stuff, let’s ensure we’re both on the same page.</p>
<h3 id="heading-install-nodejs">Install Node.js</h3>
<p>Firstly, make sure you have Node.js installed.</p>
<p>If you're unsure, run <code>node --version</code> in your terminal. You will get back a Node version if you have Node.js installed.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-11.11.18@2x.png" alt="Get NodeJS version from the CLI." width="600" height="400" loading="lazy">
<em>Get NodeJS version from the CLI.</em></p>
<p>Don’t have Node installed? Then, visit the official <a target="_blank" href="https://nodejs.org/en/download">download</a> page and install the necessary package for your operating system. It’s as easy as installing any other computer program. Click, click, click!</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-10.44.30@2x.png" alt="The NodeJS download page." width="600" height="400" loading="lazy">
<em>The NodeJS download page.</em></p>
<h3 id="heading-how-to-set-up-your-code-editor">How to set up your code editor</h3>
<p>I’ll avoid any heated debate(s) on what code editor you should be writing software with. Quite frankly, it doesn't matter to me.</p>
<p>However, I use Visual Studio Code (VSCode).</p>
<p>You can develop Astro applications with any code editor, but VSCode is also the officially recommended editor for Astro.</p>
<p>If you’re building with VSCode, install the official <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=astro-build.astro-vscode">Astro extension</a>. This helps with syntax and semantic highlighting, diagnostic messages, IntelliSense, and more.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.03.36@2x.png" alt="The official Astro VSCode extension." width="600" height="400" loading="lazy">
<em>The official Astro VSCode extension.</em></p>
<p>Let’s now get started setting up our first Astro project. To do this, we must install Astro. The fastest way to do this is to use the Astro automatic CLI.</p>
<p>To start the install wizard, run the following command:</p>
<pre><code class="lang-bash">npm create astro@latest
</code></pre>
<p>If on <code>pnpm</code> or <code>yarn</code>, the command looks as follows:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># using pnpm</span>
pnpm create astro@latest


<span class="hljs-comment"># using yarn </span>
yarn create astro
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.15.44@2x.png" alt="Starting a new project with the Astro CLI wizard." width="600" height="400" loading="lazy">
<em>Starting a new project with the Astro CLI wizard.</em></p>
<p>This will start the wizard, which will guide us through helpful prompts. It’s important to mention that we can run this from anywhere on our machine and later choose where exactly we want the project created.</p>
<p>When asked, “Where should we create your new project?” go ahead and pass a file path. In my case, this is <code>documents/dev/books/understanding-astro/astro-beginner-project</code>.</p>
<p>Alternatively, we could have run the <code>npm create astro@latest</code> command in our desired directory and just entered a shorter file path, for example, <code>./astro-beginner-project</code>.</p>
<p>When asked, “How would you like to start your new project?” go ahead and choose “Empty”.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.20.54@2x.png" alt="Answering the template CLI prompt." width="600" height="400" loading="lazy">
<em>Answering the template CLI prompt.</em></p>
<p>We want a fresh start to explore Astro from the ground up.</p>
<p>Now, we will be asked whether to install dependencies or not. Select yes and hit enter to continue the installation.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.22.21@2x.png" alt="Installing dependencies in the CLI prompt." width="600" height="400" loading="lazy">
<em>Installing dependencies in the CLI prompt.</em></p>
<p>Once the dependencies are installed, answer the “Do you plan to write TypeScript?” prompt with a yes and choose the “strictest” option.</p>
<p>We want strong type safety.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.24.22@2x.png" alt="Choosing Typescript in the CLI prompt." width="600" height="400" loading="lazy">
<em>Choosing Typescript in the CLI prompt.</em></p>
<p>Afterwards, answer the “Initialise a new Git repository?” question with whatever works for you. I’ll go with a yes here and hit enter.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.25.33@2x.png" alt="Initialising git in the CLI prompt." width="600" height="400" loading="lazy">
<em>Initialising git in the CLI prompt.</em></p>
<p>And voilà! Believe it or not, our new project is already created and ready to go!</p>
<p>Change into the directory where you set up the project. In my case, this looks like the following:</p>
<pre><code class="lang-html">cd ./documents/dev/books/understanding-astro/astro-beginner-project
</code></pre>
<p>And then run the application via the following:</p>
<pre><code class="lang-html">npm run start
</code></pre>
<p>This will start the live application on an available local port 🚀</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-11.29.57@2x.png" alt="The basic Astro project running on localhost:3000" width="600" height="400" loading="lazy">
<em>The basic Astro project running on localhost:3000</em></p>
<h2 id="heading-project-structure">Project Structure</h2>
<p>Open the newly created project in your code editor, and you’ll notice that the <code>create astro</code> CLI wizard has included some files and folders.</p>
<p>Astro has an opinionated folder structure. We can see some of this in our new project. By design, every Astro project will include the following in the root directory:</p>
<table>
    <thead>
        <tr>
            <th>
                File / Directory
            </th>
            <th>
                What?
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br>astro.config.mjs
            </td>
            <td>
                <br>The Astro configuration file. This is where we provide <br>configuration options for our Astro project.<br>
            </td>
        </tr>
        <tr>
            <td>
                <br>tsconfig.json
            </td>
            <td>
                <br>A Typescript configuration file. This specifies the root files and Typescript compiler options.<br>
            </td>
        </tr>
        <tr>
            <td>
                <br>package.json
            </td>
            <td>
                <br>A JSON file that holds the project metadata. <br>This is typically found at the root of most Node.js projects. <br>
            </td>
        </tr>
        <tr>
            <td>
                <br>public/<em></em>
            </td>
            <td>
                <br>This directory holds files and assets that will be copied into <br>the Astro build directory untouched, e.g., fonts, images and <br>files such as <code>robots.txt</code><br>
            </td>
        </tr>
        <tr>
            <td>
                <br>src/<em></em>
            </td>
            <td>
                <br>The source code of our project resides here.<br>
            </td>
        </tr>
    </tbody>
</table>

<p>Let’s now look at the files in our newly generated project.</p>
<h3 id="heading-tsconfigjson-file"><code>tsconfig.json</code> file</h3>
<p>The content of our <code>tsconfig.json</code> file is the following:</p>
<pre><code class="lang-js">{
  <span class="hljs-string">"extends"</span>: <span class="hljs-string">"astro/tsconfigs/strictest"</span>
}
</code></pre>
<p>The <code>extends</code> property points to the base configuration file path to inherit from, that is, inherit the typescript configuration from the file in <code>astro/tsconfigs/strictest</code>.</p>
<p>Using your editor, navigate to the referenced path – for example in <code>vscode</code> by clicking on the link while holding <code>CMD</code>. This will navigate us to <code>node_modules/astro/tsconfigs/strictest.json</code>, where we’ll find a well-annotated file:</p>
<pre><code class="lang-js">{
  ...
  <span class="hljs-string">"compilerOptions"</span>: {
    <span class="hljs-comment">// Report errors for fallthrough cases in switch statements</span>
    <span class="hljs-string">"noFallthroughCasesInSwitch"</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-comment">// Force functions designed to override their parent class to be specified as `override`.</span>
    <span class="hljs-string">"noImplicitOverride"</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-comment">// Force functions to specify that they can return `undefined` if a possible code path does not return a value.</span>
    <span class="hljs-string">"noImplicitReturns"</span>: <span class="hljs-literal">true</span>,
     ...
  }
}
</code></pre>
<p>This is very well annotated, so we won’t spend time on this. But the <code>compilerOptions</code> for TypeScript are set in this file. The point to make here is Astro keeps a list of TypeScript configurations (<code>base</code>, <code>strict</code> and <code>strictest</code>) that our project leverage when we initialise via the CLI wizard.</p>
<p>In this example, we’ll leave the <code>tsconfig.json</code> file as is. TypeScript (and consequently the <code>tsconfig.json</code> file) is optional in Astro projects. But I strongly recommend you leverage TypeScript. We’ll do so all through the book.</p>
<h3 id="heading-packagejson-file"><code>package.json</code> file</h3>
<p>The <code>package.json</code> file is easy to reason about. It holds metadata about our project and includes scripts for managing our Astro project, like <code>npm start</code>, <code>npm run build</code>, and <code>npm preview</code>.</p>
<h3 id="heading-package-lockjson-file"><code>package-lock.json</code> file</h3>
<p>The <code>package-lock.json</code> file is an autogenerated file that holds information on the dependencies/packages for our project. We won’t be touching this file manually. Instead, it is automatically generated (and updated) by npm.</p>
<p>Note that a project’s lock file may differ depending on the package manager, for example yarn or pnpm.</p>
<h3 id="heading-astroconfigmjs-file"><code>astro.config.mjs</code> file</h3>
<p>Most frameworks define a way for us to specify our project-specific configurations. For example, Astro achieves this via the <code>astro.config</code> file.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({});
</code></pre>
<p>At the moment, it defines an empty configuration. So we’ll leave it as is. But this is the right place to specify different build and server options, for example.</p>
<h3 id="heading-srcenvdts-file"><code>src/env.d.ts</code> file</h3>
<p><code>d.ts</code> files are called type declaration files. Yes, that’s for TypeScript alone, and they exist for one purpose: to describe the shape of some existing module. The information in this file is used for type checking by TypeScript.</p>
<pre><code class="lang-js"><span class="hljs-comment">/// &lt;reference types="astro/client" /&gt;</span>
</code></pre>
<p>The content of the file points to <code>astro/client</code>. This is essentially a reference to another declaration file at <code>astro/client.d.ts</code></p>
<h3 id="heading-srcpagesindexastro-file"><code>src/pages/index.astro</code> file</h3>
<p>As mentioned earlier, the <code>src</code> folder is where the source code for our project resides. But what’s the <code>pages</code> directory, and why’s there an <code>index.astro</code> file?</p>
<p>First, consider the contents of the <code>index.astro</code> file:</p>
<pre><code class="lang-js">---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>You’ll notice that it looks remarkably similar to standard HTML, with some exceptions.</p>
<p>Also, notice what’s written within the <code>&lt;body&gt;</code> tag: an <code>&lt;h1&gt;</code> element with the text <code>Astro</code>.</p>
<p>If we visit the running application in the browser, we have the <code>&lt;h1&gt;</code> rendered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-14.18.20@2x.png" alt="The rendered page heading." width="600" height="400" loading="lazy">
<em>The rendered page heading.</em></p>
<p>Now change the text to read <code>&lt;h1&gt;Hello world&lt;/h1&gt;</code> and notice how the page is updated in the browser:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-14.19.41@2x.png" alt="The updated page heading." width="600" height="400" loading="lazy">
<em>The updated page heading.</em></p>
<p>This leads us nicely to discuss pages in Astro — what I consider the entry point to our application.</p>
<h2 id="heading-introduction-to-astro-pages">Introduction to Astro Pages</h2>
<p>Astro leverages a file-based routing system. It achieves this by using the files in the <code>src/pages</code> directory.</p>
<p>For example, the <code>src/pages/index.astro</code> file corresponds to the <code>index</code> page served in the browser.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-09.49.26@2x.png" alt="The project’s index page." width="600" height="400" loading="lazy">
<em>The project’s index page.</em></p>
<p>Let’s go ahead and create an <code>src/pages/about.astro</code> page with similar content to <code>index.astro</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/about.astro</span>
---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>About us<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>About us<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<ul>
<li>Copy and paste the exact content of <code>index.astro</code> in <code>about.astro</code>.</li>
<li>Change the <code>&lt;h1&gt;</code> to have the text <code>About us</code>.</li>
</ul>
<p>Now, if we navigate to <code>/about</code> in the browser, we should have the new page rendered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-09.50.13@2x.png" alt="The “About us” page." width="600" height="400" loading="lazy">
<em>The “About us” page.</em></p>
<h3 id="heading-what-makes-a-valid-astro-page">What makes a valid Astro page?</h3>
<p>We’ve defined Astro pages as files in the <code>src/pages/</code>directory. Unfortunately, this is only partly correct.</p>
<p>For example, if we duplicate the <code>favicon.svg</code> file in <code>public/favicon.svg</code> into the <code>pages</code> directory, does this represent a <code>favicon</code> page?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-09.55.21.png" alt="Duplicating the favicon in the pages directory." width="600" height="400" loading="lazy">
<em>Duplicating the favicon in the pages directory.</em></p>
<p>Even though <code>index.astro</code> and <code>about.astro</code> correspond to our website’s index and about pages, <code>/favicon</code> will return a <code>404: Not found</code> error.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-09.56.51@2x.png" alt="The /favicon route. " width="600" height="400" loading="lazy">
<em>The /favicon route.</em></p>
<p>This is because only specific files make a valid astro page. For example, if we consider the <code>index</code> and <code>about</code> files in the <code>pages</code> directory, you perhaps notice something: they both have the <code>.astro</code> file ending!</p>
<p>In layperson’s terms, these are Astro files, but a more technical terminology for these is Astro components.</p>
<p>So, quick quiz: what is an Astro component?</p>
<p>That’s easy—a file with the <code>.astro</code> ending.</p>
<p>10 points to you! Well done.</p>
<h2 id="heading-anatomy-of-an-astro-component">Anatomy of an Astro component</h2>
<p>We’ve established that <code>index.astro</code> and <code>about.astro</code> represent Astro components and are valid Astro pages.</p>
<p>Now, let’s dig into the content of these files.</p>
<p>Consider the contents of the <code>index.astro</code> page:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- removed for brevity --&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Notice the distinction between the two parts of this file’s content.</p>
<p>The section at the bottom contains the page’s markup:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
<span class="hljs-comment">// ... </span>
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
  &lt;!-- removed <span class="hljs-keyword">for</span> brevity --&gt;
&lt;/html&gt;
</code></pre>
<p>This part is called the <strong>component template</strong> section.</p>
<p>While the top section contains a rather strange divider-looking syntax:</p>
<pre><code class="lang-js">---
---
</code></pre>
<p>This part is called the <strong>component script</strong> section, and the <code>---</code> is called a fence.</p>
<p>Together, these make up an Astro component.</p>
<p>Let’s take the component script section for a spin.</p>
<p>The section’s name hints at what this section of the component does. Within the component script code fence, we may declare variables, import packages and fully take advantage of JavaScript or TypeScript.</p>
<p>Oh yes, TypeScript!</p>
<p>Let’s start by creating a variable to hold our user’s profile picture, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">const</span> profilePicture = <span class="hljs-string">"https://i.imgur.com/JPGFE75.jpg"</span>;
---
</code></pre>
<p>We may then take advantage of the component template section to reference this image as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">const</span> profilePicture = <span class="hljs-string">"https://i.imgur.com/JPGFE75.jpg"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- 👀 Look here  --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">{profilePicture}</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"Frau Katerina's headshot."</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"100px"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"100px"</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Note that the <code>profilePicture</code> variable is referenced using curly braces <code>{ }</code>. This is how to reference variables from the component script in the component markup.</p>
<p>Now we should have the image rendered on the home page:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-29-at-10.30.54@2x.png" alt="Rendering the user profile photo." width="600" height="400" loading="lazy">
<em>Rendering the user profile photo.</em></p>
<p>It’s not much, but it’s honest work, eh?</p>
<p>Let’s go ahead and flesh out the page to have the user’s profile markup:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
<span class="hljs-comment">// ...</span>
  &lt;body&gt;
    &lt;!-- Look here 👀 --&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">{profilePicture}</span>
        <span class="hljs-attr">alt</span>=<span class="hljs-string">"Frau Katerina's headshot."</span>
        <span class="hljs-attr">width</span>=<span class="hljs-string">"100px"</span>
        <span class="hljs-attr">height</span>=<span class="hljs-string">"100px"</span>
      /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Frau Katerina<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>VP of Engineering at Goooogle<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
          Helping developers be excellent and succeed at building scalable
          products
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  &lt;/body&gt;
<span class="hljs-comment">// ...</span>
</code></pre>
<p>As you might have noticed, we’re writing <code>HTML</code> looking syntax in the component markup section!</p>
<p>Now we should have the user photo and their bio rendered in the browser as follows:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-10-at-14.07.31@2x.png" alt="The user profile photo and bio." width="600" height="400" loading="lazy">
<em>The user profile photo and bio.</em></p>
<h2 id="heading-component-styles">Component Styles</h2>
<p>Styling in Astro is relatively easy to reason about. Add a <code>&lt;style&gt;</code> tag to a component, and Astro will automatically handle its styling.</p>
<p>While it’s possible to select elements directly, let’s go ahead and add classes to the component markup to make this easier:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro  </span>
<span class="hljs-comment">// ...</span>
&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"profile"</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">{profilePicture}</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"profile__picture"</span> 
      {/** <span class="hljs-attr">...</span> **/}
    /&gt;</span></span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"profile__details"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Frau Katerina<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      {/** ... **/}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/div&gt;
<span class="hljs-comment">// ...</span>
</code></pre>
<p>Add a <code>&lt;style&gt;</code> tag, and write CSS as usual:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
&lt;style&gt;
  .profile {
    <span class="hljs-attr">display</span>: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    padding: <span class="hljs-number">1</span>rem <span class="hljs-number">0</span> <span class="hljs-number">3</span>rem <span class="hljs-number">0</span>;
  }

  .profile__details {
    <span class="hljs-attr">flex</span>: <span class="hljs-number">1</span> <span class="hljs-number">0</span> <span class="hljs-number">300</span>px;
  }

  .profile__details &gt; h1 {
    margin-top: <span class="hljs-number">0</span>;
  }

  .profile__picture {
    border-radius: <span class="hljs-number">50</span>%;
    margin: <span class="hljs-number">0</span> <span class="hljs-number">2</span>rem <span class="hljs-number">1</span>rem <span class="hljs-number">0</span>;
  }
&lt;/style&gt;
</code></pre>
<p>The user details should now be styled as expected.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-08.42.27@2x.png" alt="Applying styles to the index.astro page component." width="600" height="400" loading="lazy">
<em>Applying styles to the index.astro page component.</em></p>
<p>If we inspect the eventual styles applied to our UI elements via the browser developer tools, we’ll notice that the style selectors look different.</p>
<p>For example, to style the user name, we’ve written the following CSS:</p>
<pre><code class="lang-js">.profile__details &gt; h1 {
  margin-top: <span class="hljs-number">0</span>;
}
</code></pre>
<p>However, what’s applied in the browser looks something like this:</p>
<pre><code class="lang-js">.profile__details:where(.astro-J7PV25F6) &gt; h1:where(.astro-J7PV25F6) {
  margin-top: <span class="hljs-number">0</span>;
}
</code></pre>
<p>Why is this?</p>
<p>The actual style declarations for the <code>h1</code> element remain unchanged. The only difference here is the selector.</p>
<p>The <code>h1</code> element now has auto-generated class names, and the selector is now scoped via the <code>:where</code> CSS selector.</p>
<p>This is done internally by Astro. This makes sure the styles we write don’t leak beyond our component. For example, if we styled every <code>h1</code> in our component as follows:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">color</span>: red
}
</code></pre>
<p>The eventual style applied in the browser will be similar to the following:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">h1</span><span class="hljs-selector-pseudo">:where(.astro-some-unique-id)</span> {
  <span class="hljs-attribute">color</span>: red
}
</code></pre>
<p>This will ensure all other <code>h1</code> in our project remains the same, and this style only applies to our specific component <code>h1</code>.</p>
<h2 id="heading-page-layouts">Page Layouts</h2>
<p>Look at the pages of our completed application. You may notice that they all have identical forms.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-09.10.55.png" alt="A breakdown of the application page structure. " width="600" height="400" loading="lazy">
<em>A breakdown of the application page structure.</em></p>
<p>There’s a navigation bar, a footer, and some container that holds the page’s main content.</p>
<p>Should we repeat these similar UI structures across all pages?</p>
<p>Most people will answer “No”. So, is there a way to share reusable UI structures across pages?</p>
<p>Yes, yes, yes! This is where layouts come in.</p>
<p>Layouts are Astro components with a twist. They are used to provide reusable UI structures across pages, for example navigation bars and footers.</p>
<p>Conventionally, layouts are placed in the <code>src/layouts</code> directory. This is not compulsory but is a widespread pattern.</p>
<p>Let’s go ahead and create our first layout in <code>src/layouts/Main</code>. We’ll do this by moving away all the reusable UI structures currently in <code>index.astro</code> as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    {/* Add a new meta description tag */}
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Frau Katarina's website"</span> /&gt;</span>
    {/* Title is hardcoded as Astro, for now. */}
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      {/* We want the content of each page to go here */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<ul>
<li>We’ve moved the <code>&lt;html&gt;</code>, <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> elements to the <code>Main.astro</code> layout.</li>
<li>We’ve also introduced a new <code>&lt;meta name=description /&gt;</code> tag for SEO.</li>
<li>We’ve equally introduced a <code>&lt;main&gt;</code> element where we want the rest of our page to go in.</li>
<li>Note that the file name of the layout is capitalised, that is <code>Main.astro</code>, not <code>main.astro</code>.</li>
</ul>
<p>On the one hand, layouts are unique because they mostly do one thing: provide reusable structures. But, on the other hand, they aren’t unique. They are like other Astro components and can do everything a component can.</p>
<h2 id="heading-how-to-render-components-and-slots">How to Render Components and Slots</h2>
<p>Rendering an Astro component is similar to how you’d attempt to render an HTML element. For example, we’d render a div by writing the following:</p>
<pre><code class="lang-js">&lt;div&gt;
 render something within the div
&lt;/div&gt;
</code></pre>
<p>The same goes for Astro components.</p>
<p>To render the <code>Main.astro</code> component, we’d do something similar:</p>
<pre><code class="lang-js">&lt;Main&gt;
  render something within the Main component
&lt;/Main&gt;
</code></pre>
<p>Let’s put this into practice. We may now use the <code>Main</code> layout in the <code>index.astro</code> page. To do this, we will do the following:</p>
<ul>
<li>Import the <code>Main</code> layout from <code>"../layouts/Main.astro"</code></li>
<li>Substitute the <code>&lt;html&gt;</code>, <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> elements for the <code>&lt;Main&gt;</code> layout in <code>index.astro</code>.</li>
</ul>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> Main <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/Main.astro"</span>;

<span class="hljs-keyword">const</span> profilePicture = <span class="hljs-string">"https://i.imgur.com/JPGFE75.jpg"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"profile"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">{profilePicture}</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"profile__picture"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"Frau Katerina's headshot."</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"100px"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"100px"</span>
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"profile__details"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Frau Katerina<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>VP of Engineering at Goooogle<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
        Helping developers be excellent and succeed at building scalable
        products
      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<p>If we checked our app, we’d have a blank <code>index</code> page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-10-at-17.18.07.png" alt="Blank application page. " width="600" height="400" loading="lazy">
<em>Blank application page.</em></p>
<p>Why’s that?</p>
<p>Unlike HTML elements, the child elements in the <code>&lt;Main&gt;</code> tag aren’t automatically rendered.</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** Child div will not be automatically rendered */</span>}
&lt;Main&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Hello from child<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<p>The <code>&lt;Main&gt;</code> layout component is rendered, and nothing else. The child components aren’t. Hence, the empty page.</p>
<p>To render the child elements of an Astro component, we must specify where to render these using a <code>&lt;slot /&gt;</code> element.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a.png" alt="Injecting child elements into a slot." width="600" height="400" loading="lazy">
<em>Injecting child elements into a slot.</em></p>
<p>Let’s add a <code>&lt;slot&gt;</code> within <code>Main.astro</code> :</p>
<pre><code class="lang-js"><span class="hljs-comment">//...</span>
  &lt;body&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      {/* We want the content of each page to go here */}
       <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span> 
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  &lt;/body&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-10-at-17.19.59.png" alt="Page refactored to use a reusable layout component." width="600" height="400" loading="lazy">
<em>Page refactored to use a reusable layout component.</em></p>
<p>We should now have our page rendered with the reusable layout in place.</p>
<h2 id="heading-capitalising-component-names">Capitalising Component Names</h2>
<p>We’ve capitalised the file name of the <code>Main.astro</code> layout component, but is this important?</p>
<p>Theoretically, the answer to that is no.</p>
<p>We could create a file with a lower cased name, for example <code>mainLayout.astro</code> and import the component as follows:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> Main <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/mainLayout.astro"</span>;
</code></pre>
<p>This is perfectly correct.</p>
<p>But where we encounter issues is if we name the imported component with a lowercase:</p>
<pre><code class="lang-js"><span class="hljs-comment">// main NOT Main</span>
<span class="hljs-keyword">import</span> main <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/mainLayout.astro"</span>;
</code></pre>
<p>In this case, we’ll encounter issues when we attempt to render the component, as the name collides with the standard HTML <code>main</code> element.</p>
<p>For this reason, it’s common practice to capitalise both component file names and the imported variable name.</p>
<h2 id="heading-the-global-style-directive">The Global Style Directive</h2>
<p>The <code>Main</code> layout is in place but doesn’t add much to our page. Let’s start by adding some styles for the headers and also centre the page’s content:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/layouts/Main.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">h1</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">3rem</span>;
    <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1</span>;
  }

  <span class="hljs-selector-tag">h1</span> + <span class="hljs-selector-tag">h2</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.1rem</span>;
    <span class="hljs-attribute">margin-top</span>: -<span class="hljs-number">1.4rem</span>;
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.9</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">400</span>;
  }

  <span class="hljs-selector-tag">main</span> {
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">40rem</span>;
    <span class="hljs-attribute">margin</span>: auto;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>With this, we’ll have the <code>main</code> element centred, but the headers, <code>h1</code> and <code>h2</code> remain unstyled.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-10-at-17.21.33.png" alt="A comparison of the changes before and after the layout component style." width="600" height="400" loading="lazy">
<em>A comparison of the changes before and after the layout component style.</em></p>
<p>This is because styles applied via the <code>&lt;style&gt;</code> tag are locally scoped by default.</p>
<p>Can you tell me why?</p>
<p>The <code>main</code> element resides in the <code>Main</code> layout. But the header <code>h1</code> and <code>h2</code> exist in a different <code>index.astro</code> component.</p>
<p>For our use case, we need global styles.</p>
<p>We need to break out of the default locally scoped styles the Astro component provides, but how do we do this?</p>
<p>Global styles can be a nightmare — except when truly needed. For such cases, Astro provides several solutions. The first is using what’s known as a global style template directive.</p>
<p>I know that sounds like a mouthful! But in simple terms, template directives in Astro are different kinds of HTML attributes that can be used in Astro component templates.</p>
<p>For example, to break out of the default locally scoped <code>&lt;style&gt;</code> behaviour, we can add a <code>is:global</code> attribute as shown below:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">is:global</span>&gt;</span>
 ...
<span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>This will remove the local CSS scoping and make the styles available globally.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-11.06.50.png" alt="Global styles now inlined in the page via <style>. " width="600" height="400" loading="lazy">
<em>Global styles now inlined in the page via &lt;style&gt;.</em></p>
<h2 id="heading-custom-fonts-and-global-css">Custom Fonts and Global CSS</h2>
<p>Base layout components like <code>Main.astro</code> are a great place to have global properties such as global styles and custom fonts.</p>
<p>We’ve added global styles via the <code>is:global</code> template directive. But alternatively, we could have all global styles imported into <code>Main.astro</code> from a <code>global.css</code> file.</p>
<p>In cases where a project requires importing some existing global css file, this is the more straightforward approach.</p>
<p>For example, let’s refactor our project to use <code>global.css</code>. To do so, move the entire CSS content within the <code>&lt;style is:global&gt;</code> element into <code>src/styles/global.css</code>. Then import the styles in the <code>Main.astro</code> component frontmatter:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
---
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/global.css"</span>;
---
</code></pre>
<p>This will load and inject style onto the page.</p>
<p>Now, let’s turn our attention to global fonts.</p>
<p>We will use the Google <a target="_blank" href="https://fonts.google.com/specimen/Inter">Inter</a> font for the project, but how do we do this?</p>
<p>Technically speaking, to add Inter to our project, we must add the <code>&lt;link&gt;</code>s to Inter on every page required.</p>
<p>But instead of repeating ourselves on every page, we can leverage the shared <code>Main.astro</code> layout component.</p>
<p>Go ahead and add the <code>&lt;link&gt;</code>s to the Inter font as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
&lt;html lang=<span class="hljs-string">"en"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    {/** 👀 Look here ... */}
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preconnect"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preconnect"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.gstatic.com"</span> <span class="hljs-attr">crossorigin</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
      <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&amp;display=swap"</span>
      <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span>
  {<span class="hljs-comment">/** ... */</span>}
&lt;/html&gt;
</code></pre>
<p>We may now update the <code>global.css</code> file to use the new font family:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Inter"</span>, sans-serif;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">0.5rem</span>; <span class="hljs-comment">/* Additional body style */</span>
}
</code></pre>
<p>And boom! We have sorted global fonts.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-10-at-17.41.13.png" alt="The page with global fonts and styles. " width="600" height="400" loading="lazy">
<em>The page with global fonts and styles.</em></p>
<h2 id="heading-independent-astro-components">Independent Astro Components</h2>
<p>We’ve discussed two special types of Astro components: layouts and pages.</p>
<p>But a working site is made up of more than just layouts and pages. For example, different blocks of user interfaces are typically embedded within a page. These independent and reusable blocks of user interfaces can also be represented using Astro components.</p>
<p>Let’s put this into practice by creating <code>NavigationBar</code> and <code>Footer</code> components to be used in the <code>Main.astro</code> layout.</p>
<p>When creating components, a standard convention is to have them in the <code>src/components</code> directory. Let’s go ahead and create one.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Footer.astro</span>
&lt;footer&gt;&amp;copy; Frau Katerina&lt;/footer&gt;

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">footer</span> {
    <span class="hljs-comment">/* Applies top and bottom paddings */</span>
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">3rem</span> <span class="hljs-number">0</span>;
    <span class="hljs-comment">/* Centers the text content */</span>
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-comment">/* Makes the font smaller */</span>
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Let’s also create a <code>NavigationBar</code> component:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/NavigationBar.astro</span>
---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
      {/** Link points nowhere for now*/}
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span>Philosophies<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
      {/** Link points nowhere for now*/}
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>&gt;</span>Beyond technology<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">nav</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">align-items</span>: flex-start;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">2rem</span> <span class="hljs-number">0</span>;
  }

  <span class="hljs-selector-tag">ul</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-wrap</span>: wrap;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto <span class="hljs-number">0</span> <span class="hljs-number">0</span>;
  }

  <span class="hljs-selector-tag">nav</span> <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>;
    <span class="hljs-attribute">list-style</span>: none;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.95rem</span>;
  }

  <span class="hljs-selector-tag">a</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
    <span class="hljs-attribute">text-decoration</span>: none;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Now render the <code>NavigationBar</code> and <code>Footer</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
---
<span class="hljs-comment">//...</span>
<span class="hljs-keyword">import</span> Footer <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Footer.astro"</span>;
<span class="hljs-keyword">import</span> NavigationBar <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/NavigationBar.astro"</span>;
---

{<span class="hljs-comment">/** ... **/</span>}
&lt;main&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">NavigationBar</span> /&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Footer</span> /&gt;</span></span>
&lt;/main&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-15.17.48@2x.png" alt="Navigation bar and footer rendered. " width="600" height="400" loading="lazy">
<em>Navigation bar and footer rendered.</em></p>
<h2 id="heading-how-to-add-interactive-scripts">How to Add Interactive Scripts</h2>
<p>An integral part of Astro’s philosophy is shipping zero JavaScript by default to the browser.</p>
<p>This means our pages get compiled into <code>HTML</code> pages with all JavaScript stripped away by default.</p>
<p>You might ask, what about all the JavaScript written in the component script section of an Astro component?</p>
<p>The component script and markup will be used to generate the eventual <code>HTML</code> page(s) sent to the browser.</p>
<p>For example, go ahead and add a simple <code>console.log</code> to the frontmatter of the <code>index.astro</code> page:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello world!"</span>);
---
</code></pre>
<p>Inspect the browser console and notice how the log never makes it to the browser!</p>
<p>So, where’s the log?</p>
<p>Astro runs on the server. In our case, this represents our local development server. So, the <code>console.log</code> will appear in the terminal where Astro serves our local application.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-17.55.33.png" alt="Astro server logs." width="600" height="400" loading="lazy">
<em>Astro server logs.</em></p>
<p>When we eventually build our application for production with <code>npm run build</code>, Astro will output <code>HTML</code> files corresponding to our pages in <code>src/pages</code>.</p>
<p>In this example, the <code>Hello world!</code> message will be logged but not get into the compiled <code>HTML</code> pages.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-18.02.53.png" alt="Logs during building the production application." width="600" height="400" loading="lazy">
<em>Logs during building the production application.</em></p>
<p>To add interactive scripts, that is scripts that make it into the final <code>HTML</code> page build output, add a <code>&lt;script&gt;</code> element in the component markup section.</p>
<p>For example, let’s move the <code>console.log</code> from the frontmatter to the markup via a <code>&lt;script&gt;</code> element:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
--- 
<span class="hljs-comment">// ...</span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello world!"</span>);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>We should have <code>Hello world!</code> logged in the browser console:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-01-at-18.07.13@2x.png" alt="The browser “Hello world”\ log." width="600" height="400" loading="lazy">
<em>The browser “Hello world” log.</em></p>
<h2 id="heading-interactive-theme-toggle">Interactive Theme Toggle</h2>
<p>Let’s put our newly found knowledge of client-side scripts to good use.</p>
<p>Create a new <code>ThemeToggler.astro</code> component in the <code>src/components</code> directory.</p>
<p>Add the following markup:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/ThemeToggler.astro</span>
&lt;button aria-label=<span class="hljs-string">"Theme toggler"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"25px"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"sun"</span>
      <span class="hljs-attr">fill-rule</span>=<span class="hljs-string">"evenodd"</span>
      <span class="hljs-attr">d</span>=<span class="hljs-string">"M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm12-7a.8.8 0 0 1-.8.8h-2.4a.8.8 0 0 1 0-1.6h2.4a.8.8 0 0 1 .8.8zM4 12a.8.8 0 0 1-.8.8H.8a.8.8 0 0 1 0-1.6h2.5a.8.8 0 0 1 .8.8zm16.5-8.5a.8.8 0 0 1 0 1l-1.8 1.8a.8.8 0 0 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM6.3 17.7a.8.8 0 0 1 0 1l-1.7 1.8a.8.8 0 1 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM12 0a.8.8 0 0 1 .8.8v2.5a.8.8 0 0 1-1.6 0V.8A.8.8 0 0 1 12 0zm0 20a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-1.6 0v-2.4a.8.8 0 0 1 .8-.8zM3.5 3.5a.8.8 0 0 1 1 0l1.8 1.8a.8.8 0 1 1-1 1L3.5 4.6a.8.8 0 0 1 0-1zm14.2 14.2a.8.8 0 0 1 1 0l1.8 1.7a.8.8 0 0 1-1 1l-1.8-1.7a.8.8 0 0 1 0-1z"</span>
    &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">path</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">path</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"moon"</span>
      <span class="hljs-attr">fill-rule</span>=<span class="hljs-string">"evenodd"</span>
      <span class="hljs-attr">d</span>=<span class="hljs-string">"M16.5 6A10.5 10.5 0 0 1 4.7 16.4 8.5 8.5 0 1 0 16.4 4.7l.1 1.3zm-1.7-2a9 9 0 0 1 .2 2 9 9 0 0 1-11 8.8 9.4 9.4 0 0 1-.8-.3c-.4 0-.8.3-.7.7a10 10 0 0 0 .3.8 10 10 0 0 0 9.2 6 10 10 0 0 0 4-19.2 9.7 9.7 0 0 0-.9-.3c-.3-.1-.7.3-.6.7a9 9 0 0 1 .3.8z"</span>
    &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">path</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span></span>
&lt;/button&gt;
</code></pre>
<ul>
<li>For accessibility, the button has an <code>aria-label</code> of <code>Theme toggler</code>.</li>
<li>The <code>SVG</code> has a fixed width of <code>25px</code>, rendering two <code>&lt;path&gt;</code> elements.</li>
<li>The first <code>&lt;path&gt;</code> visually represents a sun icon. The second is a moon icon.</li>
<li>By default, both icons (sun and moon) are rendered. Our goal is to toggle the displayed icon based on the active theme.</li>
</ul>
<p>Then import the component and render it in the <code>NavigationBar</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/NavigationBar</span>
---
<span class="hljs-keyword">import</span> ThemeToggler <span class="hljs-keyword">from</span> <span class="hljs-string">"./ThemeToggler.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    {/** ... **/}
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  {/** 👀 Look here **/}
  <span class="hljs-tag">&lt;<span class="hljs-name">ThemeToggler</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-06.43.28.png" alt="The sun and moon icons rendered in the toggle button." width="600" height="400" loading="lazy">
<em>The sun and moon icons rendered in the toggle button.</em></p>
<p>Let’s add some <code>&lt;style&gt;</code> to <code>ThemeToggler</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/ThemeToggler.astro</span>
<span class="hljs-comment">// ... </span>
&lt;style&gt;
  button {
    <span class="hljs-attr">cursor</span>: pointer;
    border-radius: <span class="hljs-number">10</span>px;
    border: <span class="hljs-number">0</span>;
    padding: <span class="hljs-number">5</span>px <span class="hljs-number">10</span>px;
    transition: all <span class="hljs-number">0.2</span>s ease-<span class="hljs-keyword">in</span>-out;
  }

  <span class="hljs-attr">button</span>:hover {
    <span class="hljs-comment">/* Make the button smaller (scale down) when hovered */</span>
    <span class="hljs-attr">transform</span>: scale(<span class="hljs-number">0.9</span>);
  }

  <span class="hljs-attr">button</span>:active {
    <span class="hljs-comment">/** Return the button to its standard size when active */</span>
    <span class="hljs-attr">transform</span>: scale(<span class="hljs-number">1</span>);
  }

  .sun {
    <span class="hljs-comment">/* Hide the sun icon by default. This assumes a light theme by default */</span>
    <span class="hljs-attr">fill</span>: transparent;
  }
&lt;/style&gt;
</code></pre>
<p>Now, we should have a decent-looking theme toggler.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-06.50.49.png" alt="A styled theme toggle button." width="600" height="400" loading="lazy">
<em>A styled theme toggle button.</em></p>
<h2 id="heading-the-global-selector">The <code>:global()</code> Selector</h2>
<p>Let’s take a moment to consider the strategy we’ll use for toggling the theme.</p>
<p>We’ll toggle a CSS class on the root element whenever a user clicks the toggle.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/embed.png" alt="Adding a new “dark” class on toggle." width="600" height="400" loading="lazy">
<em>Adding a new “dark” class on toggle.</em></p>
<p>For example, if the user was viewing the site in light mode and clicked to toggle, we’ll add a <code>.dark</code> class to the root element and, based on that, apply dark-themed styles.</p>
<p>If the user is in dark mode, clicking the toggle will remove the <code>.dark</code> class. We’ll refer to this as a class strategy for toggling dark mode.</p>
<p>Based on this strategy, we must update our local <code>ThemeToggler</code> style to display the relevant icon depending on the global <code>.dark</code> class.</p>
<p>To do this, we will leverage the <code>:global</code> selector.</p>
<p>Here’s how we’d achieve this:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/ThemeToggler.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
 <span class="hljs-comment">/**...**/</span>

 <span class="hljs-comment">/** If a parent element has a .dark class, target the .sun icon and make the path black (shows the icon) */</span>
 <span class="hljs-selector-pseudo">:global(.dark)</span> <span class="hljs-selector-class">.sun</span> {
   <span class="hljs-attribute">fill</span>: black;
 }

 <span class="hljs-comment">/** If a parent element has a .dark class, target the .moon icon and make the path transparent (hides the icon) */</span>
 <span class="hljs-selector-pseudo">:global(.dark)</span> <span class="hljs-selector-class">.moon</span> {
   <span class="hljs-attribute">fill</span>: transparent;
 }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>To see this at work, inspect the page via the developer tools, and add a <code>dark</code> class to the root element. The toggle icon will be appropriately changed.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-07.03.08.png" alt="Inspecting icon change with a root dark class." width="600" height="400" loading="lazy">
<em>Inspecting icon change with a root dark class.</em></p>
<p>In practice, limit <code>:global</code> only to appropriate use cases, because mixing global and locally scoped component styles will become challenging to debug. But this is permissible, given our use case.</p>
<h2 id="heading-event-handling">Event Handling</h2>
<p>We’ve handled the styles for our toggle, assuming a <code>.dark</code> root class. Now, let’s go ahead and handle the toggle click event with a <code>&lt;script&gt;</code> element.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/ThemeToggler.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-comment">/** Represent the toggle theme class with a variable */</span>
  <span class="hljs-keyword">const</span> DARK_THEME_CLASS = <span class="hljs-string">"dark"</span>;

  <span class="hljs-comment">/** Grab the toggle */</span>
  <span class="hljs-keyword">const</span> toggle = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"button"</span>);
  <span class="hljs-comment">/** Grab the document root element. In this case &lt;html&gt;  */</span>
  <span class="hljs-keyword">const</span> rootEl = <span class="hljs-built_in">document</span>.documentElement;

  <span class="hljs-keyword">if</span> (toggle) {
    toggle.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">/** toggle the "dark" class on the root element */</span>
      rootEl.classList.toggle(DARK_THEME_CLASS);
    });
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Notice that this is standard JavaScript. Nothing fancy going on here.</p>
<ul>
<li>The toggle is selected via <code>document.querySelector("button")</code>.</li>
<li>To set up an event listener, we use the <code>.addEventListener</code> method on the button.</li>
<li>On clicking the button, we toggle the class list on the root element: adding or removing the “dark” class.</li>
</ul>
<p>With this in place, the toggle icon changes when clicked to either that of the sun or moon.</p>
<p>Excellent!</p>
<h2 id="heading-theming-via-css-variables">Theming via CSS Variables</h2>
<p>CSS variables are outstanding, and we’ll leverage them for theming our application.</p>
<p>Firstly, let’s go ahead and define the colour variables we’ll use in the project.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 styles/global.css</span>
html {
  --background: white;
  --grey<span class="hljs-number">-200</span>: #<span class="hljs-number">222222</span>;
  --grey<span class="hljs-number">-400</span>: #<span class="hljs-number">444444</span>;
  --grey<span class="hljs-number">-600</span>: #<span class="hljs-number">333333</span>;
  --grey<span class="hljs-number">-900</span>: #<span class="hljs-number">111111</span>;
}

html.dark {
  --background: black;
  --grey<span class="hljs-number">-200</span>: #eaeaea;
  --grey<span class="hljs-number">-400</span>: #acacac;
  --grey<span class="hljs-number">-600</span>: #ffffff;
  --grey<span class="hljs-number">-900</span>: #fafafa;
}
</code></pre>
<ul>
<li>Set the variables on the root <code>HTML</code> element to be globally scoped.</li>
<li>A CSS variable is a property that begins with two dashes, <code>--</code>  – for example <code>--background</code>.</li>
<li>For simplicity, we’ll stick to the minimal grey palette above.</li>
</ul>
<p>The first visual change we’ll make is to add the following <code>color</code> and <code>background</code> style declarations to the <code>body</code> element:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 styles/global.css</span>
body {
  <span class="hljs-attr">color</span>: <span class="hljs-keyword">var</span>(--grey<span class="hljs-number">-600</span>);
  background: <span class="hljs-keyword">var</span>(--background);
}
</code></pre>
<p>With this seemingly simple change, we should now have the text and background colour of the <code>body</code> react to clicking the toggle.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-07.51.51.png" alt="Dark mode activated. " width="600" height="400" loading="lazy">
<em>Dark mode activated.</em></p>
<p>Finally, update the navigation links in <code>NavigationBar</code> to reflect theme preferences:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* 📂 src/components/NavigationBar.astro */</span>
&lt;<span class="hljs-selector-tag">style</span>&gt;
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-selector-tag">a</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--grey-<span class="hljs-number">400</span>);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
    <span class="hljs-attribute">text-decoration</span>: none;
  }

  <span class="hljs-selector-tag">a</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--grey-<span class="hljs-number">900</span>);
  }
&lt;/<span class="hljs-selector-tag">style</span>&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-07.55.56.png" alt="Navigation links styled for dark mode." width="600" height="400" loading="lazy">
<em>Navigation links styled for dark mode.</em></p>
<h2 id="heading-how-to-access-global-client-objects">How to Access Global Client Objects</h2>
<p>Question! 🙋🏼</p>
<p>Where should we access global objects such as <code>window.localStorage</code>? Within an Astro component frontmatter or an interactive <code>&lt;script&gt;</code>?</p>
<p>At this point, I hope the answer to the question is clear from previous examples.</p>
<p>Since Astro runs on the server, attempting to access a <code>window</code> property within the frontmatter of a component will result in an error.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">---</span>
{<span class="hljs-comment">/** ❌ this will fail with the error: window is undefined **/</span>}
 <span class="hljs-selector-tag">const</span> <span class="hljs-selector-tag">value</span> = <span class="hljs-selector-tag">window</span><span class="hljs-selector-class">.localStorage</span><span class="hljs-selector-class">.getItem</span>("<span class="hljs-selector-tag">value</span>")
<span class="hljs-selector-tag">---</span>
</code></pre>
<p>To access <code>window</code> properties, we need the script to run on the client – that is, in the browser. So, we must leverage one or more client-side scripts.</p>
<p>A good use case for this is remembering the user’s theme choice.</p>
<p>If users toggle their theme from light to dark and refresh the browser, they lose the selected theme state.</p>
<p>How about we save this state to the browser’s local storage and restore the selected theme upon refresh?</p>
<p>Well, let’s do that!</p>
<p>Here are the first steps we’ll take:</p>
<ul>
<li>Grab the current state of the theme, that is dark or light, when the theme toggle is clicked.</li>
<li>Save the theme value to the browser’s local storage in the form: </li>
</ul>
<pre><code>{
  <span class="hljs-attr">COLOUR_MODE</span>: <span class="hljs-string">"LIGHT"</span> | <span class="hljs-string">"DARK"</span>    
}
</code></pre><p>Here’s that translated in code:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/ThemeToggler.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> DARK_THEME_CLASS = <span class="hljs-string">"dark"</span>;
  <span class="hljs-comment">/** Represent the local storage key by a variable */</span>
  <span class="hljs-keyword">const</span> COLOUR_MODE = <span class="hljs-string">"COLOUR_MODE"</span>;
  <span class="hljs-comment">/** Represent the local storage values by variables */</span>
  <span class="hljs-keyword">const</span> LIGHT_THEME = <span class="hljs-string">"LIGHT"</span>;
  <span class="hljs-keyword">const</span> DARK_THEME = <span class="hljs-string">"DARK"</span>;
  <span class="hljs-comment">/** ... **/</span>
  toggle.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">/** ... */</span>
    <span class="hljs-comment">/**Get the current theme mode, i.e., light or dark */</span>
    <span class="hljs-keyword">const</span> colourMode = rootEl.classList.contains(DARK_THEME_CLASS)
      ? DARK_THEME
      : LIGHT_THEME;

    <span class="hljs-comment">/** Save the current theme to local storage   */</span>
    <span class="hljs-built_in">window</span>.localStorage.setItem(COLOUR_MODE, colourMode);
  });
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>We have saved the theme to local storage but must now set the active theme as soon as the page is loaded and the <code>script</code> is executed.</p>
<p>Here’s the annotated code required to achieve this:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/ThemeToggler.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  {<span class="hljs-comment">/**... **/</span>}
  <span class="hljs-keyword">const</span> getInitialColourMode = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">/** Get colour mode from local storage **/</span>
    <span class="hljs-keyword">const</span> previouslySavedColourMode = <span class="hljs-built_in">window</span>.localStorage.getItem(COLOUR_MODE);
    <span class="hljs-keyword">if</span> (previouslySavedColourMode) {
      <span class="hljs-keyword">return</span> previouslySavedColourMode;
    }
    <span class="hljs-comment">/** Does the user prefer dark mode, e.g., through an operating system or user agent setting? */</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.matchMedia(<span class="hljs-string">"(prefers-color-scheme: dark)"</span>).matches) {
      <span class="hljs-keyword">return</span> DARK_THEME;
    }
    <span class="hljs-comment">/** Default to the light theme */</span>
    <span class="hljs-keyword">return</span> LIGHT_THEME;
  };
  <span class="hljs-comment">/**Get initial colour mode */</span>
  <span class="hljs-keyword">const</span> initialColourMode = getInitialColourMode();
  <span class="hljs-keyword">const</span> setInitialColourMode = <span class="hljs-function">(<span class="hljs-params">mode: string</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (mode === LIGHT_THEME) {
      rootEl.classList.remove(DARK_THEME_CLASS);
    } <span class="hljs-keyword">else</span> {
      rootEl.classList.add(DARK_THEME_CLASS);
    }
  };
  <span class="hljs-comment">/** Set the initial colour mode as soon as the script is executed */</span>
  setInitialColourMode(initialColourMode);
{<span class="hljs-comment">/**... **/</span>}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Now, give this a try. First, toggle the theme and refresh to see the theme choice preserved.</p>
<h2 id="heading-the-magic-of-scripts">The Magic of Scripts</h2>
<p>Client-side scripts added via a <code>&lt;script&gt;</code> may seem like your typical vanilla JavaScript, but they’re more capable in specific ways.</p>
<p>The most crucial point is that Astro processes these. This means within a <code>&lt;script&gt;</code>, we can import other scripts or import npm packages, and Astro will resolve and package the script for use in the browser.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
 <span class="hljs-comment">/** ✅ valid package import **/</span>
 <span class="hljs-keyword">import</span> { titleCase } <span class="hljs-keyword">from</span> <span class="hljs-string">"title-case"</span>;

 <span class="hljs-keyword">const</span> title = titleCase(<span class="hljs-string">"string"</span>) 

 alert(title)
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<pre><code class="lang-html">/** ✅ valid script reference **/
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"path-to-script.js"</span>/&gt;</span>
</code></pre>
<p>Another critical point is the <code>&lt;script&gt;</code> fully supports TypeScript. For example, in our solution, we typed the parameter for the <code>setInitialColourMode</code> function:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// mode is of type string </span>
<span class="hljs-keyword">const</span> setInitialColourMode = <span class="hljs-function">(<span class="hljs-params">mode: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  ...
};
</code></pre>
<p>We don’t have to sacrifice type safety within the client <code>&lt;script&gt;</code> elements and can go on to write standard TypeScript code. Astro will strip out the types at build time and only serve the processed JavaScript to the browser.</p>
<p>Here’s a summary of what Astro does:</p>
<ul>
<li><code>NPM</code> packages and local files can be imported and will be bundled.</li>
<li>TypeScript is fully supported within the <code>&lt;script&gt;</code>.</li>
<li>If a single <code>Astro</code> component with a <code>&lt;script&gt;</code> is used multiple times on a page, the <code>&lt;script&gt;</code> is processed and only included once.</li>
<li>Astro will process and insert the script in the <code>&lt;head&gt;</code> of the page with a <code>type=module</code> attribute.</li>
<li>❗️The implication of <code>type=module</code> is that the browser will defer the script, that is load in parallel and <strong>execute it only after</strong> the page’s parsed.</li>
</ul>
<h2 id="heading-how-to-leverage-inline-scripts">How to Leverage Inline Scripts</h2>
<p>By default, Astro processes <code>&lt;script&gt;</code>s. However, to opt out of Astro’s default script processing, we may pass a <code>is:inline</code> directive as shown below:</p>
<pre><code class="lang-ts">&lt;script is:inline&gt; 
 <span class="hljs-comment">// Imports will not be processed </span>
 <span class="hljs-comment">// Typescript not supported by default </span>
 <span class="hljs-comment">// Script will be added as is, e.g., multiple times if the component is used more than once on a page. </span>
&lt;/script&gt;
</code></pre>
<p>In the real world, we quickly realise that the defaults don’t always satisfy every project requirement.</p>
<p>For example, consider the un-styled flash of incorrect theme when we refresh our home page. For a user who chose the dark theme previously, refreshing the page shows light-themed rendered content before changing to dark after the script is parsed.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-03.24.42.png" alt="Transitioning light themed content viewed on Regular 3G throttling." width="600" height="400" loading="lazy">
<em>Transitioning light themed content viewed on Regular 3G throttling.</em></p>
<p>This occurs because we restore the user-chosen theme only after the page’s HTML has been parsed, that is the default behaviour of processed Astro scripts.</p>
<p>To prevent this, we will use the <code>is:inline</code> directive, which will make the script blocking, that is, it'll be executed immediately and stops parsing until completed.</p>
<p>Since scripts with the <code>is:inline</code> attribute aren’t processed, they’ll be added multiple times if used in reusable components that appear more than once on the page.</p>
<p>So, let’s go ahead and move the theme restoration code bit into <code>Main.astro</code> — because the <code>Main</code> layout is only included once per page.</p>
<p>We’ll also make sure to add this within the <code>&lt;head&gt;</code> of the layout, as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/layouts/Main.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> 
   <span class="hljs-comment">&lt;!-- ... --&gt;</span>    
    <span class="hljs-comment">&lt;!-- 👀 add is:inline --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">is:inline</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">const</span> DARK_THEME_CLASS = <span class="hljs-string">"dark"</span>;
      <span class="hljs-keyword">const</span> COLOUR_MODE = <span class="hljs-string">"COLOUR_MODE"</span>;
      <span class="hljs-keyword">const</span> LIGHT_THEME = <span class="hljs-string">"LIGHT"</span>;
      <span class="hljs-keyword">const</span> DARK_THEME = <span class="hljs-string">"DARK"</span>;
      <span class="hljs-keyword">const</span> rootEl = <span class="hljs-built_in">document</span>.documentElement;
      <span class="hljs-keyword">const</span> getInitialColourMode = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">/** ... */</span>
      }
      <span class="hljs-keyword">const</span> initialColourMode = getInitialColourMode();
      <span class="hljs-comment">// 👀 remove string type on mode </span>
      <span class="hljs-keyword">const</span> setInitialColourMode = <span class="hljs-function">(<span class="hljs-params">mode</span>) =&gt;</span> {
         <span class="hljs-comment">/** ... */</span>
      };
      <span class="hljs-comment">/** Set the initial colour mode as soon as the script is executed */</span>
      setInitialColourMode(initialColourMode);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
</code></pre>
<p>We’re explicitly adding this to the <code>&lt;head&gt;</code> because Astro will not process the <code>is:inline</code> script. As such, it won’t be moved to the <code>head</code> by Astro.</p>
<p>Be careful with <code>is:inline</code> as it removes the default non-blocking nature of scripts. But it’s ideal for this use case.</p>
<p>Open your developer tools and throttle the network. Then go ahead and refresh after toggling dark mode. We should have eradicated the flash of incorrect theme:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-07.30.21@2x.png" alt="Throttling the network via the chrome developer tools." width="600" height="400" loading="lazy">
<em>Throttling the network via the chrome developer tools.</em></p>
<h2 id="heading-global-selectors-in-scripts">Global Selectors in Scripts</h2>
<p>Understanding how Astro processes the <code>&lt;script&gt;</code> in our components helps us make informed decisions.</p>
<p>We know the <code>&lt;script&gt;</code> will eventually be bundled and injected into our page’s <code>&lt;head&gt;</code>.</p>
<p>But consider our selector for registering the theme toggle clicks:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// 📂 src/components/ThemeToggler.astro </span>
<span class="hljs-keyword">const</span> toggle = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"button"</span>);
</code></pre>
<p>The problem with this seemingly harmless code is that <code>document.querySelector</code> will return the first element that matches the selector — a button element.</p>
<p>This will be selected if we add a random button somewhere on the page before our theme toggle button.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
&lt;button&gt; Donate to charity &lt;/button&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Nav</span> /&gt;</span></span>

<span class="hljs-comment">//...</span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-03.38.21.png" alt="The donate to charity button." width="600" height="400" loading="lazy">
<em>The donate to charity button.</em></p>
<p>This button, which has nothing to do with theme toggling, will now be responsible for toggling the user’s theme.</p>
<p>Clicking “donate to charity” now toggles the theme. This is unacceptable.</p>
<p>The lesson here is to be mindful of your DOM selectors and be specific where possible, for example via ids or classes:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#some-unique-id"</span>)
</code></pre>
<p>Let’s refactor our solution to use a data attribute.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/ThemeToggler.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Theme toggler"</span> <span class="hljs-attr">data-theme-toggle</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-comment">/** 👀 Look here */</span>
  <span class="hljs-keyword">const</span> toggle = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"[data-theme-toggle]"</span>);
  <span class="hljs-comment">// ... </span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>With the more specific selector, only an element with the data attribute <code>theme-toggle</code> will be selected, leaving <code>&lt;button&gt; Donate to charity &lt;/button&gt;</code> out of our theme toggle business.</p>
<h2 id="heading-markdown-pages">Markdown Pages</h2>
<p>We’ve established that not all file types are valid pages in Astro. We’ve seen Astro components as pages, but allow me to introduce markdown pages.</p>
<p>Markdown is a popular, easy-to-use markup language for creating formatted text. I’m sure my nan does not know markdown, so it’s safer to say it’s a famous text format among developers.</p>
<p>It’s no surprise Astro supports creating pages via markdown. So, let’s put this to the test.</p>
<p>We’ll create two new pages to replace our dead <code>Philosophies</code> and <code>Beyond technology</code> navigation links.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-02-at-10.50.19@2x.png" alt="The dead navigation links." width="600" height="400" loading="lazy">
<em>The dead navigation links.</em></p>
<p>Create the first page in <code>src/pages/philosophies.md</code> with the following content:</p>
<pre><code class="lang-md"><span class="hljs-bullet">-</span> Be present and enjoy the now
<span class="hljs-bullet">-</span> Be driven by values
<span class="hljs-bullet">-</span> Health is wealth
<span class="hljs-bullet">-</span> Be deliberate
<span class="hljs-bullet">-</span> Laugh out loud
</code></pre>
<p>Create the second page in <code>src/pages/beyond-tech.md</code> with the following content:</p>
<pre><code class="lang-md"><span class="hljs-bullet">-</span> 5X Marathoner
<span class="hljs-bullet">-</span> Olympic gold medalist
<span class="hljs-bullet">-</span> Fashion model
<span class="hljs-bullet">-</span> Michellin star restaurant owner
<span class="hljs-bullet">-</span> Adviser to the vice president
</code></pre>
<p>These files are written in markdown syntax.</p>
<p>As with Astro component pages, markdown pages eventually get compiled to standard <code>HTML</code> pages rendered in the browser. The same file-based routing is also used. For example, to access the <code>philosophies</code> and <code>beyond-tech</code> pages, visit the <code>/philosophies</code> and <code>/beyond-tech</code>  routes, respectively.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-02.42.23.png" alt="The philosophies page" width="600" height="400" loading="lazy">
<em>The philosophies page</em></p>
<h2 id="heading-how-to-navigate-between-pages">How to Navigate Between Pages</h2>
<p>Navigating between pages in Astro requires no magic wand. Surprise!</p>
<p>Astro uses the standard <code>&lt;a&gt;</code> element to navigate between pages. This makes sense as each page is a separate <code>HTML</code> page.</p>
<p>Let’s update the navigation links to point to the new markdown pages as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 NavigationBar.astro --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/philosophies"</span>&gt;</span>Philosophies<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/beyond-tech"</span>&gt;</span>Beyond technology<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
</code></pre>
<p>Clicking any of these links should now lead us to their appropriate pages.</p>
<h2 id="heading-markdown-layouts">Markdown Layouts</h2>
<p>Let’s face it – we won’t be winning any design awards for our current markdown pages. This is because they seem off and don’t share the same layout as our existing page. Can we fix this?</p>
<p>You’ve probably realised I ask questions and then provide answers. All right, you’ve got me. So that’s my trick to make you think about a problem — hoverer brief — before explaining the solution.</p>
<p>Believe it or not, Astro component frontmatter was inspired by markdown. The original markdown syntax supports frontmatter for providing metadata about the document. For example, we could add a <code>title</code> metadata as shown below:</p>
<pre><code class="lang-ts">---
title: Understanding Astro
---
</code></pre>
<p>This is excellent news because Astro leverages this to provide layouts for markdown pages.</p>
<p>Instead of the <em>so dull I can’t take it</em> page, we can utilise a layout to bring some reusable structure to all our markdown pages.</p>
<p>Let’s get started.</p>
<p>With Astro markdown pages, we can provide layouts for a markdown page by providing a layout frontmatter metadata as shown below:</p>
<pre><code class="lang-ts">---
layout: path-to-layout
---
</code></pre>
<p>First, let’s reuse the same <code>Main</code> layout by adding the following to both markdown pages:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// add at the top of the Markdown pages.</span>
---
layout: ../layouts/Main.astro
---
</code></pre>
<p>The markdown pages should now reuse our existing layout with the theming, navigation and footer all set in place.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-04.40.15.png" alt="Using the Main layout in the markdown pages. " width="600" height="400" loading="lazy">
<em>Using the Main layout in the markdown pages.</em></p>
<p>Since <code>Main.astro</code> includes our <code>global.css</code> files, let’s go ahead and provide some default global styles for paragraphs and lists:</p>
<pre><code class="lang-css">{<span class="hljs-comment">/** 📂 src/styles/global.css **/</span>}
<span class="hljs-selector-tag">p</span>,
<span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--gray-<span class="hljs-number">400</span>);
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>;
}

<span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-07.51.10@2x.png" alt="Global list styles are now applied to the Markdown pages." width="600" height="400" loading="lazy">
<em>Global list styles are now applied to the Markdown pages.</em></p>
<p>We should now have these styles take effect on our markdown pages! Isn’t life better with shared layout components? 😉</p>
<h2 id="heading-how-to-compose-layouts">How to Compose Layouts</h2>
<p>Layouts are Astro components, meaning we can compose them – that is, render one layout in another.</p>
<p>For example, let’s create a separate <code>Blog.astro</code> layout that composes our base <code>Main.astro</code> layout.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Blog.astro</span>
---
<span class="hljs-keyword">import</span> Main <span class="hljs-keyword">from</span> <span class="hljs-string">"./Main.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<p>Composing the layouts in this way means we can reuse all the good stuff in <code>Main.astro</code> while extending <code>Blog.astro</code> to include only blog-specific elements.</p>
<p>The separation of concern significantly improves legibility and forces each layout to have a single responsibility.</p>
<p>Now, at this point, the markdown pages have the same layout markup and styles from <code>Main.astro</code>. We’ve made no customisations. But we can already change the <code>beyond-tech</code> and <code>philosophies</code> pages to use the new <code>Blog.astro</code> layout as shown below:</p>
<pre><code class="lang-md">---
<span class="hljs-section">layout: ../layouts/Blog.astro
---</span>
</code></pre>
<h2 id="heading-component-props">Component Props</h2>
<p>As we build reusable components, we often find situations where we must customise certain values within a component. For example, consider the <code>&lt;title&gt;</code> in our <code>Main.astro</code> layout component:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
&lt;title&gt;Astro&lt;/title&gt;
</code></pre>
<p>A hardcoded <code>title</code> on every page where the <code>Main</code> layout is used is ridiculous.</p>
<p>To foster reusability, components can accept properties. These are commonly known as <strong>props</strong>.</p>
<p>Props are passed to components as attributes.</p>
<pre><code class="lang-js">&lt;Main title=<span class="hljs-string">"Some title"</span> /&gt;
</code></pre>
<p>The prop values are then accessed via <code>Astro.props</code>. This is better explained with an example.</p>
<p>Go ahead and update <code>Main</code> to accept a <code>title</code> prop as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro </span>
--- 
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">const</span> { title } = Astro.props;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    {/** ... **/}
    {/** 👀 look here **/}
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
     {/** ... **/}
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>To enforce TypeScript checks, define the <code>Props</code> type alias or interface.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Either of these is valid </span>
type Props = {
  <span class="hljs-attr">title</span>: string 
}

interface Props {
  <span class="hljs-attr">title</span>: string 
}
</code></pre>
<p>For simplicity, I’ll stick to a type alias for the <code>Main</code> layout:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Main.astro</span>
---
type Props = {
  <span class="hljs-attr">title</span>: string 
}

<span class="hljs-keyword">const</span> { title } = Astro.props;
---
<span class="hljs-comment">// ...</span>
</code></pre>
<p>With the type declared, we’ll have TypeScript error(s) in files where we’ve used <code>&lt;Main&gt;</code> without the required <code>title</code> prop.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-06.01.52.png" alt="Invalid title props error. " width="600" height="400" loading="lazy">
<em>Invalid title props error.</em></p>
<p>Update the <code>index.astro</code> and <code>Blog.astro</code> pages to pass a <code>title</code> prop to <code>Main</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/index.astro</span>
&lt;Main title=<span class="hljs-string">"Frau Katarina"</span>&gt; 
{<span class="hljs-comment">/* ... */</span>}
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/Blog.astro</span>
&lt;Main title=<span class="hljs-string">"Frau Katarina | Blog"</span>&gt;
{<span class="hljs-comment">/* ... */</span>}
</code></pre>
<h2 id="heading-how-to-leverage-markdown-frontmatter-properties">How to Leverage Markdown Frontmatter Properties</h2>
<p>All markdown pages in our application will have a title, subtitle, and poster. Luckily, a great way to represent these is via frontmatter properties.</p>
<p>Update the markdown pages to now include these properties, as shown below.</p>
<p><code>📂 src/pages/beyond-tech.md</code>:</p>
<pre><code class="lang-md">---
layout: ../layouts/Blog.astro
poster: "/images/road-trip.jpg"
title: "Beyond Technology"
<span class="hljs-section">subtitle: "Humans are multi-faceted. Beyond tech, I indulge in the following:"
---</span>
...
</code></pre>
<p><code>📂 src/pages/philosophies.md</code>:</p>
<pre><code class="lang-md">---
layout: ../layouts/Blog.astro
poster: "/images/philosophies.jpg"
title: "My Guiding Philosophies"
<span class="hljs-section">subtitle: "These are the philosophies that guide every decision and action I make."
---</span>
...
</code></pre>
<p>Note that <code>poster</code> points to image paths. These paths reference the <code>public</code> directory. So <code>/images/philosophies.jpg</code> points to an image in <code>public/images/philosophies.jpg</code>.</p>
<p>If you’re coding along, feel free to download any image from Unsplash and move them to the <code>public</code> directory.</p>
<p>Adding metadata to our markdown pages doesn’t do us any good if we can't use them.</p>
<p>Luckily, markdown layouts have a unique superpower — they can access markdown frontmatter via <code>Astro.props.frontmatter</code>.</p>
<p>Let’s go ahead and globally handle this in our <code>Blog.astro</code> layout component. Below’s the component script section:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// 📂 src/layouts/Blog.astro </span>
---
<span class="hljs-comment">// import the type utility for the markdown layout props</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { MarkdownLayoutProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;
<span class="hljs-comment">// import the base layout: Main.astro</span>
<span class="hljs-keyword">import</span> Main <span class="hljs-keyword">from</span> <span class="hljs-string">"./Main.astro"</span>;

<span class="hljs-comment">// defined the Props type </span>
<span class="hljs-keyword">type</span> Props = MarkdownLayoutProps&lt;{
  <span class="hljs-comment">// Define the expected frontmatter props here</span>
  title: <span class="hljs-built_in">string</span>;
  poster: <span class="hljs-built_in">string</span>;
  subtitle: <span class="hljs-built_in">string</span>;
}&gt;;

<span class="hljs-comment">// get properties from the markdown frontmatter</span>
<span class="hljs-keyword">const</span> { poster, title, subtitle } = Astro.props.frontmatter;
---
</code></pre>
<ul>
<li>The <code>MarkdownLayoutProps</code> utility type accepts a generic and returns the type for all the properties available to a markdown layout. So feel free to inspect the entire shape.</li>
<li><code>MarkdownLayoutProps</code> accepts our frontmatter property type definition as a generic, that is <code>title</code>, <code>poster</code> and <code>subtitle</code>. These are properties we’ve added in the frontmatter of our Markdown pages.</li>
<li><code>type Props = ...</code> or <code>interface Props {}</code> is how we provide types for an Astro component.</li>
<li>The final line deconstructs the properties from <code>Astro.props.frontmatter</code> with full TypeScript support.</li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-05.16.20.png" alt="Typescript support in the Markdown layout." width="600" height="400" loading="lazy">
<em>Typescript support in the Markdown layout.</em></p>
<p>Equally update the layout markup to render the image, title, and subtitle:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/layouts/Blog.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">figure</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"figure"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">{poster}</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">"480px"</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"figure__image"</span>
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">figcaption</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"figure__caption"</span>&gt;</span>
      Poster image for {title.toLowerCase()}
    <span class="hljs-tag">&lt;/<span class="hljs-name">figcaption</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">figure</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{subtitle}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">h1</span> + <span class="hljs-selector-tag">h2</span> {
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">3rem</span>;
  }

  <span class="hljs-selector-class">.figure</span> {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  }

  <span class="hljs-selector-class">.figure__image</span> {
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">100%</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
  }

  <span class="hljs-selector-class">.figure__caption</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>Most of the markup is arguably standard. However, note the <code>title.toLowerCase()</code> call for the poster image caption. This is possible because any valid JavaScript expression can be evaluated within curly braces <code>{ }</code> in the component markup.</p>
<p>Our markdown pages will now have styled titles, subtitles, and poster images. With all this handled in one place — the markdown layout.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-05.19.26.png" alt="The fully formed Markdown page." width="600" height="400" loading="lazy">
<em>The fully formed Markdown page.</em></p>
<h2 id="heading-interactive-navigation-state">Interactive Navigation State</h2>
<p>Now that we’re pros at handling interactive scripts in Astro let’s go ahead and make sure that we style our active navigation links differently.</p>
<p>As with all things programming, there are different ways to achieve this, but we will go ahead and script this.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/components/NavigationBar.astro --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> { pathname } = <span class="hljs-built_in">window</span>.location;
  <span class="hljs-keyword">const</span> activeNavigationElement = <span class="hljs-built_in">document</span>.querySelector(
    <span class="hljs-string">`nav a[href="<span class="hljs-subst">${pathname}</span>"]`</span>
  );

  <span class="hljs-keyword">if</span> (activeNavigationElement) {
    activeNavigationElement.classList.add(<span class="hljs-string">"active"</span>);
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<ul>
<li>Get the <code>pathname</code> from the <code>location</code> object. This will be in the form <code>"/beyond-tech"</code>, <code>"/philosophies</code> or <code>"/"</code>.</li>
<li>Since the <code>pathname</code> corresponds to the <code>href</code> on the anchor tag element, we may select the active anchor tag via: <code>document.querySelector(</code>nav a[href="${pathname}"]<code>).</code></li>
<li>Finally, we add the <code>active</code> class to the active anchor tag.</li>
</ul>
<p>Finally, add the relevant style for the active tag:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* 📂 src/components/NavigationBar.astro */</span>
&lt;<span class="hljs-selector-tag">style</span>&gt;
  <span class="hljs-comment">/* ... */</span>
 <span class="hljs-selector-tag">a</span><span class="hljs-selector-class">.active</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">var</span>(--grey-<span class="hljs-number">900</span>);
  <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--background);
 }
&lt;/<span class="hljs-selector-tag">style</span>&gt;
</code></pre>
<p>Violà! We should now have the active anchor tag styled differently.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-03-at-09.44.02.png" alt="Active anchor tag styles." width="600" height="400" loading="lazy">
<em>Active anchor tag styles.</em></p>
<h2 id="heading-component-composition">Component Composition</h2>
<p>Our first look at component composition was with the <code>Main</code> and <code>Blog</code> layouts. Let’s take this further.</p>
<p>Our goal is to create a set of different yet identical cards. Each card acts as a link to a blog and will have a title and some background gradient.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-04-at-09.44.58.png" alt="The eventual card layout we will build." width="600" height="400" loading="lazy">
<em>The eventual card layout we will build.</em></p>
<p>To achieve this, we’ll have a <code>Cards.astro</code> component that renders multiple <code>Card.astro</code> components.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/b.png" alt="The card composition visualised. " width="600" height="400" loading="lazy">
<em>The card composition visualised.</em></p>
<p>Let’s start by creating <code>Card.astro</code>.</p>
<p>Define the relevant component props and relevant markup as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Card.astro</span>
---
{<span class="hljs-comment">/** Export the Props type alias **/</span>}
<span class="hljs-keyword">export</span> type Props = {
  <span class="hljs-attr">to</span>: string;
  title: string;
  gradientFrom: string;
  gradientTo: string;
};

<span class="hljs-comment">// Get component props from Astro.props</span>
<span class="hljs-keyword">const</span> { title, to } = Astro.props;
---
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{to}</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card__inner"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card__title"</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Render the arrow via HTML entity name: → = &amp;rarr;--&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card__footer"</span>&gt;</span><span class="hljs-symbol">&amp;rarr;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-class">.card</span> {
   <span class="hljs-comment">/** local CSS variable reused below */</span>
    <span class="hljs-attribute">--radius</span>: <span class="hljs-number">10px</span>;

    <span class="hljs-attribute">padding</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-built_in">var</span>(--radius);
    <span class="hljs-attribute">text-decoration</span>: none;
    <span class="hljs-attribute">transition</span>: all <span class="hljs-number">0.2s</span> ease-in-out;
  }

  <span class="hljs-selector-class">.card</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">scale</span>(<span class="hljs-number">0.95</span>);
  }

  <span class="hljs-selector-class">.card__inner</span> {
    <span class="hljs-attribute">background</span>: <span class="hljs-built_in">var</span>(--background);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-built_in">var</span>(--radius);
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
  }

  <span class="hljs-selector-class">.card__title</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--grey-<span class="hljs-number">900</span>);
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
    <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.75rem</span>;
  }

  <span class="hljs-selector-class">.card__footer</span> {
    <span class="hljs-attribute">padding-top</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--grey-<span class="hljs-number">900</span>);
    <span class="hljs-attribute">margin</span>: auto <span class="hljs-number">0</span> <span class="hljs-number">0</span> auto;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>Now, go ahead and create the <code>Cards.astro</code> component as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Cards.astro</span>
---
<span class="hljs-comment">// Import the Card component</span>
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">"./Card.astro"</span>;
<span class="hljs-comment">// Import the Card Props type</span>
<span class="hljs-keyword">import</span> type { Props <span class="hljs-keyword">as</span> CardProp } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Card.astro"</span>;

<span class="hljs-comment">// Define the Props for this component</span>
type Props = {
  <span class="hljs-attr">cards</span>: CardProp[]; <span class="hljs-comment">// accepts an array of CardProps</span>
};

<span class="hljs-comment">// Retrieve the cards prop</span>
<span class="hljs-keyword">const</span> { cards } = Astro.props;
---
</code></pre>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cards"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Dynamically render multiple Card components and spread the required card props --&gt;</span>
   {cards.map((card) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">Card</span> {<span class="hljs-attr">...card</span>} /&gt;</span>)}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-class">.cards</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
  }

  <span class="hljs-comment">/* Since this is standard CSS, we can have media queries here */</span>
  <span class="hljs-keyword">@media</span> screen <span class="hljs-keyword">and</span> (<span class="hljs-attribute">min-width:</span> <span class="hljs-number">768px</span>) {
    <span class="hljs-selector-class">.cards</span> {
      <span class="hljs-attribute">flex-direction</span>: row;
    }
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>To see the fruits of our labour, we must now import and render <code>Cards</code> in the <code>index.astro</code> page component.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro </span>
---
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> Cards <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Cards.astro"</span>;
---
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"profile"</span>&gt;</span>
   {/** ... **/}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  {/** 👀 look here **/}
  <span class="hljs-tag">&lt;<span class="hljs-name">Cards</span>
    <span class="hljs-attr">cards</span>=<span class="hljs-string">{[</span>
      {
        <span class="hljs-attr">title:</span> "<span class="hljs-attr">Here</span> <span class="hljs-attr">are</span> <span class="hljs-attr">my</span> <span class="hljs-attr">guiding</span> <span class="hljs-attr">philosophies</span> <span class="hljs-attr">for</span> <span class="hljs-attr">life</span>",
        <span class="hljs-attr">gradientFrom:</span> "#<span class="hljs-attr">818cf8</span>",
        <span class="hljs-attr">gradientTo:</span> "#<span class="hljs-attr">d8b4fe</span>",
        <span class="hljs-attr">to:</span> "/<span class="hljs-attr">philosophies</span>",
      },
      {
        <span class="hljs-attr">title:</span> "<span class="hljs-attr">A</span> <span class="hljs-attr">summary</span> <span class="hljs-attr">of</span> <span class="hljs-attr">my</span> <span class="hljs-attr">work</span> <span class="hljs-attr">history</span>",
        <span class="hljs-attr">gradientFrom:</span> "#<span class="hljs-attr">fde68a</span>",
        <span class="hljs-attr">gradientTo:</span> "#<span class="hljs-attr">fca5a5</span>",
        <span class="hljs-attr">to:</span> "/<span class="hljs-attr">work-summary</span>",
      },
      {
        <span class="hljs-attr">title:</span> "<span class="hljs-attr">What</span> <span class="hljs-attr">I</span> <span class="hljs-attr">do</span> <span class="hljs-attr">beyond</span> <span class="hljs-attr">technology</span>",
        <span class="hljs-attr">gradientFrom:</span> "#<span class="hljs-attr">6ee7b7</span>",
        <span class="hljs-attr">gradientTo:</span> "#<span class="hljs-attr">9333ea</span>",
        <span class="hljs-attr">to:</span> "/<span class="hljs-attr">beyond-tech</span>",
      },
    ]}
  /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-04-at-10.18.23.png" alt="The rendered cards." width="600" height="400" loading="lazy">
<em>The rendered cards.</em></p>
<p>Clicking any of the links will point to the respective blog page.</p>
<p>Let’s not forget to add the new <code>work-summary.md</code> page:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/work-summary.md</span>
---
layout: ../layouts/Blog.astro
<span class="hljs-attr">poster</span>: <span class="hljs-string">"/images/work-summary.jpg"</span>
<span class="hljs-attr">title</span>: <span class="hljs-string">"Work summary"</span>
<span class="hljs-attr">subtitle</span>: <span class="hljs-string">"A summary of my work:"</span>
---

- VP Engineering at Google
- VP Engineering at Facebook
- VP Engineering at Tesla
- VP Engineering at Amazon
- VP Engineering at Netflix
</code></pre>
<p>There we go!</p>
<h2 id="heading-the-template-flow-of-data">The Template Flow of Data</h2>
<p>As we’ve discussed, the data in the frontmatter runs on the server and is not available in the browser.</p>
<p>As we’ve built our application, we’ve frequently leveraged data in the frontmatter in the template section, as shown below:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">const</span> data = <span class="hljs-string">"Understanding Astro"</span>
---

<span class="hljs-comment">//Use data in the template </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>This is easy to reason about for our static website. We know this will eventually be compiled into HTML.</p>
<p>But consider a more robust markup that includes <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> elements. How do we reference data from the frontmatter in these markup sections?</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">const</span> data = <span class="hljs-string">"Understanding Astro"</span>
---

<span class="hljs-comment">// ✅ Use data in the template </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>

<span class="hljs-comment">// styles </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
 {<span class="hljs-comment">/** ❌referencing data here will fail */</span>}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span> 

<span class="hljs-comment">// scripts </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
{<span class="hljs-comment">/** ❌referencing data here will fail */</span>}
 <span class="hljs-built_in">console</span>.log(data)
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>One answer is via the <code>define:vars</code> template directive.</p>
<p><code>define:vars</code> will pass our variables from the frontmatter into the client <code>&lt;script&gt;</code> or <code>&lt;style&gt;</code>. It’s important to note that only JSON serialisable values work here.</p>
<p>Let’s give this a shot.</p>
<p>We must reference the <code>gradientFrom</code> and <code>gradientTo</code> variables passed as props in our <code>&lt;style&gt;</code>.</p>
<p>First, to make the variables available within <code>&lt;style&gt;</code>, we’ll go ahead and use <code>define:vars</code> as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Card.astro</span>
---
<span class="hljs-keyword">const</span> { title, to, gradientFrom, gradientTo } = Astro.props;
<span class="hljs-comment">// ... </span>
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">define:vars</span>=<span class="hljs-string">{{gradientFrom,</span> <span class="hljs-attr">gradientTo</span> }}&gt;</span><span class="css">
  {<span class="hljs-comment">/** ... **/</span>}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p><code>define:vars</code> accepts an object of variables we want available within <code>&lt;style&gt;</code>.</p>
<p>The variables are defined but not used yet.</p>
<p>Now, we can reference the variables via custom properties (aka css variables) as shown below:</p>
<pre><code class="lang-css"><span class="hljs-comment">/** 📂 src/components/Card.astro **/</span>
&lt;<span class="hljs-selector-tag">style</span> <span class="hljs-selector-tag">define</span><span class="hljs-selector-pseudo">:vars</span>={{gradientFrom, gradientTo }}&gt;
  <span class="hljs-comment">/** 👀 look here **/</span>
  <span class="hljs-selector-class">.card</span> {
    <span class="hljs-attribute">background-image</span>: <span class="hljs-built_in">linear-gradient</span>(
      to right,
      var(--gradientFrom), 
      <span class="hljs-built_in">var</span>(--gradientTo)
    );
  }
 <span class="hljs-comment">/** ... **/</span>
&lt;/<span class="hljs-selector-tag">style</span>&gt;
</code></pre>
<p>And voilà!</p>
<p>Our cards are now more beautiful than ever.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-04-at-10.45.36.png" alt="Applying dynamic gradients to the cards. " width="600" height="400" loading="lazy">
<em>Applying dynamic gradients to the cards.</em></p>
<h2 id="heading-the-dark-side-of-definevars">The Dark Side of <code>define:vars</code></h2>
<p>We’ve seen <code>define:vars</code> come in handy for using variables from the frontmatter of an Astro component. But be careful when using <code>define:vars</code> with scripts.</p>
<p>Using <code>define:vars</code> with a <code>&lt;script&gt;</code> is similar to using the <code>is:inline</code> directive.</p>
<p>Astro will not bundle the script and will be added multiple times if the same component is rendered more than once on a page.</p>
<p>Here’s an example to make this clear.</p>
<p>In <code>Card.astro</code>, go ahead and add a <code>&lt;script&gt;</code> with the <code>define:vars</code> directive as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** 📂 src/components/Card.astro **/</span>
&lt;script define:vars={{ gradientFrom }}&gt;
  <span class="hljs-built_in">console</span>.log(gradientFrom);
&lt;/script&gt;
</code></pre>
<p>Inspect the elements via the developer tools. You’ll notice that the <code>&lt;script&gt;</code> is inlined and unprocessed, that is, just as we’ve written it, apart from being wrapped in an immediately invoked function execution (IIFE).</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-04-at-12.07.32.png" alt="The inlined scripts. " width="600" height="400" loading="lazy">
<em>The inlined scripts.</em></p>
<p>The script is also added three times — with a different value of <code>gradientFrom</code> for each rendered card.</p>
<p>With scripts, a better solution (except the inline behaviour is ideal for your use case) is to pass the data from the component frontmatter to the rendered element via <code>data-</code> attributes and then access these via JavaScript.</p>
<p>For example, we may rewrite the previous solution as shown below:</p>
<pre><code class="lang-html">---

---
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{to}</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span> <span class="hljs-attr">data-gradientfrom</span>=<span class="hljs-string">{gradientFrom}</span>&gt;</span>
 ...
<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
...
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> card = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".card"</span>);

  <span class="hljs-comment">// narrow the type of card to HTMLElement to access ".dataset" </span>
  <span class="hljs-keyword">if</span> (card <span class="hljs-keyword">instanceof</span> HTMLElement) {
    <span class="hljs-comment">// access data in dataset.gradientfrom</span>
    <span class="hljs-built_in">console</span>.log(card.dataset.gradientfrom);
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Note that this is a contrived example and only retrieves the first card element with its associated <code>gradientfrom</code> data. Still, this demonstrates how to prevent unwanted behaviours with <code>define:vars</code> in <code>&lt;script&gt;</code>s.</p>
<h2 id="heading-how-to-load-multiple-local-files">How to Load Multiple Local Files</h2>
<p>Let’s go ahead and create a new <code>blog</code> directory to hold some more markdown pages. The pages and their content are shown below:</p>
<p><code>📂 pages/blogs/rust-javascript-tooling.md</code> :</p>
<pre><code class="lang-md">---
layout: "../../layouts/Blog.astro"
poster: "/images/adventure.jpg"
title: "Why Rust is the Future of Javascript Tooling"
<span class="hljs-section">subtitle: "How to create fast, speedy developer experiences."
---</span>

<span class="hljs-bullet">-</span> Rust is fast
<span class="hljs-bullet">-</span> Yes, it is fast
<span class="hljs-bullet">-</span> Touted as the new C++
<span class="hljs-bullet">-</span> Did I mention it's pretty fast?
</code></pre>
<p><code>📂 pages/blogs/sleep-more.md</code> :</p>
<pre><code class="lang-md">---
layout: "../../layouts/Blog.astro"
poster: "/images/sleeping-cat.jpg"
title: "Why you should sleep more"
<span class="hljs-section">subtitle: "Sleep is great for you. Here's why:"
---</span>

<span class="hljs-bullet">-</span> Sleep
<span class="hljs-bullet">-</span> Sleep more
<span class="hljs-bullet">-</span> Sleep a little more
</code></pre>
<p><code>📂 pages/blogs/typescript-new-javascript.md</code>  :</p>
<pre><code class="lang-md">---
layout: "../../layouts/Blog.astro"
poster: "/images/coding.jpg"
title: "Typescript is the new Javascript"
<span class="hljs-section">subtitle: "Typescript is becoming a standard for web development these days:"
---</span>

<span class="hljs-bullet">-</span> Type safety
<span class="hljs-bullet">-</span> Type safety!
<span class="hljs-bullet">-</span> Even more type safety!
</code></pre>
<p>We aim to list these blog titles on our home page. One way to do this would be to render all link elements in <code>index.astro</code> manually:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/index.astro --&gt;</span> 
...
<span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
 ... 
 <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"featured-blogs"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"featured-blogs__title"</span>&gt;</span>Featured Blogs<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"featured-blogs__description"</span>&gt;</span>
      Opinion pieces that will change everything you know about web development.
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

 <span class="hljs-tag">&lt;<span class="hljs-name">ol</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blogs"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blogs__list"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"blogs/typescript-new-javascript"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blog__link"</span>
        &gt;</span>Typescript is the new Javascript<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>
      &gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blogs__list"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/blogs/rust-javascript-tooling"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blog__link"</span>
        &gt;</span>Why Rust is the future of Javascript tooling<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>
      &gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blogs__list"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/blogs/sleep-more"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blog__link"</span>
        &gt;</span>Why you should sleep more<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>
      &gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span>
</code></pre>
<p>Then update our component styles:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/index.astro --&gt;</span> 
...
<span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  ... 
  <span class="hljs-selector-class">.featured-blogs</span> {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">3rem</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span>;
  }
  <span class="hljs-selector-class">.featured-blogs__title</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--gray-<span class="hljs-number">900</span>);
  }

  <span class="hljs-selector-class">.featured-blogs__description</span> {
    <span class="hljs-attribute">margin-top</span>: -<span class="hljs-number">1.2rem</span>;
  }

  <span class="hljs-selector-class">.blogs</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
  }

  <span class="hljs-selector-class">.blogs__list</span> {
    <span class="hljs-attribute">border-bottom</span>: <span class="hljs-number">1px</span> solid;
    <span class="hljs-attribute">border-color</span>: <span class="hljs-built_in">var</span>(--gray-<span class="hljs-number">200</span>);
  }

  <span class="hljs-selector-class">.blog__link</span> {
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
    <span class="hljs-attribute">display</span>: block;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--gray-<span class="hljs-number">200</span>);
    <span class="hljs-attribute">text-decoration</span>: none;
    <span class="hljs-attribute">transition</span>: opacity <span class="hljs-number">0.2s</span> ease-in-out;
  }

  <span class="hljs-selector-class">.blog__link</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.7</span>;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>This isn’t necessarily a wrong approach to getting this done. We will now have a list of the blogs, as expected.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-12.52.18@2x.png" alt="The rendered blog list." width="600" height="400" loading="lazy">
<em>The rendered blog list.</em></p>
<p>A better solution is to use <code>Astro.glob()</code> to load multiple files.</p>
<p><code>Astro.glob()</code> accepts a single <code>URL</code> glob parameter of the files we’d like to import. <code>glob()</code> will then return an array of the exports from the matching file.</p>
<p>Talk is cheap, so let’s put this into action.</p>
<p>Instead of manually writing out the list of blog articles, we will use <code>Astro.glob()</code> to fetch all the blog posts:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro </span>
---
<span class="hljs-keyword">const</span> blogs = <span class="hljs-keyword">await</span> Astro.glob&lt;{
  <span class="hljs-attr">poster</span>: string;
  title: string;
  subtitle: string;
}&gt;(<span class="hljs-string">"../pages/blogs/*.md"</span>);
...
---
...
</code></pre>
<ul>
<li>Note the argument passed to <code>.glob</code>, that is <code>../pages/blogs/*.md</code>. This relative glob path represents all markdown files in the <code>/blogs</code> directory.</li>
<li>Also note the typing provided. <code>.glob</code> implements a generic, which, in this case, represents the markdown frontmatter object type. </li>
</ul>
<pre><code class="lang-js">{          
    <span class="hljs-attr">poster</span>: string;
    title: string;
    subtitle: string;    
}
</code></pre>
<p>Now, we may replace the manual list with a dynamically rendered list, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro </span>
...
  &lt;ol&gt;
    {
      blogs.map(<span class="hljs-function">(<span class="hljs-params">blog</span>) =&gt;</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blogs__list"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{blog.url}</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"blog__link"</span>&gt;</span>
            {blog.frontmatter.title}
          <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span></span>
      ))
    }
  &lt;/ol&gt;
</code></pre>
<ul>
<li>Dynamically render the blog list using the <code>.map</code> array function.</li>
<li><code>Astro.glob()</code> returns markdown properties including frontmatter and <code>url</code> where <code>blog.url</code> refers to the browser url path for the markdown file.</li>
</ul>
<p>And voilà! Same result with a much neater implementation.</p>
<h2 id="heading-how-to-deploy-a-static-astro-site">How to Deploy a Static Astro Site</h2>
<p>We’ve come a long way! Now, let’s deploy this baby into the wild.</p>
<p>Deploying a static website is relatively the same regardless of the technology used to create the site.</p>
<p>At the end of your deployment build, we’ll have static assets to deploy to any service we choose.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/generate-prod-build-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Generating production builds.</em></p>
<p>Once this is done, we must wire up a static web server to serve this content when your users visit the deployed site.</p>
<p>NB: a static web server is a web server that serves static content. It essentially serves any files (for example, HTML, CSS, JS) the client requests.</p>
<p>This breaks down the process of deploying a static website into two parts:</p>
<ol>
<li>Create the static production assets</li>
<li>Serve the static assets via a static web server</li>
</ol>
<p>Let’s go through these steps.</p>
<h3 id="heading-1-create-static-production-assets">1. Create static production assets</h3>
<p>To build our application for production, run the command:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p>This will internally run the <code>astro build</code> command and build our application production static assets.</p>
<p>By default, these assets will exist in the <code>dist</code> folder.</p>
<h3 id="heading-2-serve-the-static-assets-via-a-static-web-server">2. Serve the static assets via a static web server</h3>
<p>Choosing a web server will come down to your choice. I’ll go ahead and explain how to use Netlify. But the steps you'll take with your web server provider will look similar.</p>
<p>Go over to Netlify and create an account.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-04.51.46@2x.png" alt="The Netlify homepage." width="600" height="400" loading="lazy">
<em>The Netlify homepage.</em></p>
<p>Once you create an account and sign in, you’ll find a manual section to deploy a site.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-04.56.37@2x.png" alt="The Netlify dashboard. " width="600" height="400" loading="lazy">
<em>The Netlify dashboard.</em></p>
<p>Now, click <code>browse to upload</code> and upload the <code>dist</code> folder containing our static production assets.</p>
<p>Once the upload is completed, you’ll have your site deployed with a random public URL, as shown below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-04.57.57@2x.png" alt="Deployed Netlify site URL." width="600" height="400" loading="lazy">
<em>Deployed Netlify site URL.</em></p>
<p>Visit the URL to view your newly deployed website!</p>
<h2 id="heading-the-problem-with-manual-deployments">The Problem with Manual Deployments</h2>
<p>Manual deployments are great for conceptually breaking down the process of deploying a static website.</p>
<p>But in the real world, you may find this less optimal.</p>
<p>The main challenge here is that every change made to your website requires you to build the application and re-upload it to your server manually.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/manual-redeployment.png" alt="Image" width="600" height="400" loading="lazy">
<em>Manually redeploying after new changes.</em></p>
<p>This is a well-known problem with a standardised solution. The solution involves automating the entire process of deploying static websites by connecting your website to a Git provider.</p>
<h2 id="heading-how-to-automate-the-deployment-of-a-static-website">How to Automate the Deployment of a Static Website</h2>
<p>Automating the deployment of a static website looks something like this:</p>
<p><strong>Step 1</strong>: Write and push your code to a Git provider like GitHub.</p>
<p><strong>Step 2</strong>: Connect the GitHub project to your static web server provider, for example Netlify.</p>
<p><strong>Step 3</strong>: You provide your website’s <code>build</code> command and the location of the built assets to your web server provider, for example Netlify.</p>
<p><strong>Step 4</strong>: Your web server provider automatically runs the build command and serves your static assets.</p>
<p><strong>Step 5</strong>: Anytime you make changes to the GitHub project, your web server provider picks up the changes and reruns step 4, that is automatically deploying your website changes.</p>
<p>To see this process in practice with Netlify, go over <a target="_blank" href="https://app.netlify.com/start">to your dashboard</a> and connect a Git provider (step 1).</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-05.46.08@2x.png" alt="Netlify: connecting a Git provider." width="600" height="400" loading="lazy">
<em>Netlify: connecting a Git provider.</em></p>
<p>I’ll go ahead to select GitHub, authorise Netlify, and select the GitHub project (step 2).</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-05.47.23@2x.png" alt="Netlify: selecting the Github project." width="600" height="400" loading="lazy">
<em>Netlify: selecting the Github project.</em></p>
<p>Once that’s selected, provide the settings for your application deployment (Step 3). By default, Netlify will suggest the <code>build</code> and <code>publish directory</code>. Check these to make sure there are no errors.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-25-at-05.49.46@2x.png" alt="Netlify: suggested build command and publish directory." width="600" height="400" loading="lazy">
<em>Netlify: suggested build command and publish directory.</em></p>
<p>Hit deploy, and your site will be live in seconds (step 4).</p>
<p>To see the redeployment after a new change, push a new change to the connected git repository.</p>
<h2 id="heading-how-fast-is-our-astro-website">How Fast is Our Astro Website?</h2>
<p>Astro boasts of insanely fast websites compared to frameworks like React or Vue.</p>
<p>Let’s put this to the test by following the steps below:</p>
<ul>
<li>Visit the newly deployed website on Chrome.</li>
<li>Open the Chrome developer tools.</li>
<li>Go to the Lighthouse tab.</li>
<li>Analyse the page load.</li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-13.42.45@2x.png" alt="Analysing page load via lighthouse." width="600" height="400" loading="lazy">
<em>Analysing page load via lighthouse.</em></p>
<p>Here’s my result running the test:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-11-at-13.44.24@2x.png" alt="Lighthouse 100% scores. " width="600" height="400" loading="lazy">
<em>Lighthouse 100% scores.</em></p>
<p>If this were a school examination, we would have just scored A+ on performance without trying.</p>
<p>This is a fast website!</p>
<p>Feel free to run the test on other pages.</p>
<h2 id="heading-wrapping-up-this-chapter">Wrapping Up This Chapter</h2>
<p>This has been a lengthy introduction to Astro! We’ve delved into building a project and learned a handful of Astro’s capabilities, from installation to project structure to the nuances of inline scripts and, eventually, project deployment.</p>
<p>Why stop here? We’ve only just scratched the surface.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-134.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter two.</em></p>
<h1 id="heading-chapter-2-astro-components-in-depth-1">Chapter 2: Astro Components In-Depth</h1>
<p>In this section, you'll beyond the basics and master the essential Astro entity.</p>
<h2 id="heading-what-youll-learn-1">What You’ll Learn</h2>
<ul>
<li>What zero JavaScript means in practical terms.</li>
<li>Why we should consider ditching the JavaScript runtime overhead.</li>
<li>Truly understand what an Astro component is.</li>
<li>Understand the behaviour of Astro component markup, styles and scripts.</li>
<li>Learn the powerful Astro template syntax and how it differs from <code>JSX.</code></li>
</ul>
<h2 id="heading-introduction-1">Introduction</h2>
<p>Consider the Pareto principle:</p>
<blockquote>
<p>The Pareto principle, also known as the 80/20 rule, states that 20% of the input can significantly impact 80% of the outcome in a particular situation or system.</p>
</blockquote>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/pareto.png" alt="The pareto principle illustrated" width="600" height="400" loading="lazy">
<em>The Pareto principle illustrated</em></p>
<p>Now, pay attention because this is where things get spicy. When it comes to working with Astro components, I've got a sneaky suspicion that that magic 20% yields a whopping 80% productivity.</p>
<p>So, let's get cracking and master these Astro components, shall we?</p>
<h2 id="heading-the-backbone-of-astro">The Backbone of Astro</h2>
<p>At the time of writing, consider the definition of Astro components from the official docs:</p>
<blockquote>
<p>Astro components are the basic building blocks of any Astro project. They are HTML-only templating components with no client-side runtime.</p>
</blockquote>
<p>The first part of the sentence is clear as daylight: <em>Astro components are the basic building blocks of any Astro project.</em></p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/building-blocks.png" alt="Like a fun game of Tetris, Astro components are how we build Astro applications." width="600" height="400" loading="lazy">
<em>Like a fun game of Tetris, Astro components are how we build Astro applications.</em></p>
<p>The second part of the sentence leaves room for interpretation or ambiguity: <em>they are HTML-only templating components with no client-side runtime.</em></p>
<p>But in this sentence lies the heartbeat of Astro components.</p>
<p>Let’s explore this in practical terms.</p>
<h3 id="heading-the-javascript-runtime-fatigue">The JavaScript runtime fatigue</h3>
<p>To truly appreciate Astro components, we must turn to our “standard” user interface framework components, for example those provided by <code>React</code> or <code>Vue</code>.</p>
<p>Your level of familiarity with these frameworks doesn’t matter. I’ll explain the following steps as clearly as possible. So trust me and follow along.</p>
<p>Firstly, create a new React project called <code>test-react-app</code> with the following terminal command:</p>
<pre><code class="lang-bash">npx create-react-app test-react-app
</code></pre>
<p>This utilises the <a target="_blank" href="https://create-react-app.dev/">create-react-app</a> utility.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-12.28.51@2x.png" alt="Creating a new React project from the terminal." width="600" height="400" loading="lazy">
<em>Creating a new React project from the terminal.</em></p>
<p>This will create a new React app in the <code>test-react-app</code> directory.</p>
<p>Now change the current directory, install dependencies, and start up the React application with the following command:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> test-react-app &amp;&amp; npm install &amp;&amp; npm run start
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-12.30.17@2x.png" alt="Starting the test React application." width="600" height="400" loading="lazy">
<em>Starting the test React application.</em></p>
<p>This will start a trivial React application on <code>http://localhost:3000/</code> or any other available local port.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-12.31.38@2x.png" alt="The React test application running in the browser." width="600" height="400" loading="lazy">
<em>The React test application running in the browser.</em></p>
<p>This is a contrived React application. It renders text paragraphs, and the React logo, and the application has no significant UI state changes or complex logic.</p>
<p>Now, let’s bundle this application for production.</p>
<p>Stop the local running server and build the application with the following command:</p>
<pre><code class="lang-js">npm run build
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-12.34.26@2x.png" alt="Building the test React application for production." width="600" height="400" loading="lazy">
<em>Building the test React application for production.</em></p>
<p>Let’s take a look at the build output.</p>
<p>Open the <code>test-react-app</code> directory in your code editor of choice and observe the <code>build/index.html</code> file. This root file will be served to the browser when the React application is visited.</p>
<p>Unwrap the minified file:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 build/index.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.ico"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#000000"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
      <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
      <span class="hljs-attr">content</span>=<span class="hljs-string">"Web site created using create-react-app"</span>
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/logo192.png"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/manifest.json"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>React App<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">defer</span>=<span class="hljs-string">"defer"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/js/main.3b5961bb.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/css/main.073c9b0a.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">noscript</span>&gt;</span>You need to enable JavaScript to run this app.<span class="hljs-tag">&lt;/<span class="hljs-name">noscript</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"root"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This is a standard HTML file. But what’s of note in its content is the following:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 build/index.html --&gt;</span>
... 
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">defer</span>=<span class="hljs-string">"defer"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/static/js/main.3b5961bb.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/static/css/main.073c9b0a.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> /&gt;</span>
... 

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"root"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
...
</code></pre>
<p>The document renders a <code>&lt;div id="root"&gt;&lt;/div&gt;</code> node, and the bundled <code>JS</code> and <code>CSS</code> assets are linked in the <code>&lt;head&gt;</code>.</p>
<p>Do you see the <code>defer</code> attribute on the <code>&lt;script&gt;</code>?</p>
<p>With the <code>defer</code> attribute, the script will be downloaded in parallel as the page is parsed and will be executed after the page is parsed.</p>
<p>By implication, this page renders an empty <code>&lt;div&gt;</code> at first until the JavaScript is parsed.</p>
<p>Well, let’s not panic. Instead, let’s explore the JavaScript referenced here. First, look at the bundled JavaScript asset in <code>build/static/js/main...js</code>.</p>
<p>If we unwrap the minified file, we should have a file that’s a little short of <code>9500</code> lines of JavaScript!</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-12.46.05@2x.png" alt="Unwrapping the minified Javascript asset for the trivial React application." width="600" height="400" loading="lazy">
<em>Unwrapping the minified Javascript asset for the trivial React application.</em></p>
<p>Wait … what?! For such a trivial application?! 😱</p>
<p>Oh yes.</p>
<p>I considered adding a funny meme here, but let’s not stray from the point’s importance.</p>
<p>Explaining what goes on within these <code>9000+</code> lines of JavaScript is beyond the scope of this book. But what we have in the file is an immediately invoked function (IIFE) with its entire content executed.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 build/static/js/main...js</span>
!(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ... lines of code go here</span>
})();
</code></pre>
<p>We certainly didn’t write the <code>9000+</code> lines of code in the <code>main</code> bundle. No! Most of that is the React runtime needed to make our React application work in the way React’s built: state, props, hooks, virtual DOM, and all the lovely abstractions React provides.</p>
<h3 id="heading-ditching-the-runtime">Ditching the runtime</h3>
<p>Unlike most JavaScript frameworks, Astro advocates for zero JavaScript by default. This means no JavaScript runtime overhead, as in the previous React application.</p>
<p>So, I’ve done what any competent investigator would — reconstructed the crime scene.</p>
<p>To do this, I built the same React starter application using Astro.</p>
<p>Use the following command to create the project:</p>
<pre><code class="lang-js">npm create astro@latest -- --template ohansemmanuel/astrojs-ditch-the-runtime-react --yes
</code></pre>
<p>We use the same <code>create astro</code> command to create a new project. The difference here is the <code>--template</code> argument that points to <code>ohansemmanuel/astrojs-ditch-the-runtime-react</code> and the <code>--yes</code> argument to skip all prompts and accept the defaults.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-09-at-07.40.44.png" alt="Creating a new Astro project with a template." width="600" height="400" loading="lazy">
<em>Creating a new Astro project with a template.</em></p>
<p>Choose the project directory, then start the application via:</p>
<pre><code class="lang-js">npm run start
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-14-at-09.31.23@2x.png" alt="The new Astro project running on localhost" width="600" height="400" loading="lazy">
<em>The new Astro project running on localhost</em></p>
<p>Note that the application is similar to the starter React application we explored earlier.</p>
<p>Now let’s go ahead and build this application for production with the following command:</p>
<pre><code class="lang-js">npm run build
</code></pre>
<p>This will build the Astro application and generate static in the <code>dist/</code> directory.</p>
<p>Explore the build output and find the main <code>HTML</code>, <code>CSS</code> and image files in <code>dist/assets</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-28-at-13.04.29@2x.png" alt="The Astro project build output." width="600" height="400" loading="lazy">
<em>The Astro project build output.</em></p>
<p>Look closely, and you’ll realise there’s no JavaScript build output! Instead, we have the <code>index.html</code> file, associated <code>CSS</code>, and image assets.</p>
<p>For the same result, we’ve eliminated the 9000+ lines of JavaScript the React example required.</p>
<p>This right here is what’s meant by <strong>zero JavaScript by default.</strong> This is the Astro premise.</p>
<p>I’m not advocating that you don’t use React or your favourite framework. But this example helps you understand Astro’s premise, that is to eliminate the need to have such client-side runtime <strong>if you don’t need it.</strong></p>
<p>The exciting truth is that we don’t need the JavaScript runtime overhead for many applications, such as content-driven websites. So you can ditch it in favour of Astro.</p>
<h2 id="heading-what-is-an-astro-component">What is an Astro Component?</h2>
<p>Before defining Astro components, let’s consider a more generic question. In straightforward terms, what is a website?</p>
<p>My straightforward answer would be: a website is a set of related <code>HTML</code> pages under a single domain.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/2.png" alt="A multi page website" width="600" height="400" loading="lazy">
<em>A multi page website</em></p>
<p>Now, with a single-page application, my definition would need to be updated. This is because a single-page website now consists of a single <code>HTML</code> page with routing handled via client-side JavaScript.</p>
<p>Regardless of the type of website, there’s a common denominator: the browser renders one or more <code>HTML</code> pages.</p>
<p>So, we will start our discussion by exploring the basic <code>HTML</code> page shown below:</p>
<pre><code class="lang-js">&lt;!DOCTYPE html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-GB"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>HTML 101<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">p</span> {
        <span class="hljs-attribute">color</span>: red;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello world'</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>We won’t win any design awards with this page, but it suffices for our learning purposes.</p>
<p>In the <code>HTML</code> above, notice how we’ve produced a paragraph with the text <code>Hello world</code>, styled it with some <code>CSS</code> and logged a message to the console using <code>JavaScript</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-02-02-at-06.19.40.png" alt="The basic HTML page" width="600" height="400" loading="lazy">
<em>The basic HTML page</em></p>
<p>In this seemingly simple file, we’ve combined <code>style</code>, <code>script</code> and <code>markup</code> — the three core components of any web application.</p>
<p>Astro components are identical to HTML files, leading us to our first definition of an Astro component.</p>
<h3 id="heading-an-astro-component-is-a-astro-file-capable-of-rendering-any-valid-html">An Astro component is a <code>.astro</code> file capable of rendering any valid HTML</h3>
<p>An Astro component is a document with a <code>.astro</code> file ending, that is <code>file.astro</code> or <code>anotherFile.astro</code> capable of rendering valid HTML content.</p>
<p>Let’s start a barebones <code>hello-astro</code> project to explore this statement. This time, we will not use the <code>create astro</code> utility. Instead, we will manually install Astro.</p>
<p>Create an empty directory and navigate into it:</p>
<pre><code class="lang-bash">mkdir hello-astro
<span class="hljs-built_in">cd</span> hello-astro
</code></pre>
<p>Run the following command to start the new project:</p>
<pre><code class="lang-js">npm init --yes
</code></pre>
<p>The <code>--yes</code> flag will use all the defaults, skipping the prompts.</p>
<p>Now install <code>astro</code>:</p>
<pre><code class="lang-js">npm install astro
</code></pre>
<p>Create an empty Astro page in the project in <code>src/pages/index.astro</code>.</p>
<p>This file must be in the <code>src/pages</code> directory as <code>pages</code> are the entry point to an Astro project.</p>
<p>Now we should have a project structure similar to the following:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-02-02-at-07.30.52.png" alt="The hello-astro project structure." width="600" height="400" loading="lazy">
<em>The hello-astro project structure.</em></p>
<p>At this point, go ahead and paste the starting <code>HTML</code> snippet into the <code>index.astro</code> component as follows:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/index.astro --&gt;</span>
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-GB"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>HTML 101<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">p</span> {
        <span class="hljs-attribute">color</span>: red;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello world'</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Then start up the application with the command:</p>
<pre><code class="lang-html">npx astro dev
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-02-04-at-07.09.03.png" alt="The hello astro application" width="600" height="400" loading="lazy">
<em>The hello astro application</em></p>
<p>We’ve got <code>Hello World</code> in red! <code>index.astro</code> successfully renders the <code>HTML</code> content to our web application’s <code>index</code> page.</p>
<p>Valid HTML is thus valid Astro.</p>
<p>If you know HTML, you already know some Astro.</p>
<p>The familiarity with HTML makes Astro approachable. But Astro components would be useless if they were equivalent to <code>HTML</code> pages. Building a new library (Astro) identical to HTML would waste resources. Well, apart from the fancy Astro logo, that’s a win.</p>
<p>Luckily, the Astro component syntax provides features expected from a modern frontend library, making it <strong>a superset of HTML</strong>.</p>
<p>This leads to our second definition.</p>
<h3 id="heading-astro-components-can-be-composed-to-make-complex-pages">Astro components can be composed to make complex pages</h3>
<p>Standard HTML files cannot be composed. We cannot import HTML files into another HTML file. That would be invalid.</p>
<p>But composability is vital to structuring complex user interfaces.</p>
<p>Astro components are composable, which makes them highly flexible and reusable.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c.png" alt="The parent child component relationship" width="600" height="400" loading="lazy">
<em>The parent child component relationship</em></p>
<p>The following pseudocode would be a valid representation of parent-child components:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">AstroComponent</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- render children components in here --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ChildAstroComponent</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ChildAstroComponent</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ChildAstroComponent</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">AstroComponent</span>&gt;</span>
</code></pre>
<p>The simplified mental model for building classic websites involves stringing together a bunch of HTML pages to make up a website.</p>
<p>Astro builds upon the same mental model.</p>
<p>So, essentially, an Astro website comprises pages that eventually get compiled into <code>HTML</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-1.png" alt="A website made of Astro pages." width="600" height="400" loading="lazy">
<em>A website made of Astro pages.</em></p>
<p>Since Astro pages are just Astro components found in the <code>src/pages</code> directory of our Astro project, they can also compose other Astro components.</p>
<p>Let’s give this a shot.</p>
<p>Consider the starting <code>index.astro</code> page below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂src/pages/index.astro --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-GB"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>HTML 101<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">p</span> {
        <span class="hljs-attribute">color</span>: red;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Hello world'</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Conceptually, we could compose the <code>index.astro</code> component from two smaller components: <code>Head</code> and <code>Body</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-2.png" alt="Composing the index page from the Head and Body components" width="600" height="400" loading="lazy">
<em>Composing the index page from the Head and Body components</em></p>
<p>Here’s how:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/pages/index.astro --&gt;
---
<span class="hljs-keyword">import</span> Body <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Body.astro"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Head.astro"</span>;
---

&lt;!DOCTYPE html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-GB"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Head</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Body</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<ul>
<li>The child components are imported within a code fence <code>---</code></li>
<li>The child components are rendered within the component template, that is <code>&lt;Head /&gt;</code> and <code>&lt;Body /&gt;</code> — similar to self-closing <code>HTML</code> tags.</li>
</ul>
<p>Where <code>Body</code> and <code>Head</code> are as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Body.astro</span>
&lt;body&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
&lt;/body&gt;
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Head.astro </span>
&lt;head&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>HTML 101<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-tag">p</span> {
      <span class="hljs-attribute">color</span>: red;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Hello world"</span>);
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
&lt;/head&gt;
</code></pre>
<p>Note how <code>Head</code> and <code>Body</code> represent “partial” <code>HTML</code> building blocks.</p>
<p>The level of composition we build our pages from is entirely up to us. For example, we could further break down the <code>Head</code> component into smaller bits.</p>
<p>Let’s consider introducing isolated components for the <code>meta</code>, <code>title</code>, <code>style</code> and script elements.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-3.png" alt="Composing the Head component from other smaller components" width="600" height="400" loading="lazy">
<em>Composing the Head component from other smaller components</em></p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Head.astro</span>
---
<span class="hljs-keyword">import</span> Meta <span class="hljs-keyword">from</span> <span class="hljs-string">"./Meta.astro"</span>;
<span class="hljs-keyword">import</span> Title <span class="hljs-keyword">from</span> <span class="hljs-string">"./Title.astro"</span>;
<span class="hljs-keyword">import</span> Style <span class="hljs-keyword">from</span> <span class="hljs-string">"./Style.astro"</span>;
<span class="hljs-keyword">import</span> Script <span class="hljs-keyword">from</span> <span class="hljs-string">"./Script.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Meta</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Title</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> /&gt;</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">Script</span> /&gt;</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span></span></span>
</code></pre>
<p>The <code>index</code> page still composes the same top-level components, that is <code>Head</code> and <code>Body</code>. However, <code>Head</code> now contains even more components.</p>
<p>This is the level of composition available to us with many modern frontend libraries. But to prevent unwanted bugs, there are some essential behaviours to be aware of when composing components in Astro.</p>
<h4 id="heading-1-styles-are-local-by-default">1. Styles are local by default</h4>
<p>It is vital to distinguish how Astro behaves when composing components with styles.</p>
<p>For example, we had a red paragraph when we started with all the <code>HTML</code> content in <code>index.astro</code>.</p>
<p>Now we’ve lost the paragraph style after our composition.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-02-04-at-13.04.36.png" alt="The red paragraph style lost after the composition" width="600" height="400" loading="lazy">
<em>The red paragraph style lost after the composition</em></p>
<p>What’s gone wrong?</p>
<p>To understand this, we must determine where the style seats in the component composition.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-4.png" alt="Styles in Astro components are local by default and do not leak over." width="600" height="400" loading="lazy">
<em>Styles in Astro components are local by default and do not leak over.</em></p>
<p>We have the <code>style</code> defined in the <code>Head.astro</code> component and expect it to affect the <code>&lt;p&gt;</code> in the <code>Body.astro</code> component.</p>
<p>This does not work.</p>
<p>This is because, with Astro components, styles are local by default. This means the <code>&lt;style&gt;</code> in <code>Head.astro</code> only affects elements defined in the <code>Head.astro</code> component.</p>
<p>Since the <code>&lt;p&gt;Hello world&lt;/p&gt;</code> lives in a separate component, the styles never leak over.</p>
<h4 id="heading-2-the-html-element-will-always-be-present">2. The HTML element will always be present</h4>
<p>The <code>&lt;html&gt;</code> element represents the top-level element of an HTML document. It is often called the root element. Other elements must be descendants.</p>
<p>Our current <code>index.astro</code> page composition looks like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/index.astro</span>
---
<span class="hljs-keyword">import</span> Body <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Body.astro"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Head.astro"</span>;
---

&lt;!DOCTYPE html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-GB"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Head</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">Body</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Every child component is housed in <code>Head</code> and <code>Body</code> and rendered within the root <code>html</code> element.</p>
<p>But what happens if we remove this element (and the associated <code>DOCTYPE</code>) as seen below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/index.astro</span>
---
<span class="hljs-keyword">import</span> Body <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Body.astro"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Head.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Head</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Body</span> /&gt;</span></span>
</code></pre>
<p>The <code>HTML</code> page will be rendered with a reasonable default:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Default HTML wrapper provided --&gt;</span> 
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Every other component rendered here --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-17-at-06.40.58@2x.png" alt="The rendered page with a reasonable default." width="600" height="400" loading="lazy">
<em>The rendered page with a reasonable default.</em></p>
<p>Did you know that according to HTML standards, the use of <code>&lt;html&gt;</code> is optional? This means that even without it, the browser can still render the page with a suitable default. Browsers can even render invalid HTML pages! </p>
<p>That being said, Astro’s default setting allows you to template even invalid HTML. So, be careful.</p>
<p>For accessibility reasons, include an <code>&lt;html&gt;</code> element. This is relevant to providing the <code>lang</code> attribute for the webpage. Again, this is helpful for screen-reading technologies.</p>
<h4 id="heading-3-styles-and-scripts-are-hoisted">3. Styles and scripts are hoisted</h4>
<p>Our page’s <code>&lt;script&gt;</code> and <code>&lt;style&gt;</code> elements exist in the associated <code>Script</code> and <code>Style</code> components.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-5.png" alt="The Style and Script child components" width="600" height="400" loading="lazy">
<em>The Style and Script child components</em></p>
<p>These child components are also precisely rendered within the <code>Head</code> component, and ultimately, we have a markup with <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> in <code>&lt;head&gt;</code>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span> ... <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span> ... <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>/&gt;</span>
</code></pre>
<p>As previously mentioned, <code>HTML</code> is quite lenient and will even attempt to render invalid HTML markup. But the <code>&lt;style&gt;</code> element must be included in the <code>&lt;head&gt;</code> of an <code>HTML</code> document.</p>
<p>Let’s attempt to break this rule.</p>
<p>Change <code>index.astro</code> to have <code>Style</code> and <code>Script</code> as adjacent sibling components to <code>Head</code>:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> Body <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Body.astro"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Head.astro"</span>;
<span class="hljs-keyword">import</span> Style <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Style.astro"</span>;
<span class="hljs-keyword">import</span> Script <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Script.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Head</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Body</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Style</span> /&gt;</span></span>
<span class="xml"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Script</span> /&gt;</span></span></span>
</code></pre>
<p>Instead of rendering <code>Style</code> and <code>Script</code> within the <code>&lt;head&gt;</code> of the document, we’ve placed them adjacent to the <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> elements.</p>
<p>From the composition above, you may expect a render markup similar to the following:</p>
<pre><code class="lang-js">&lt;head&gt; ... &lt;head&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span> .... <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span> ... <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span> ... <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>But inspect the rendered Astro page, and you’ll find the <code>style</code> and <code>script</code> elements still placed within the <code>&lt;head&gt;</code> of the document.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-02-04-at-13.50.39.png" alt="The hoisted script and style elements" width="600" height="400" loading="lazy">
<em>The hoisted script and style elements</em></p>
<p>This is because in Astro, we can freely use the <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> elements within our components, and they’ll be hoisted to the <code>&lt;head&gt;</code> of the rendered document. This is regardless of the component composition.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-6.png" alt="<style> and <script> are hoisted to the <head> of our page" width="600" height="400" loading="lazy">
<em>&lt;style&gt; and &lt;script&gt; are hoisted to the &lt;head&gt; of our page</em></p>
<p>As we’ll learn later, there’s an exception to this behaviour with inline scripts.</p>
<h4 id="heading-4-the-element-and-its-children-will-not-be-hoisted">4. The  element and its children will not be hoisted</h4>
<p>Seeing how <code>&lt;style&gt;</code> and <code>&lt;script&gt;</code> elements are hoisted may tempt you to use a <code>&lt;head&gt;</code> element incorrectly in your component composition.</p>
<p>But note that the <code>&lt;head&gt;</code> element and its children will not be hoisted, that is it does not get moved to the top of the page or merged with an existing <code>&lt;head&gt;</code>.</p>
<p>Let’s add a new adjacent <code>&lt;head&gt;</code> element:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/index.astro</span>
---
<span class="hljs-keyword">import</span> Body <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Body.astro"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Head.astro"</span>;
<span class="hljs-keyword">import</span> Style <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Style.astro"</span>;
<span class="hljs-keyword">import</span> Script <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Script.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Head</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Body</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Style</span> /&gt;</span></span>
<span class="xml"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Script</span> /&gt;</span></span></span>
<span class="xml"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"article"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span></span>
</code></pre>
<p>Adding a new <code>&lt;head&gt;</code> element to the bottom of the page is a silly composition. But browsers are forgiving of bad <code>HTML</code> markup, so in this case, the extra <code>&lt;head&gt;</code> element is ignored, and its content is rendered within the <code>&lt;body&gt;</code> element of the page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-01-17-at-07.50.01@2x.png" alt="The browser trying to make sense of the wrong composition" width="600" height="400" loading="lazy">
<em>The browser trying to make sense of the wrong composition</em></p>
<p>Always have the <code>&lt;head&gt;</code> page elements in a layout component to prevent unwanted behaviours. This is a recommended best practice.</p>
<h3 id="heading-astro-components-can-leverage-a-powerful-templating-syntax">Astro components can leverage a powerful templating syntax</h3>
<p>Templating is at the heart of most beloved frontend libraries. Think React and JSX or Vue and Vue templates.</p>
<p>Astro isn’t different.</p>
<p>Astro provides powerful templating by splitting a component into two main parts: the component script and the component template sections.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-7.png" alt="The make-up of an Astro component" width="600" height="400" loading="lazy">
<em>The make-up of an Astro component</em></p>
<p>It is important to note that technically, an Astro component is still valid with one or none of the sections present, that is an empty (yet valid) Astro component will have none of these sections.</p>
<h4 id="heading-component-script">Component script</h4>
<p>The component script section is identified with a code fence <code>(---)</code>.</p>
<pre><code class="lang-js">--- 
  <span class="hljs-comment">// This is the component script section </span>
---
</code></pre>
<p>Typically, the component script section is where we write the JavaScript code we need to reference within our template.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/c-8.png" alt="Leverage values from the component script section in the component template" width="600" height="400" loading="lazy">
<em>Leverage values from the component script section in the component template</em></p>
<p>Remember that when our Astro component is eventually compiled, the JavaScript expressions in the script section are evaluated at build time. Therefore, the JavaScript values are used to generate the eventual <code>HTML</code> pages once.</p>
<p>The component script section is not the place for dynamic interactive JavaScript code.</p>
<p>That being said, there are three main actions we’ll be performing in the component script section.</p>
<p>Let’s take a look at these.</p>
<h5 id="heading-1-creating-or-referencing-variables">1. Creating or referencing variables</h5>
<p>We may need to create variables for various reasons, for example to keep our markup DRY (don’t repeat yourself). In addition, the component script section supports standard JavaScript and TypeScript code. So creating or referencing variables works as we would expect.</p>
<pre><code class="lang-js">--- 
<span class="hljs-comment">// Javascript</span>
<span class="hljs-keyword">const</span> newVariable = <span class="hljs-string">"This is a new variable"</span>
<span class="hljs-comment">// Typescript</span>
<span class="hljs-keyword">let</span> newVar: string = <span class="hljs-string">"This is a new var"</span>;
newVar = <span class="hljs-number">9</span>;
---
</code></pre>
<p>If the IDE is setup for TypeScript, we’ll get a warning within the editor when we try the reassign the <code>newVar</code> variable to a number:</p>
<pre><code class="lang-js">Type <span class="hljs-string">'number'</span> is not assignable to type <span class="hljs-string">'string'</span>.
</code></pre>
<p>TypeScript is supported in the component script section by default.</p>
<p>Components are also capable of receiving props. Props are HTML-like attributes passed when we render a component. For example, here is a name prop passed to a <code>MyAstroComponent</code> component:</p>
<pre><code class="lang-js">&lt;MyAstroComponent name=<span class="hljs-string">"Emmanuel"</span>/&gt;
</code></pre>
<p>Within the component script section, props passed to a component may be referenced on the <code>Astro.props</code> global as shown below:</p>
<pre><code class="lang-js">&lt;!-- 📂 MyAstroComponent.astro --&gt;
---
<span class="hljs-keyword">const</span> { name } = Astro.props 
---
</code></pre>
<p>Since TypeScript is valid within the component script section, we can also type a component’s prop.</p>
<p>To provide prop types, go ahead and define a <code>Props</code> interface or type alias in the component script section:</p>
<pre><code class="lang-js">---
<span class="hljs-comment">// ✅ This is valid </span>
type Props = {
  <span class="hljs-attr">name</span>: string 
}
---
</code></pre>
<pre><code class="lang-js">---
<span class="hljs-comment">// ✅ This is equally valid </span>
interface Props {
  <span class="hljs-attr">name</span>: string 
}
---
</code></pre>
<p>Astro will automatically pick up the defined <code>Props</code> type and give relevant type warnings/errors related to wrong component props usage.</p>
<h5 id="heading-2-handling-imports">2. Handling imports</h5>
<p>At the start of most JavaScript modules lie imports. Astro components are not any different.</p>
<p>Composing multiple Astro components to build complex pages typically means importing other components or leveraging modules required to get our page working as expected.</p>
<p>Out of the box, Astro supports a wide range of file types, namely:</p>
<ul>
<li>Astro Components (<code>.astro</code>)</li>
<li>Markdown (<code>.md</code>, <code>.markdown</code>, and so on)</li>
<li>JavaScript (<code>.js</code>, <code>.mjs</code>)</li>
<li>TypeScript (<code>.ts</code>, <code>.tsx</code>)</li>
<li>NPM Packages</li>
<li>JSON (<code>.json</code>)</li>
<li>JSX (<code>.jsx</code>, <code>.tsx</code>)</li>
<li>CSS (<code>.css</code>)</li>
<li>CSS Modules (<code>.module.css</code>)</li>
<li>Images &amp; Assets (<code>.svg</code>, <code>.jpg</code>, <code>.png</code>, and so on)</li>
</ul>
<p>That’s a lot of file types supported natively! Here are some examples of import statements:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Astro </span>
<span class="hljs-keyword">import</span> Book <span class="hljs-keyword">from</span> <span class="hljs-string">'./book.astro'</span>

<span class="hljs-comment">// Javascript </span>
<span class="hljs-keyword">import</span> { getUnderstandingAstro } <span class="hljs-keyword">from</span> <span class="hljs-string">'./book.js'</span>; 

<span class="hljs-comment">// Typescript</span>
<span class="hljs-keyword">import</span> { getUser } <span class="hljs-keyword">from</span> <span class="hljs-string">'./book'</span>; 
<span class="hljs-keyword">import</span> type { UserType } <span class="hljs-keyword">from</span> <span class="hljs-string">'./book'</span>; 

<span class="hljs-comment">// NPM package </span>
<span class="hljs-keyword">import</span> { v4 <span class="hljs-keyword">as</span> uuidv4 } <span class="hljs-keyword">from</span> <span class="hljs-string">'uuid'</span>;

<span class="hljs-comment">// load JSON via default export</span>
<span class="hljs-keyword">import</span> json <span class="hljs-keyword">from</span> <span class="hljs-string">'./data.json'</span>; 

<span class="hljs-comment">// load and inject style onto the page</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'./style.css'</span>; 

<span class="hljs-comment">// css modules </span>
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'./style.module.css'</span>; 

<span class="hljs-comment">// other assets</span>
<span class="hljs-keyword">import</span> imgReference <span class="hljs-keyword">from</span> <span class="hljs-string">'./image.png'</span>; 
<span class="hljs-keyword">import</span> svgReference <span class="hljs-keyword">from</span> <span class="hljs-string">'./image.svg'</span>; 
<span class="hljs-keyword">import</span> txtReference <span class="hljs-keyword">from</span> <span class="hljs-string">'./words.txt'</span>;
</code></pre>
<p>The important point to note here is apart from TypeScript files and NPM packages, we typically need to add the file ending to the Astro import statement, for example:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ✅ do this </span>
<span class="hljs-keyword">import</span> Book <span class="hljs-keyword">from</span> <span class="hljs-string">'./book.astro'</span>

<span class="hljs-comment">// ❌ not this </span>
<span class="hljs-keyword">import</span> Book <span class="hljs-keyword">from</span> <span class="hljs-string">'./book'</span>
</code></pre>
<p>Astro also supports importing components from other UI frameworks such as React, Vue, Svelte, and so on. An example import for a React component would look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Header } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Header.jsx'</span>
<span class="hljs-comment">// if file ending is .tsx</span>
<span class="hljs-keyword">import</span> { Header } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Header'</span>
</code></pre>
<p>We will explore these in a later chapter.</p>
<p>It’s equally important to note that we can import any asset from the <code>public</code> directory. But note that assets in the <code>public</code> directory will remain untouched by Astro, that is they will be copied as is into the final build without processing (for example, minification).</p>
<pre><code class="lang-js"><span class="hljs-comment">// image in public/img-public.png</span>
<span class="hljs-keyword">import</span> imageRef <span class="hljs-keyword">from</span> <span class="hljs-string">"/img-public.png"</span>;
</code></pre>
<p>As a matter of best practice, favour placing images within the <code>src</code> directory so Astro can transform, optimise, and bundle them where possible. The exception is images in markdown (<code>.md</code>) files.</p>
<p>Images within <code>src</code> won’t work in markdown files, so use the <code>public</code> directory or a remote <code>src</code> URL as shown below:</p>
<pre><code class="lang-md">// my-nice-blog.md

![<span class="hljs-string">A wonderful photo of a cat</span>](<span class="hljs-link">/photo-in-public-dir.png</span>)
![<span class="hljs-string">Another cat photo</span>](<span class="hljs-link">https://www.photos.com/this-is-a-cat.png</span>)
</code></pre>
<h5 id="heading-3-fetching-data">3. Fetching data</h5>
<p>Astro components can utilise the global <code>fetch</code> function to establish HTTP requests to remote APIs from the component script section. The fetched data can subsequently be accessed within the component template.</p>
<pre><code class="lang-js">---
{<span class="hljs-comment">/** Random user generator **/</span>}
<span class="hljs-keyword">const</span> URL = <span class="hljs-string">"https://random-data-api.com/api/users/random_user?size=1"</span>
<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(URL)
<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json()
---

<span class="hljs-comment">// Use data in the template </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">pre</span>&gt;</span>{JSON.stringify(data, null, 2)}<span class="hljs-tag">&lt;/<span class="hljs-name">pre</span>&gt;</span></span>
</code></pre>
<p>The API call will only be made once for statically generated Astro sites to build the <code>HTML</code> page.</p>
<p>But while developing locally, the API requests in the component script section are fetched every time on page refresh. This is only a development behaviour. In our example, we will get a new random user on every page refresh.</p>
<p>Run the production build with <code>npm run build</code> and preview the production application with <code>npm run preview</code> to see the standard behaviour in action. We will have a single user on every page refresh, that is the user fetched at build time.</p>
<h4 id="heading-component-template">Component template</h4>
<p>The variables created, imports made, and data fetched in the component script section exist primarily for one reason: to be consumed in the component template section of the component.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/d.png" alt="Consuming variables in the component template section" width="600" height="400" loading="lazy">
<em>Consuming variables in the component template section</em></p>
<p>If Astro components are eventually built to <code>HTML</code>, the template section defines the markup of the said <code>HTML</code> page. But the component template section lets us do this dynamically, that is by leveraging the power of JavaScript expressions.</p>
<p>Let’s explore some of the actions we’re likely to perform within the component template of an Astro component.</p>
<h5 id="heading-consuming-variables">Consuming variables</h5>
<p>To consume a variable, wrap the name of the variable in curly braces as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> book = <span class="hljs-string">"Understanding AstroJS"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{book}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span> <span class="hljs-comment">// Outputs &lt;h1&gt;Understanding AstroJS&lt;/h1&gt;</span>
</code></pre>
<h5 id="heading-create-dynamic-attributes">Create dynamic attributes</h5>
<p>Creating a dynamic attribute is similar to consuming a variable. Use the variable in curly braces to pass attributes to both HTML elements and components:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> { author } = Astro.props;
<span class="hljs-keyword">const</span> book = <span class="hljs-string">"Understanding AstroJS"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">data-name</span>=<span class="hljs-string">{book}</span>&gt;</span>A new book<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span> 
<span class="hljs-comment">// Outputs &lt;h1 data-name="Understanding AstroJS"&gt;A new book&lt;/h1&gt;</span>
</code></pre>
<h5 id="heading-dynamic-html">Dynamic HTML</h5>
<p>Dynamic HTML is quite the lifesaver as we’ll occasionally not want to repeat ourselves. For example, consider how we may create dynamic lists as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> technologies = [<span class="hljs-string">'Javascript'</span>, <span class="hljs-string">'Typescript'</span>, <span class="hljs-string">'NodeJS'</span>]
---
<span class="hljs-comment">// Dynamically create a list of elements from technologies</span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span> 
  {items.map((item) =&gt; <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>{item}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>)}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span>
</code></pre>
<p>Or we may find ourselves in need of conditional rendering. To do this, leverage logical operators and ternary expressions as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> showCallToAction = <span class="hljs-literal">true</span>;
---

<span class="hljs-comment">// This will render &lt;button&gt;Buy now&lt;/button&gt;</span>
{showCallToAction &amp;&amp; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Buy now<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>} 

<span class="hljs-comment">// Alternatively, represent this with a ternary to provide a fallback</span>
{showCallToAction ?  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Buy now<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span> : <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Continue
 shopping<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>}
</code></pre>
<p>This will render <code>&lt;button&gt;Buy now&lt;/button&gt;</code> when <code>showCallToAction</code> is truthy and <code>&lt;p&gt;Continue shopping&lt;/p&gt;</code> otherwise.</p>
<h5 id="heading-dynamic-tags">Dynamic Tags</h5>
<p>Less commonly used, dynamic tags can still be useful in certain situations, such as building polymorphic components. </p>
<p>Depending on the consumer’s prop input, these components can render to various element nodes. An example is the <code>Text.astro</code> component that can render any element passed to it:</p>
<pre><code class="lang-js"><span class="hljs-comment">// usage </span>
&lt;Text <span class="hljs-keyword">as</span>=<span class="hljs-string">"h1"</span> /&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"div"</span> /&gt;</span></span>
</code></pre>
<p>In both cases, we want to render the same component with different underlying HTML element nodes, that is <code>h1</code> and <code>div</code> text nodes.</p>
<p>We can handle this dynamically, as shown below:</p>
<pre><code class="lang-js">&lt;!-- 📂 Text.astro --&gt;
---
<span class="hljs-keyword">const</span> { <span class="hljs-attr">as</span>: As = <span class="hljs-string">"h1"</span> } = Astro.props;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">As</span>&gt;</span>Text content<span class="hljs-tag">&lt;/<span class="hljs-name">As</span>&gt;</span></span>
</code></pre>
<p>Within the component script section, we deconstruct the <code>as</code> prop and rename it to a capitalised variable <code>As</code>. This is important as the variable names for a dynamically rendered component must be capitalised, that is:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ✅ Do this </span>
&lt;As&gt;Text content&lt;/As&gt;

<span class="hljs-comment">// ❌ not this </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">as</span>&gt;</span>Text content<span class="hljs-tag">&lt;/<span class="hljs-name">as</span>&gt;</span></span>
</code></pre>
<p>If we pass a lower cased variable, Astro will try to render the variable name as a literal <code>HTML</code> tag. In our example, <code>&lt;as&gt;Text content&lt;/as&gt;</code> and not the dynamic <code>&lt;h1&gt;Text content&lt;/h1&gt;</code> or <code>&lt;div&gt;Text content&lt;/div&gt;</code> element.</p>
<h5 id="heading-revisiting-slots">Revisiting Slots</h5>
<p>If you want to easily add external HTML content to your component template, the <code>&lt;slot /&gt;</code> element is your friend! Any child elements you include will be automatically rendered in a component’s <code>&lt;slot /&gt;</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/slot.png" alt="Using the <slot/> element." width="600" height="400" loading="lazy">
<em>Using the &lt;slot/&gt; element.</em></p>
<p>If we had a basic <code>Main</code> component with a slot as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/main.astro</span>
--- 
--- 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
</code></pre>
<p>The child elements of <code>Main</code> will be rendered in the <code>&lt;slot /&gt;</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
---
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This will be rendered in the slot <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<p>We can also provide fallback <code>&lt;slot&gt;</code> content when no child elements are passed to the component. To do this, provide the <code>&lt;slot /&gt;</code> its own children as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/main.astro</span>
--- 
--- 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This paragraph will be rendered if no child elements are passed to Main<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">slot</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
</code></pre>
<p>It is possible to provide more than one slot via named slots. Consider the following example:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/main.astro</span>
--- 
--- 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span> This is header <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is an INTRO paragraph <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"after-intro"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span> <span class="hljs-symbol">&amp;copy;</span> 2023 <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"after-footer"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
</code></pre>
<p>In this case, we can render specific child elements to the specific slots <code>after-intro</code> and <code>after-footer</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
---
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"after-intro"</span>&gt;</span>Hello after Intro<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This will be rendered in the default (nameless) slot <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  {/** This will be rendered in the after-footer slot **/}
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"after-footer"</span>&gt;</span>Download my new book <span class="hljs-tag">&lt;/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Main</span>&gt;</span></span>
</code></pre>
<h5 id="heading-not-quite-jsx">Not quite JSX</h5>
<p>Astro’s syntax will feel very familiar to React developers because it is designed to feel similar to HTML and JSX. But there are significant differences to be aware of so we don’t shoot ourselves in the foot.</p>
<p>All <code>HTML</code> attributes in <code>JSX</code> use <code>camelCase</code> formats. In Astro, stick to the standard <code>kebab-case</code> format:</p>
<pre><code class="lang-js">
&lt;!-- JSX --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"foo"</span> <span class="hljs-attr">dataValue</span>=<span class="hljs-string">"bar"</span> /&gt;</span></span>

&lt;!-- Astro --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"foo"</span> <span class="hljs-attr">data-value</span>=<span class="hljs-string">"bar"</span> /&gt;</span></span>
</code></pre>
<p>Unlike <code>JSX</code>, use <code>class</code>, not <code>className</code>.</p>
<p>In Astro, we can also use standard JavaScript or HTML comments:</p>
<pre><code class="lang-js">---
<span class="hljs-comment">//This is a comment</span>
---
&lt;!-- HTML-style comment --&gt;
{<span class="hljs-comment">/* JS style comment also valid */</span>}
</code></pre>
<p>Both are valid in Astro components. But in JSX, only JavaScript-style comments are supported.</p>
<p>With Astro, it is essential to note that HTML-style comments will be included in the browser DOM upon building the page. But JavaScript-style comments will be skipped. As such, for development-only comments, prefer the use of JavaScript-style comments.</p>
<p>My favourite difference is we can use the attribute shorthand for identically named variables in Astro, for example:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> name = <span class="hljs-string">"Understanding astro"</span>
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">MyComponent</span> {<span class="hljs-attr">name</span>} /&gt;</span></span> 

<span class="hljs-comment">// This is identical to writing &lt;MyComponent name={name}&gt;</span>
</code></pre>
<p>This shorthand is not supported in JSX.</p>
<p>Astro and JSX also differ in how whitespaces are treated. Astro follows the HTML rules as closely as possible. But unlike JSX, whitespaces are not escaped.</p>
<pre><code class="lang-js"><span class="hljs-comment">// ❌ will render span (string) with extra whitespace(s)</span>
&lt;span&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span></span>
&lt;/span&gt;

<span class="hljs-comment">// ✅ will add no extra character spaces</span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
</code></pre>
<p>In most cases, this isn’t very important except when you don’t want that space there! For example, with coloured text backgrounds.</p>
<p>Consider the <code>Code.astro</code> component shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Code.astro</span>
---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">code</span> {
    <span class="hljs-attribute">background-color</span>: red;
    <span class="hljs-attribute">color</span>: wheat;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Including the <code>Code</code> component within a paragraph will result in highlighted white spaces.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/white-space.png" alt="Extra white spaces in coloured text backgrounds." width="600" height="400" loading="lazy">
<em>Extra white spaces in coloured text backgrounds.</em></p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> Code <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Code.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Use an <span class="hljs-tag">&lt;<span class="hljs-name">Code</span>&gt;</span>if<span class="hljs-tag">&lt;/<span class="hljs-name">Code</span>&gt;</span> statement. Displaying a list? Try array <span class="hljs-tag">&lt;<span class="hljs-name">Code</span>&gt;</span>map()<span class="hljs-tag">&lt;/<span class="hljs-name">Code</span>&gt;</span>.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre>
<p>To prevent this, change the <code>Code</code> component render to ignore white spaces:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ✅ will add no extra character spaces</span>
&lt;span&gt;<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span></span>&lt;/span&gt;
</code></pre>
<p>And that’s it!</p>
<h2 id="heading-wrapping-up-this-chapter-1">Wrapping Up This Chapter</h2>
<p>Put these together, and we now have a solid definition for an Astro component: a document with a .<code>astro</code> file ending representing a composable superset of HTML. It also provides a powerful templating syntax and renders to HTML with no Javascript runtime overhead.</p>
<p>Wow, if I were to ask a candidate about an Astro component definition in an interview and they gave me this answer, I would knight them on the spot! The job is theirs.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-135.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter three.</em></p>
<h1 id="heading-chapter-3-build-your-own-component-island-1">Chapter 3: Build Your Own Component Island</h1>
<blockquote>
<p>“What I cannot create, I do not understand” — Richard Feynman</p>
</blockquote>
<p>Astro’s fast narrative relies on component islands, which allows you to use other framework components like React, Vue, or Svelte in your Astro applications. This chapter will guide us in creating our own component island from the ground up.</p>
<p>To view the complete application, see the <a target="_blank" href="https://github.com/understanding-astro/build-your-own-component-island">GitHub repo</a>. </p>
<h2 id="heading-what-youll-learn-2">What You’ll Learn</h2>
<ul>
<li>An overview of different web application rendering techniques.</li>
<li>Build your own component islands implementation from scratch.</li>
<li>Comprehend the island architecture.</li>
</ul>
<h2 id="heading-a-brief-history-of-how-we-got-here">A Brief History of How We Got Here</h2>
<p>To ensure the coming technical implementation is built on a solid understanding, let’s peep into the past and explore the several application rendering techniques we may employ on a frontend application.</p>
<p>It is essential to note that this isn’t an exhaustive guide to front-end application rendering. But you'll learn enough to understand and appreciate the component islands architecture.</p>
<h3 id="heading-where-it-all-begins">Where it all begins</h3>
<p>In simple terms, there are two main actors in serving an application to a user:</p>
<ol>
<li>The user client, for example a web browser</li>
<li>The application server</li>
</ol>
<p>To display a website, a user requests a resource from an application server.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-5.png" alt="The web browser requesting article.html from an application server" width="600" height="400" loading="lazy">
<em>The web browser requesting article.html from an application server</em></p>
<p>With these two actors at play, a significant architectural decision you’ll make when building any decent frontend application is whether to render an application on the client or server.</p>
<p>Let’s briefly explore both options.</p>
<h3 id="heading-client-side-rendering-csr">Client-side rendering (CSR)</h3>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/1.png" alt="Choosing client side rendering." width="600" height="400" loading="lazy">
<em>Choosing client side rendering.</em></p>
<p>By definition, a client-side rendered application renders pages directly in the browser using JavaScript. All logic, data-fetching, templating and routing are handled on the client (the user’s browser).</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-1.png" alt="An overview of a client-side rendered application." width="600" height="400" loading="lazy">
<em>An overview of a client-side rendered application.</em></p>
<p>The past years saw the rise of client-side rendering, particularly among single-page applications. You’ve likely seen this in action if you’ve worked with libraries like React or Vue.</p>
<p>For a practical overview, consider the webpage for a blog article with a like count and a comment section below the initial viewport.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-2.png" alt="A blog article with a dynamic sidebar and a comment section below the article." width="600" height="400" loading="lazy">
<em>A blog article with a dynamic sidebar and a comment section below the article.</em></p>
<p>If this application was entirely client-side rendered, the simplified rendering flow would look like this:</p>
<ol>
<li>The user visits your website.</li>
<li>Your static server returns a near-empty <code>HTML</code> page to the browser.</li>
<li>The browser fetches the linked script file in the <code>HTML</code> page.</li>
<li>The JavaScript is loaded and parsed.</li>
<li>The data for the article, number of comments, and comments are fetched.</li>
<li>A fully interactive page is shown to the user.</li>
</ol>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-3.png" alt="Visualising the rendering process from a user's perspective." width="600" height="400" loading="lazy">
<em>Visualising the rendering process from a user's perspective.</em></p>
<h4 id="heading-the-pros-of-client-side-rendering-csr">The pros of client-side rendering (CSR)</h4>
<ul>
<li>The user gets back the resource from the server quickly. In our case, a near-empty <code>HTML</code> page, but on the bright side, the user receives that quickly! In technical terms, client-side rendering yields a high time to first byte (<strong>TTFB</strong>).</li>
<li>Arguably accessible to reason about. All logic, data-fetching, templating and routing are handled in one place – the client.</li>
</ul>
<h4 id="heading-the-cons-of-client-side-rendering">The cons of client-side rendering</h4>
<ul>
<li>It potentially takes the user a long time to see anything tangible on our page, that is they’re initially met with an empty screen. Even if we change the initial <code>HTML</code> page sent to the browser to be an empty application shell, it still potentially takes time for the user to see eventual data, that is after the Javascript is parsed and the data fetched from the server.</li>
<li>As the application grows, the amount of JavaScript parsed and executed before displaying data increases. This can impact mobile performance negatively.</li>
<li>The page's time to interactivity (<strong>TTI</strong>) suffers, for example it takes a long time before our users can interact with the comments. All JavaScript must be parsed, and all associated data must be fetched first.</li>
<li>Detrimental SEO if not implemented correctly.</li>
</ul>
<h3 id="heading-server-side-rendering">Server-side rendering</h3>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/choosing-ssr.png" alt="Choosing server-side rendering." width="600" height="400" loading="lazy">
<em>Choosing server-side rendering.</em></p>
<p>Let’s assume we’re unhappy with client-side rendering and decide to do the opposite.</p>
<p>On the opposing end of the rendering pole lies server-side rendering.</p>
<p>In a server-side rendered application, a user navigates to our site, and the server generates the full <code>HTML</code> for the page and sends it back to the user.</p>
<p>In our example, here’s what a simplified flow would look like:</p>
<ol>
<li>The user visits our website.</li>
<li>The data for the article, user profile, and comments are fetched on the server.</li>
<li>The server renders the <code>HTML</code> page with the article, the number of comments, and other required assets.</li>
<li>The server sends the client a fully formed <code>HTML</code> page.</li>
</ol>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/aa.png" alt="Visualising the rendering process from a user's perspective." width="600" height="400" loading="lazy">
<em>Visualising the rendering process from a user's perspective.</em></p>
<p>NB: it is assumed that the server sends a mostly static <code>HTML</code> page with minimal JavaScript needed for interactivity.</p>
<h4 id="heading-the-pros-of-server-side-rendering">The pros of server-side rendering</h4>
<ul>
<li>As soon as the user browser receives our fully formed <code>HTML</code> page, they can almost immediately interact with it, for example the rendered comments. There’s no need to wait for more JavaScript to be loaded and parsed. In performance lingo, the time to interactivity (<strong>TTI</strong>) equals the first contentful paint (<strong>FCP</strong>).</li>
<li>Great SEO benefits as search engines can index your pages and crawl them just fine.</li>
</ul>
<h4 id="heading-the-cons-of-server-side-rendering">The cons of server-side rendering</h4>
<ul>
<li>Generating pages on the server takes time. In our case, we must wait for all the relevant data to be fetched on the server. As such, the time to first byte (<strong>TTFB</strong>) is slow.</li>
<li>Resource intensive: the server takes on the burden of rendering content for users and bots. As a result, associated server costs increase as rendering needs to be done on the server.</li>
<li>Full page reloads for every requested server resource.</li>
</ul>
<h3 id="heading-server-side-rendering-with-client-side-hydration">Server-side rendering with client-side hydration</h3>
<p>We’ve explored rendering on both sides of the application rendering pole. But what if there was a way to use server and client-side rendering? Some strategy right in the middle of the hypothetic rendering pole?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/ssr-with-client-rehydration.png" alt="Choosing SSR with client-side hydration." width="600" height="400" loading="lazy">
<em>Choosing SSR with client-side hydration.</em></p>
<p>If we were building an interactive application and working with a framework like React or Vue, a widely common approach is to render on the server and hydrate on the client.</p>
<p>Hydration, in layperson’s terms, means re-rendering the entire application again on the client to attach event handlers to the DOM and support interactivity.</p>
<p>In theory, this is supposed to give us the wins of server-side rendering plus the interactivity we get with rich client-side rendered applications.</p>
<p>In our example, here’s what a simplified flow would look like:</p>
<ol>
<li>The user visits our website.</li>
<li>The data for the article, user profile, and comments are fetched on the server.</li>
<li>The server renders the <code>HTML</code> page with the article, the number of comments, and other required assets.</li>
<li>The server sends the client a fully formed <code>HTML</code> page alongside the JavaScript client runtime.</li>
<li>The client then “boots up” JavaScript to make the page interactive.</li>
</ol>
<p>Making an otherwise static page interactive (for example, attaching event listeners) is called hydration.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/ssr-csr-hydrate-flow.png" alt="Visualising the rendering process from a user's perspective." width="600" height="400" loading="lazy">
<em>Visualising the rendering process from a user's perspective.</em></p>
<h4 id="heading-the-pros-of-server-side-rendering-with-client-side-hydration">The pros of server-side rendering with client-side hydration</h4>
<ul>
<li>Benefits of SSR, for example quick FP and FMP</li>
<li>Can power highly interactive applications.</li>
<li>Supported rendering style in most frontend frameworks such as React and Vue.</li>
</ul>
<h4 id="heading-the-cons-of-server-side-rendering-with-client-side-hydration">The cons of server-side rendering with client-side hydration</h4>
<ul>
<li>Slow time to first byte — similar to standard SSR.</li>
<li>It can delay time to Interactivity (TTI) by making the user interface look ready before completing client-side processing. The period where the UI looks ready but is unresponsive (not hydrated) is what’s been — quite hilariously — dubbed the uncanny valley.</li>
</ul>
<p>NB: this assumes certain parts of our application, such as the likes and comments, can be interacted with, for example clicked to perform further action.</p>
<h3 id="heading-partial-hydration-for-the-win">Partial hydration for the win</h3>
<p>Combining server-side rendering with client-side hydration has the potential to offer the best of both worlds. But it is not without its demerits.</p>
<p>One way to tackle the heavy delay in time to interactivity (TTI) seems clear. Instead of hydrating the entire application, why not hydrate only the interactive bits?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/p-hydration.png" alt="Partial hydration vs full-page hydration." width="600" height="400" loading="lazy">
<em>Partial hydration vs full-page hydration.</em></p>
<p>As opposed to hydrating the entire application client side, partial hydration refers to hydrating specific parts of an application while leaving the rest static.</p>
<p>For example, in our application, we’d leave the rest of the page static while hydrating just the like button and comment section.</p>
<p>We may also take partial hydration further and implement what’s known as lazy hydration. For example, our application has a comment section below the initial viewport.</p>
<p>In this case, we may hydrate the like button when the page is loaded and hydrate the comment section only when the user scrolls below the initial viewport.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-4.png" alt="Hydrate the comment section at a later time." width="600" height="400" loading="lazy">
<em>Hydrate the comment section at a later time.</em></p>
<p>Talk about flexibility!</p>
<h4 id="heading-the-pros-of-partial-hydration">The pros of partial hydration</h4>
<ul>
<li>The same benefits of server-side rendering with client-side hydration.</li>
<li>Faster time to interactivity as the entire application isn’t hydrated.</li>
</ul>
<h4 id="heading-the-cons-of-partial-hydration">The cons of partial hydration</h4>
<ul>
<li>If most of the parts of the application are interactive and have a high priority, the advantage of partial hydration could be arguably minimal, that is the entire application would take just as long to be hydrated.</li>
</ul>
<h3 id="heading-where-does-the-island-architecture-come-from">Where does the island architecture come from?</h3>
<p>The island architecture is built upon the foundation of partial hydration. Essentially, the islands architecture refers to having “islands of interactivity” on an otherwise static <code>HTML</code> page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/independent-islands.png" alt="Islands of interactivity on an otherwise static webpage." width="600" height="400" loading="lazy">
<em>Islands of interactivity on an otherwise static webpage.</em></p>
<p>To make sense of this, think of these islands as partially hydrated components. So our entire page isn’t hydrated, but rather these islands.</p>
<h2 id="heading-how-to-implement-a-partial-hydration-islands-architecture">How to Implement a Partial Hydration Islands Architecture</h2>
<p>It’s game time, mate.</p>
<p>This section might seem challenging, but I suggest taking your time and coding along if possible. But, of course, you’ll probably be fine if you’re a more experienced engineer.</p>
<p>We will begin building our own island architecture implementation from the ground up. In more technical terms, we will implement a framework-independent partial hydration islands architecture implementation.</p>
<p>Phew! That’s a mouthfull.</p>
<p>Let’s break that down.</p>
<h3 id="heading-objectives">Objectives</h3>
<p>The goal of this exercise is not to build a full-blown library or to create an exact clone of the Astro Island implementation. No!</p>
<p>Our objective is to peel back the perceived layer of complexity and strip down component islands to a fundamental digestible unit.</p>
<p>Here are the functional requirements for our island implementation:</p>
<ol>
<li>Framework-independent: our solution must work across multiple frameworks, for example, <code>Preact</code>, <code>Vue</code>, <code>Petite-Vue</code>, and <code>React</code>.</li>
<li>A partial hydration islands architecture implementation: we will strip away JavaScript by default and only hydrate on an as-needed basis.</li>
<li>No frontend build step: for simplicity, our implementation will disregard a frontend build step, for example using <code>babel.</code></li>
<li>Support lazy hydration: this is a form of partial hydration where we can trigger hydration later and not immediately after loading the site. For example, if an island is off-screen (not in the viewport), we will not load the JavaScript for the island. We will only do so when the island is in view.</li>
</ol>
<h3 id="heading-installation">Installation</h3>
<p>Let’s call our island module <code>mini-island</code>.</p>
<p>To install <code>mini-island</code>, a developer will import our <em>soon-to-be-built</em> module as shown below:</p>
<pre><code class="lang-js">&lt;script type=<span class="hljs-string">"module"</span>&gt;
    {<span class="hljs-comment">/** import a mini-island.js module **/</span>}
    <span class="hljs-keyword">import</span> <span class="hljs-string">"/mini-island.js"</span>
&lt;/script&gt;
</code></pre>
<p>To enjoy the benefits of partial hydration, developers will add <code>mini-island.js</code> to their page with the promise of having a small JS footprint — a small price to pay to get partially hydrated islands of interactivity.</p>
<h3 id="heading-api-design">API design</h3>
<p>Our first objective is to make sure our solution is framework agnostic. An excellent native solution for framework-agnostic implementations is <strong>web components</strong>.</p>
<p>By definition, web components are a suite of technologies that allows us to create reusable custom elements.</p>
<p>If you’re new to web components, instead of rendering a standard HTML element, for example a <code>div</code>, we will create our custom HTML element, <code>mini-island</code>.</p>
<p><code>mini-island.js</code> will expose a custom element with the following basic usage:</p>
<pre><code class="lang-js">&lt;mini-island&gt;
 This is an island
&lt;/mini-island&gt;
</code></pre>
<p>Within <code>&lt;mini-island&gt;</code>, a developer will be able to leverage an island of interactivity on an otherwise static page.</p>
<p>We will support three different <code>&lt;mini-island&gt;</code> attributes to handle partial and lazy hydration: <code>client:idle</code>, <code>client:visible</code> and <code>client:media={QUERY}</code>.</p>
<p>Here’s an example of how they’d be used on <code>&lt;mini-island&gt;</code>:</p>
<pre><code class="lang-js">&lt;mini-island client:idle /&gt; 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:visible</span> /&gt;</span></span> 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:media</span>=<span class="hljs-string">"(max-width: 400px)"</span> /&gt;</span></span>
</code></pre>
<p>These attributes will affect how the island is hydrated.</p>
<ul>
<li><code>client:idle</code>: load and hydrate JavaScript when the whole page is loaded and the browser is idle.</li>
<li><code>client:visible</code>: we will load and hydrate the island JavaScript once the island is visible, for example, when it's entered the user’s viewport.</li>
<li><code>client:media</code>: we will load and hydrate the island once the query is satisfied, for example <code>client:media="(max-width: 400px)"</code>.</li>
</ul>
<p>There’s one final piece to our API design. How will developers define the scripts or markup to be hydrated?</p>
<p>We will use the <code>&lt;template&gt;</code> HTML element, the content template element.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- ❌ incorrect usage: --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:idle</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"this should be partially hydrated"</span>)
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>

<span class="hljs-comment">&lt;!-- ✅ correct usage: --&gt;</span> 
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:idle</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- use the &lt;template&gt; element --&gt;</span>     
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"this should be partially hydrated"</span>)
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
</code></pre>
<p><code>&lt;template&gt;</code> is generally used for holding <code>HTML</code> that shouldn’t be rendered immediately on page load. But the <code>HTML</code> may be instantiated via JavaScript.</p>
<p>For example, assuming a user wanted to log a warning to the console but wanted to use our island implementation, they’d do the following:</p>
<pre><code class="lang-js">&lt;mini-island&gt; 
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span> Warning, something may be wrong <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript"> 
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"something has gone wrong"</span>)
     </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span></span>
</code></pre>
<p>When the above is rendered, the <code>&lt;h2&gt; Warning, something may be wrong &lt;/h2&gt;</code> message will be displayed. But child elements of the <code>template</code> will not be rendered by default, that is the <code>script</code> will never be executed.</p>
<p>Our <code>mini-island</code> implementation will grab the content of the <code>template</code> and initialise the <code>&lt;script&gt;</code> when desired.</p>
<p>For example, if the user passes a <code>client:visible</code> attribute, we will ensure the script only runs when the island is visible.</p>
<pre><code class="lang-js">&lt;mini-island client:visible&gt; 
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span> Warning, something may be wrong <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript"> 
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"something has gone wrong"</span>)
     </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span></span>
</code></pre>
<p>It’s important to note that we expect the developer to pass a <code>data-island</code> attribute to the <code>template</code>. We will only hydrate templates with the <code>data-island</code> attribute to avoid interfering with other potential user-defined templates.</p>
<p>Don’t worry if these seem fuzzy right now. We will implement and test these with examples that’ll solidify your understanding.</p>
<h3 id="heading-getting-started-1">Getting started</h3>
<p>Ready?</p>
<p>Start by creating a <code>mini-island.js</code> file in whatever directory you want.</p>
<p>In <code>mini-island</code>, create a barebones custom component as annotated below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>

<span class="hljs-comment">/**
 * Define a MiniIsland class to encapsulate the behaviour of 
our custom element, &lt;mini-island&gt;
 * This class extends HTMLElement where the HTMLElement 
interface represents any HTML element.
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MiniIsland</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">/**
   * Define the name for the custom element as a static class 
property.
   * Custom element names require a dash to be used in them 
(kebab-case).
   * The name can't be a single word. ✅ mini-island ❌ 
miniIsland
   */</span>
  <span class="hljs-keyword">static</span> tagName = <span class="hljs-string">'mini-island'</span>;
  <span class="hljs-comment">/**
   * Define the island element attributes
   *, e.g., &lt;mini-island data-island&gt;
   */</span>
  <span class="hljs-keyword">static</span> attributes = {
    <span class="hljs-attr">dataIsland</span>: <span class="hljs-string">"data-island"</span>,
  };
}

<span class="hljs-comment">/**
 * Our solution relies heavily on web components. Check that the
 * browser supports web components via the 'customElements' property
 */</span>

<span class="hljs-keyword">if</span> (<span class="hljs-string">'customElements'</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>) {
  <span class="hljs-comment">/**
   * Register our custom element on the CustomElementRegistry object using the define method.
   *
   * NB: The CustomElementRegistry interface provides methods for registering custom elements and querying registered elements.
   *
   * NB: The arguments to the define method are the name of the custom element (mini-island)
   * and the class (MiniIsland) that defines the behaviour of the custom element.
   *
   * NB: "MiniIsland.tagName" below represents the static class property, i.e., "static tagName".
   */</span>
  <span class="hljs-built_in">window</span>.customElements.define(MiniIsland.tagName, MiniIsland);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-comment">/**
   * custom elements not supported, log an error to the console
   */</span>
  <span class="hljs-built_in">console</span>.error(
    <span class="hljs-string">'Island cannot be initiated because Window.customElements is unavailable.'</span>
  );
}
</code></pre>
<p>Let’s get some basic manual testing to nudge us in the right direction.</p>
<p>Create a new <code>demos/initial.html</code> file with the following content:</p>
<pre><code class="lang-js">&lt;!DOCTYPE html&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-string">"../mini-island.js"</span>;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>To view this via a local web server, run the following command from the project directory:</p>
<pre><code class="lang-bash"> npx local-web-server
</code></pre>
<p>By default, this should start a local static web server on port <code>8000</code>. We may now view the initial demo page on <code>http://localhost:8000/demos/initial.html</code></p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-14-at-07.29.14.png" alt="The initial demo page." width="600" height="400" loading="lazy">
<em>The initial demo page.</em></p>
<p>Let’s confirm that our custom element <code>mini-island</code> is registered rendering the custom element with a simple paragraph child element:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/initial.html --&gt;</span> 
...
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello future island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>This will render the custom element and the <code>Hello future island</code> paragraph as expected:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-14-at-07.27.26.png" alt="Rendering the custom element with a child element. " width="600" height="400" loading="lazy">
<em>Rendering the custom element with a child element.</em></p>
<p>Now, let’s go ahead and add some JavaScript within <code>&lt;mini-island&gt;</code> as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/initial.html --&gt;</span> 
...
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello future island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
</code></pre>
<p>If you refresh the page and check the browser console, you should see the warning logged.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-14-at-07.32.44.png" alt="Console warning from the island." width="600" height="400" loading="lazy">
<em>Console warning from the island.</em></p>
<p>This means the script was fired almost immediately. Not our ideal solution.</p>
<p>While images and video account for over 70% of the bytes downloaded for the average website, byte per byte, JavaScript has a more significant negative impact on performance.</p>
<p>So, our goal is to ensure JavaScript doesn’t run by default. We will render any relevant markup in the island (HTML and CSS) but defer the loading of JavaScript.</p>
<h3 id="heading-how-to-leverage-the-content-template-element">How to leverage the content template element</h3>
<p><code>&lt;template&gt;</code> is a native HTML element that’s near perfect for our use case.</p>
<p>The contents within a <code>&lt;template&gt;</code> element are parsed for correctness by the browser but not rendered.</p>
<p>For example, let’s go ahead and wrap the script from the previous example in a <code>&lt;template&gt;</code> element as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/initial.html --&gt;</span> 
...
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello future island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
</code></pre>
<p>If you refresh the page, you’ll notice that the <code>Hello future island</code> paragraph is rendered, but the <code>script</code> within <code>&lt;template&gt;</code> isn’t, that is no log to the console.</p>
<p>This is step one: isolate JavaScript from being loaded right away.</p>
<p>However, the eventual goal here is to ensure the developer can decide when to run the <code>script</code> within our island <code>template</code>.</p>
<p>As discussed in the proposed API implementation, consider the following:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:visible</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello future island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
</code></pre>
<p>With the <code>client:visible</code> attribute, we will only initialise the script when the island is visible (within the user viewport).</p>
<p>Without taking the <code>client:</code> attributes into question, let’s go ahead and initialise any template content as soon as the <code>&lt;mini-island&gt;</code> element is attached to the DOM.</p>
<p>Consider the annotated code below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MiniIsland</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// ... </span>

  <span class="hljs-comment">/**
   * The connectedCallback is a part of the custom elements lifecycle callback.
   * It is invoked anytime the custom element is attached to the DOM
   */</span>
  <span class="hljs-keyword">async</span> connectedCallback() {
    <span class="hljs-comment">/**
     * As soon as the island is connected, we will go ahead and hydrate the island
     */</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.hydrate();
  }

  hydrate() {
    <span class="hljs-comment">/**
     * Retrieve the relevant &lt;template&gt; child elements of the island
     */</span>
    <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
  }
}
</code></pre>
<p>Now, we will turn our attention to <code>getTemplates()</code>.</p>
<p>Since <code>&lt;mini-island&gt;</code> is a custom element extending a standard <code>HTMLElement</code>, we can access traditional DOM querying methods such as <code>querySelectorAll</code>.</p>
<p>So, let’s use <code>querySelectorAll</code> to retrieve a list of all child template elements with a <code>data-island</code> attribute.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>

getTemplates() {
  <span class="hljs-comment">/**
   * querySelectorAll() returns a list of the document's elements that match the specified group of selectors.
   * The selector, in this case, is of the form "template[data-island]."
   *, i.e., this.querySelectorAll("template[data-island]")
  */</span>
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.querySelectorAll(
    <span class="hljs-string">`template[<span class="hljs-subst">${MiniIsland.attributes.dataIsland}</span>]`</span>
  );
}
</code></pre>
<p>Note that the <code>data-island</code> attribute is retrieved in the code above via <code>MiniIsland.attributes.dataIsland</code>.</p>
<p>Also, do you remember why we’re using the <code>data-island</code> attribute?</p>
<p>This is because we want to give developers the flexibility to use standard <code>&lt;template&gt;</code> elements within our island. So, our island will only concern itself with <code>&lt;template data-island&gt;</code> elements.</p>
<p>Now that we’ve retrieved the template node via <code>getTemplates()</code>, we will grab its content and hydrate it.</p>
<p>Let’s update the <code>hydrate</code> method as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
hydrate() {
    <span class="hljs-comment">/**
     * Retrieve the relevant &lt;template&gt; child elements of the island
     */</span>
    <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
    <span class="hljs-comment">/**
     * Grab the DOM subtree within the template and replace the template with live content
     */</span>
    <span class="hljs-built_in">this</span>.replaceTemplates(relevantChildTemplates);
}
</code></pre>
<p>The <code>replaceTemplates</code> method is as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
 replaceTemplates(templates) {
    <span class="hljs-comment">/**
     * Iterate over all nodes in the template list.
     * templates refer to a NodeList of templates
     * node refers to a single &lt;template&gt;
     */</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> node <span class="hljs-keyword">of</span> templates) {
      <span class="hljs-comment">/**
       * replace the &lt;template&gt; with its HTML content
       * e.g., &lt;template&gt;&lt;p&gt;Hello&lt;/p&gt;&lt;/template&gt; becomes &lt;p&gt;Hello&lt;/p&gt;
       */</span>
      node.replaceWith(node.content);
    }
  }
</code></pre>
<p>Do you see what we’re doing here?</p>
<p>We’re grabbing the template DOM subtree, accessing its content and removing the <code>&lt;template&gt;</code> element.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 👀 before --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>

<span class="hljs-comment">&lt;!-- ✅ after --&gt;</span> 
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
</code></pre>
<p>This will attach the content to the DOM and kick off rendering and script loading.</p>
<p>With the templates now replaced, let’s go ahead and change the initial demo file to hold a more tangible example, as shown below:</p>
<pre><code class="lang-js">&lt;!-- 📂 demos/initial.html --&gt; 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello future island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span></span>
</code></pre>
<p>Note that the <code>&lt;template&gt;</code> element has the <code>data-island</code> attribute. This is how we signal to the island to hydrate the template content.</p>
<p>Now, refresh your browser and notice how the <code>console.warn</code> is triggered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-15-at-07.10.42.png" alt="Hydrated island script. " width="600" height="400" loading="lazy">
<em>Hydrated island script.</em></p>
<p>If you also inspect the elements, you’ll notice that the <code>&lt;template&gt;</code> has been replaced with its live child content.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-15-at-07.11.54.png" alt="Replaced island <template> element." width="600" height="400" loading="lazy">
<em>Replaced island &lt;template&gt; element.</em></p>
<p>We’re officially hydrating our island!</p>
<h3 id="heading-how-to-handle-lazy-hydration-via-client-attributes">How to handle lazy hydration via “client:” attributes</h3>
<p>Our current solution isn’t going to win us any awards. As soon as the island is attached to the DOM, we hydrate the island. Let’s make it better by introducing lazy hydration.</p>
<p>Lazy hydration is a form of partial hydration where we hydration later — not immediately after page load.</p>
<p>Lazy hydration is powerful because we can determine what’s essential or priority for our site, that is we can choose to delay the execution of unimportant JavaScript.</p>
<p>Update the <code>initial.html</code> document to consider our first use case. Here’s the updated code:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/initial.html --&gt;</span> 
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-string">"../mini-island.js"</span>;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- 👀 look here  --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"padding-bottom: 100vh"</span>&gt;</span>Scroll down<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- 👀 look here  --&gt;</span>     
    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:visible</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-15-at-07.18.38.png" alt="The client:visible demo" width="600" height="400" loading="lazy">
<em>The client:visible demo</em></p>
<p>We now have a paragraph that reads <code>scroll down</code>, which has a large enough bottom padding to push the island off the viewport.</p>
<p>With the <code>client:visible</code> attribute on the <code>&lt;mini-island&gt;</code>, we should not hydrate the island except when it’s visible, that is when the user scrolls to view the island.</p>
<p>However, test this in your browser.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-15-at-07.20.43.png" alt="The island is hydrated before being in view." width="600" height="400" loading="lazy">
<em>The island is hydrated before being in view.</em></p>
<p>The script is hydrated before we scroll (as soon as the page loads), and the <code>THIS IS A WARNING FROM AN ISLAND</code> message is logged.</p>
<p>Let’s prevent this from happening.</p>
<p>To achieve this, take a second look at the island hydrate method:</p>
<pre><code class="lang-js">  hydrate() {
    <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
    <span class="hljs-built_in">this</span>.replaceTemplates(relevantChildTemplates);
  }
</code></pre>
<p>Conceptually, we aim to wait for specific loading conditions to be met before we replace the island templates. In this case, we want to wait until the island is visible.</p>
<p>In pseudo-code:</p>
<pre><code class="lang-js">  hydrate() {
     <span class="hljs-comment">// Get island conditions, e.g., client:visible, client:idle</span>
    <span class="hljs-comment">// If these exist, wait for the conditions to be met before the next steps</span>
    <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
    <span class="hljs-built_in">this</span>.replaceTemplates(relevantChildTemplates);
  }
</code></pre>
<p>To manage our island loading conditions, let’s introduce a new <code>Conditions</code> class as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js </span>

<span class="hljs-comment">// ... </span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{

}

<span class="hljs-comment">// same existing code ...</span>
<span class="hljs-keyword">if</span> (<span class="hljs-string">"customElements"</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>) {
  <span class="hljs-built_in">window</span>.customElements.define(MiniIsland.tagName, MiniIsland);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.error(
    <span class="hljs-string">"Island cannot be initiated because Window.customElements is unavailable."</span>
  );
}
</code></pre>
<p>Within <code>Conditions</code>, we will introduce a static property that’s a key-value representation of the <code>client:</code> attribute and async methods.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/attr-promise.png" alt="An object with key-value corresponding to attribute and promise condition." width="600" height="400" loading="lazy">
<em>An object with key-value corresponding to attribute and promise condition.</em></p>
<p>Our conditions will be fulfilled at a later unknown time. So, we will represent these with async functions. These async functions will return promises that are resolved when the associated condition is met.</p>
<p>Here’s the representation of this in code:</p>
<pre><code class="lang-js"><span class="hljs-comment">// // 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
  <span class="hljs-comment">/**
   * A map of loading conditions and their respective async methods
   */</span>
  <span class="hljs-keyword">static</span> map = {
    <span class="hljs-attr">idle</span>: Conditions.waitForIdle,
    <span class="hljs-attr">visible</span>: Conditions.waitForVisible,
    <span class="hljs-attr">media</span>: Conditions.waitForMedia,
  };

  <span class="hljs-keyword">static</span> waitForIdle() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> resolve());
  }

  <span class="hljs-keyword">static</span> waitForVisible() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> resolve());
  }

  <span class="hljs-keyword">static</span> waitForMedia() {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> resolve());
  }
}
</code></pre>
<p>At the moment, the promises resolve immediately. But let’s go ahead and flesh out our use case for <code>client:visible</code>.</p>
<p>First, we will expose a <code>getConditions</code> method on the <code>Conditions</code> class. The method will check if a certain DOM node (in our case, our <code>mini-island</code>) has an attribute in the form of <code>client:${condition}</code>.</p>
<p>Below’s the annotated implementation:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
 <span class="hljs-comment">// ...</span>
  <span class="hljs-keyword">static</span> getConditions(node) {
    <span class="hljs-comment">/**
     * The result variable will hold the 
     * key:value representing condition:attribute.
     * e.g., For &lt;mini-island client:visible&gt;
     * result should be { visible: "" }
     * and for &lt;mini-island client:media="(max-width: 400px)" /&gt;
     * result should be { media: "(max-width: 400px)" }
     */</span>
    <span class="hljs-keyword">let</span> result = {};

    <span class="hljs-comment">/**
     * Loop over all keys of the static map, 
     *, i.e., ["idle", "visible", "media"]
     */</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> condition <span class="hljs-keyword">of</span> <span class="hljs-built_in">Object</span>.keys(Conditions.map)) {
      <span class="hljs-comment">/**
       * Check if the node has the attribute 
       * of the form "client:${key}".
       */</span>
      <span class="hljs-keyword">if</span> (node.hasAttribute(<span class="hljs-string">`client:<span class="hljs-subst">${condition}</span>`</span>)) {
        <span class="hljs-comment">/**
         * If the node has the attribute...
         * save the condition (key) - attribute (value)    
         * to the result object
         */</span>
        result[condition] = node.getAttribute(<span class="hljs-string">`client:<span class="hljs-subst">${condition}</span>`</span>);
      }
    }
    <span class="hljs-comment">/** return the result */</span>
    <span class="hljs-keyword">return</span> result 
  }
}
</code></pre>
<p>Next, we will expose a <code>hasConditions</code> method responsible for checking if an island has one or more conditions:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
 <span class="hljs-comment">// ...</span>
  <span class="hljs-keyword">static</span> hasConditions(node) {
    <span class="hljs-comment">/**
     * Using the "getConditions" static class method, retrieve
     * a conditions attributes map
     */</span>
    <span class="hljs-keyword">const</span> conditionAttributesMap = Conditions.getConditions(node);

    <span class="hljs-comment">/**
     * Check the length of the result keys to determine if there are
     * any loading conditions on the node
     */</span>
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(conditionAttributesMap).length &gt; <span class="hljs-number">0</span>;
  }
}
</code></pre>
<p>With <code>hasConditions</code> and <code>getConditions</code> ready, let’s go ahead and use these within the <code>MiniIsland</code> hydrate method.</p>
<p>First, here’s the current state of the <code>hydrate</code> method.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MiniIsland</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
 <span class="hljs-comment">// ...</span>
  hydrate() {
    <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
    <span class="hljs-built_in">this</span>.replaceTemplates(relevantChildTemplates);
  }
 <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now, update the method with the following. I have provided annotations to make it easier to understand.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MiniIsland</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
 <span class="hljs-comment">// ...</span>
  <span class="hljs-keyword">async</span> hydrate() {
    <span class="hljs-comment">/**
     * conditions will hold an array of potential
     * promises to be resolved before hydration
     */</span>
    <span class="hljs-keyword">const</span> conditions = [];

    <span class="hljs-comment">/**
     * Get the condition - attribute value map
     * NB: the argument passed to 
     * `Conditions.getConditions` is the island node
     */</span>
    <span class="hljs-keyword">let</span> conditionAttributesMap = Conditions.getConditions(<span class="hljs-built_in">this</span>);

    <span class="hljs-comment">/**
     * Loop over the conditionAttributesMap variable
     */</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> condition <span class="hljs-keyword">in</span> conditionAttributesMap) {
      <span class="hljs-comment">/**
       * Grab the condition async function from the static map
       * Remember that the function that returns a promise when invoked
       */</span>
      <span class="hljs-keyword">const</span> conditionFn = Conditions.map[condition];

      <span class="hljs-comment">/**
       * Check if the condition function exists
       */</span>
      <span class="hljs-keyword">if</span> (conditionFn) {
        <span class="hljs-comment">/**
         * Invoke the condition function with two arguments:
         * (1) The value of the condition attribute set on the node 
         * For example: 
         * for &lt;mini-island client:visible /&gt; this is an empty string ""
         * for &lt;mini-island client:media="(max-width: 400px)" /&gt;
         * This is the string "(max-width: 400px)"
         *
         * (2) The node, i.e., the island DOM node
         */</span>
        <span class="hljs-keyword">const</span> conditionPromise = conditionFn(
          conditionAttributesMap[condition],
          <span class="hljs-built_in">this</span>
        );

        <span class="hljs-comment">/**
         * append the promise to the conditions array
         */</span>

        conditions.push(conditionPromise);
      }

      <span class="hljs-comment">/**
       * Await all promise conditions to be 
       * resolved before replacing the template nodes
       */</span>
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(conditions);
      <span class="hljs-comment">/**
       * Retrieve the relevant &lt;template&gt; child elements of the island
       */</span>
      <span class="hljs-keyword">const</span> relevantChildTemplates = <span class="hljs-built_in">this</span>.getTemplates();
      <span class="hljs-comment">/**
       * Grab the DOM subtree in the template
       * and replace the template with live content
       */</span>
      <span class="hljs-built_in">this</span>.replaceTemplates(relevantChildTemplates);
    }
  }
}
</code></pre>
<p>At the moment, remember that our condition promises in <code>Conditions</code> resolve immediately.</p>
<p>Before we test our solution, we must satisfy the condition for the <code>client:visible</code> attribute.</p>
<p>How do we ensure that the island is visible?</p>
<p>The best solution here is to use the <code>IntersectionObserver</code> API. Let’s take advantage of that as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
 <span class="hljs-comment">// ...</span>
   <span class="hljs-comment">/**
   *
   * <span class="hljs-doctag">@param <span class="hljs-variable">noop</span></span> - the value of the condition attribute.
   * This is named "noop" as it is not relevant in this condition, i.e.,
   * as per our API, client:visible always has a falsy attribute value, e.g.,
   * ✅ &lt;mini-island client:visible /&gt;
   * ❌ &lt;mini-island client:visible={some-value} /&gt;
   * <span class="hljs-doctag">@param <span class="hljs-variable">el</span></span> - the node element.
   * This represents our island DOM node passed during hydration
   * <span class="hljs-doctag">@returns </span>- a Promise that resolves when "el" is visible
   * NB: relies on the Intersection Observer API
   */</span>
  <span class="hljs-keyword">static</span> waitForVisible(noop, el) {
    <span class="hljs-comment">/**
     * If the Intersection Observer API is not available,
     * go ahead and exit immediately.
     */</span>
    <span class="hljs-keyword">if</span> (!(<span class="hljs-string">"IntersectionObserver"</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>)) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">/**
     * Otherwise, set up a new Promise that is resolved when the
     * node parameter (our island DOM node) is visible
     */</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
      <span class="hljs-keyword">let</span> observer = <span class="hljs-keyword">new</span> IntersectionObserver(<span class="hljs-function">(<span class="hljs-params">entries</span>) =&gt;</span> {
        <span class="hljs-keyword">let</span> [entry] = entries;

        <span class="hljs-comment">/**
         * is it visible?
         */</span>
        <span class="hljs-keyword">if</span> (entry.isIntersecting) {
          <span class="hljs-comment">/**
           * remove observer
           */</span>
          observer.unobserve(entry.target);
          <span class="hljs-comment">/**
           * resolve promise
           */</span>
          resolve();
        }
      });

      <span class="hljs-comment">/**
       * set up the observer on the "el" argument
       */</span>
      observer.observe(el);
    });
  }
}
</code></pre>
<p>This is excellent work!</p>
<p>Return to the demo <code>initial.html</code> application running in your browser, refresh, and notice how the island behaves.</p>
<p>The island is no longer hydrated until we scroll down and the island is visible 🎉</p>
<p>Well done, mate! Give yourself a round of applause and a cuppa tea. We’ve smashed it. Take a pause if you need one, and let’s get on the next set of requirements when you’re ready.</p>
<h3 id="heading-how-to-support-the-clientidle-and-clientmedia-conditions">How to support the <code>client:idle</code> and <code>client:media</code> conditions</h3>
<p>We have a pretty robust solution within the <code>hydrate</code> method. So, to support more loading conditions, we have to flesh out the other condition promises.</p>
<h4 id="heading-waitforidle">waitForIdle</h4>
<p>Take a pause and consider how we should do this. For example, what heuristic do we rely on the determine when the browser is “idle”?</p>
<p>It begs the question, what’s “idle” in this case?</p>
<p>Well, for our implementation, the definition of idle is when the browser is not actively loading any resources, and no latency-critical events, such as animation and input responses, are in progress.</p>
<p>To achieve this, we will rely on two properties:</p>
<p>(i) The <code>document.readyState</code> event</p>
<p>If the value of this event is <code>complete</code>, the document and all sub-resources have finished loading. This includes all dependent resources such as stylesheets, scripts, iframes, and images.</p>
<p>Listening to this event ensures we hydrate the island when all other essential assets have been downloaded.</p>
<p>(ii) The <code>window.requestIdleCallback()</code> method</p>
<p>By definition, the <code>window.requestIdleCallback()</code> method will queue a function to be called when a browser is idle. This ensures the function is only executed when the browser handles no latency-critical event.</p>
<p>Let’s put these together and create a promise that resolves when the <code>document.readyState</code> event is <code>complete</code>, and no latency-critical events are being handled.</p>
<p>Here’s the implementation below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
 <span class="hljs-comment">// ...</span>
 <span class="hljs-keyword">static</span> waitForIdle() {
    <span class="hljs-keyword">const</span> onLoad = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
      <span class="hljs-comment">/**
       * The document.readyState property 
       * describes the loading state of the document.
       */</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.readyState !== <span class="hljs-string">"complete"</span>) {
        <span class="hljs-comment">/**
         * Set up an event listener for the "load" event.
         * The load event is fired when the whole page 
         * has loaded, including all dependent resources
         * such as stylesheets, scripts, iframes, and
         * images.
         */</span>
        <span class="hljs-built_in">window</span>.addEventListener(
          <span class="hljs-string">"load"</span>,
          <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-comment">/**
             * resolve this promise once the "load" event is fired.
             */</span>
            resolve();
          },
          <span class="hljs-comment">/**
           * Remove the listener after the first 
           * invocation of the "load" event.
           */</span>
          { <span class="hljs-attr">once</span>: <span class="hljs-literal">true</span> }
        );
      } <span class="hljs-keyword">else</span> {
        resolve();
      }
    });

    <span class="hljs-comment">/**
     * The window.requestIdleCallback() method queues a  
     * function to be called during a browser's idle periods. 
     * This enables developers to perform background 
     * and low-priority work on the main event loop
     */</span>

    <span class="hljs-keyword">const</span> onIdle = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
      <span class="hljs-comment">/**
       * Check for "requestIdleCallback" support
       */</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-string">"requestIdleCallback"</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>) {
        requestIdleCallback(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-comment">/**
           * pass the promise resolve function 
           * as the operation to be queued
           */</span>
          resolve();
        });
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">/**
         * resolve the promise immediately
         * if requestIdleCallback isn't supported
         */</span>
        resolve();
      }
    });

    <span class="hljs-comment">/**
     * waitForIdle will wait for both 
     * promises to be resolved, i.e., onIdle and onLoad
     */</span>
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all([onIdle, onLoad]);
  }
}
</code></pre>
<p>Now, go to the <code>initial.html</code> demo file and update the file as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/initial.html --&gt;</span> 
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-comment">&lt;!-- content unchanged --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"https://raw.githubusercontent.com/ohansemmanuel/larder/main/large_image.jpeg"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"34MB large satellite image from Effigis."</span>
    /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:idle</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Note that we’ve introduced a large <code>34MB</code> image from <a target="_blank" href="https://effigis.com/en/solutions/satellite-images/satellite-image-samples/">Effigis</a> and passed a <code>client:idle</code> attribute to <code>&lt;mini-island&gt;</code>.</p>
<p>Tip: consider downloading the large image and referencing it locally instead of hitting the GitHub servers repeatedly.</p>
<p>The large image will keep the browser busy for some time. Before testing this in the browser, I suggest disabling the browser cache via developer tools.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-15-at-11.43.31.png" alt="The disable cache property in Firefox." width="600" height="400" loading="lazy">
<em>The disable cache property in Firefox.</em></p>
<p>Open the page in the browser and notice how the script is not invoked until the browser has finished loading the large image and is in an idle state.</p>
<p>This is great!</p>
<p>Instead of potentially allowing non-priority JavaScript code to compete for the browser resources, we’ve shelved that to be initialised later during the browser’s idle period.</p>
<h4 id="heading-waitformedia">waitForMedia</h4>
<p>The media condition is fascinating. The island is only hydrated when a CSS media query is met. This is useful for mobile toggles or other elements only visible on specific screen sizes.</p>
<p>We will leverage the <code>window.matchMedia()</code> to determine if the document matches the media query string.</p>
<p>Here’s the annotated implementation:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 mini-island.js</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conditions</span> </span>{
<span class="hljs-comment">/**
   *
   * <span class="hljs-doctag">@param <span class="hljs-type">{*}</span> <span class="hljs-variable">query</span></span> - the query string 
   * passed to the client:media attribute
   * <span class="hljs-doctag">@returns </span>Promise that resolves when
   * the document matches the passed CSS media query
   */</span>
  <span class="hljs-keyword">static</span> waitForMedia(query) {
    <span class="hljs-comment">/**
     * window.matchMedia(query) returns A MediaQueryList object.
     * This object stores information on a media query
     * applied to a document and one of the properties 
     * on this object is "matches" - a boolean for
     * whether the document matches the media query or not.
     * Create a new simple object of similar form, i.e.,  
     * with a "matches" property
     */</span>
    <span class="hljs-keyword">let</span> queryList = {
      <span class="hljs-attr">matches</span>: <span class="hljs-literal">true</span>,
    };

    <span class="hljs-keyword">if</span> (query &amp;&amp; <span class="hljs-string">"matchMedia"</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>) {
     <span class="hljs-comment">/** 
       Override our stub with the actual query list
     */</span>
      queryList = <span class="hljs-built_in">window</span>.matchMedia(query);
    }

    <span class="hljs-comment">/**
     * If matchMedia isn't supported or the 
     * query is truthy, return immediately
     * e.g., truthy if matchMedia isn't in the window object
     */</span>
    <span class="hljs-keyword">if</span> (queryList.matches) {
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
      <span class="hljs-comment">/**
       * Set a new listener on the queryList object
       * and resolve the promise when there's a match
       */</span>
      queryList.addListener(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (e.matches) {
          resolve();
        }
      });
    });
  }
}
</code></pre>
<p>With this in place, we may update the <code>initial.html</code> demo file to the following:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- content remains the same --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Initial island demo<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:media</span>=<span class="hljs-string">"(max-width: 400px)"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello island<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"THIS IS A WARNING FROM AN ISLAND"</span>);
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Now refresh the page in your browser and notice how the script is never initialised until you resize your browser window to match the CSS query, that is a maximum width of <code>400px</code>.</p>
<h3 id="heading-how-to-support-frameworks-vue-petite-vue-and-preact">How to support frameworks: Vue, Petite-vue, and Preact</h3>
<p>Our <code>&lt;mini-island&gt;</code> implementation is simple yet effective. But you may not appreciate it until you’ve seen it used with other frameworks. Coincidentally, this is also a part of our objectives – to develop a framework-agnostic solution.</p>
<p>The following sections show framework examples utilising <code>&lt;mini-island&gt;</code>. To do this, we will build out the same framework user interface in the form of a simple counter.</p>
<h4 id="heading-vue">Vue</h4>
<p>Vue is a JavaScript framework for building user interfaces. Vue’s mental model builds on top of standard HTML, CSS, and JavaScript, making it easy to understand for most people.</p>
<p>As expected of a modern UI framework, Vue is declarative and reactive.</p>
<p>Let’s go ahead and build a counter application leveraging Vue and <code>&lt;mini-island&gt;</code> as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/vue.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Vue mini-island demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-string">"../mini-island.js"</span>;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Vue<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>&gt;</span>This is a vue counter <span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      By default, this button does not load any Javascript and isn't hydrated.
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      Resize your browser to match the media query:
      <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>(max-width: 400px)<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span> to hydrate the island
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:media</span>=<span class="hljs-string">"(max-width: 400px)"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"vue-app"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"count++"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>⬆️<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Vue<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">v-html</span>=<span class="hljs-string">"count"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>-<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>clicks<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"https://unpkg.com/vue@3.2.36/dist/vue.esm-browser.prod.js"</span>;

          createApp({
            <span class="hljs-attr">data</span>: <span class="hljs-function">() =&gt;</span> ({ <span class="hljs-attr">count</span>: <span class="hljs-number">0</span> }),
          }).mount(<span class="hljs-string">"#vue-app"</span>);
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>It’s okay if you do not understand the Vue code snippets. What’s important is the following:</p>
<ul>
<li>The HTML markup is rendered as soon as the HTML page is loaded and parsed.</li>
<li>This includes the static counter markup within <code>mini-island</code>, that is:<br>      <code>&lt;div id="vue-app"&gt;</code><br>        <code>&lt;button @click="count++"&gt;</code><br>          <code>&lt;span&gt;⬆️&lt;/span&gt;</code><br>      <code>` </code><div><code>`&lt;strong&gt;Vue&lt;/strong&gt;</code><br>            <code>&lt;div&gt;</code><br>              <code>&lt;span v-html="count"&gt;0&lt;/span&gt;</code><br>              <code>&lt;span&gt;-&lt;/span&gt;</code><br>              <code>&lt;span&gt;clicks&lt;/span&gt;</code><br>            <code>&lt;/div&gt;</code><br>          <code>&lt;/div&gt;</code><br>        <code>&lt;/button&gt;</code><br>      <code>&lt;/div&gt;</code></div></li>
<li>But the counter is not hydrated at this point. So, clicking the counter will not increase the count. This is because Vue hasn’t been loaded, and the counter button is not yet hydrated.</li>
<li>Consider the loading condition set on the island, that is <code>client:media="(max-width: 400px)"</code>.</li>
<li>Now, resize your browser (take advantage of the developer tools) to a width less than <code>400px</code> to hydrate the island.</li>
<li>This will import Vue and hydrate the counter. Here’s the code responsible for within the island <code>template</code>:<br>      <code>&lt;template data-island&gt;</code><br>          <code>&lt;script type="module"&gt;</code><br>                <code>import { createApp } from "https://unpkg.com/vue@3.2.36/dist/vue.esm-browser.prod.js";</code><br>      <code>` </code>createApp({<code>`data: () =&gt; ({ count: 0 }),</code><br>            <code>}).mount("#vue-app");</code><br>          <code>&lt;/script&gt;</code><br>      <code>&lt;/template&gt;</code></li>
<li>The counter should now be hydrated. We may now click to our heart’s content.</li>
</ul>
<h4 id="heading-petite-vue">Petite-vue</h4>
<p>From the official Vue <a target="_blank" href="https://vuejs.org/guide/extras/ways-of-using-vue.html#standalone-script">documentation</a>, Vue also provides an alternative distribution called petite-vue that is optimised for progressively enhancing existing HTML.</p>
<p>This is perfect for our use case.</p>
<p>Let’s go ahead and create a similar demo using <code>petite-vue</code> as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/petite-vue.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Vue mini-island demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-string">"../mini-island.js"</span>;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Petite-vue<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>&gt;</span>This is a petite-vue counter <span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      By default, this button does not load any Javascript and isn't hydrated.
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      Resize your browser to match the media query:
      <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>(max-width: 400px)<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span> to hydrate the island
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:media</span>=<span class="hljs-string">"(max-width: 400px)"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"vue-app"</span> <span class="hljs-attr">v-scope</span>=<span class="hljs-string">"{ count: 0 }"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"count++"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>⬆️<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Petite-vue<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">v-html</span>=<span class="hljs-string">"count"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>-<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>clicks<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js"</span>;

          createApp().mount(<span class="hljs-string">"#vue-app"</span>);
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Apart from a few changes, the code above is identical to the standard Vue API.</p>
<p>Here’s how this works:</p>
<ul>
<li>The HTML markup is rendered as soon as the HTML page is loaded and parsed.</li>
<li>This includes the static counter markup within <code>mini-island</code>, that is:<br>       <code>&lt;div id="vue-app" v-scope="{ count: 0 }"&gt;</code><br>              <code>&lt;button @click="count++"&gt;</code><br>                <code>&lt;span&gt;⬆️&lt;/span&gt;</code><br>      <code>` </code><div><code>`&lt;strong&gt;Vue&lt;/strong&gt;</code><br>                  <code>&lt;div&gt;</code><br>                    <code>&lt;span v-html="count"&gt;0&lt;/span&gt;</code><br>                    <code>&lt;span&gt;-&lt;/span&gt;</code><br>                    <code>&lt;span&gt;clicks&lt;/span&gt;</code><br>                  <code>&lt;/div&gt;</code><br>                <code>&lt;/div&gt;</code><br>              <code>&lt;/button&gt;</code><br>            <code>&lt;/div&gt;</code></div></li>
<li>NB: the significant difference in the code above is the introduction of the <code>v-scope</code> attribute to hold our count data variable.</li>
<li>The counter, however, is not hydrated at this point. So, clicking the counter will not increase the count. This is because petite-vue hasn’t been loaded, and the counter button is not yet hydrated.</li>
<li>Consider the loading condition set on the island, that is <code>client:media="(max-width: 400px)"</code></li>
<li>Now, resize your browser (use the developer tools) to a width less than <code>400px</code> to hydrate the island.</li>
<li>This will import Petite-vue and hydrate the counter. Here’s the code responsible for within the island <code>template</code>:<br>      <code>&lt;template data-island&gt;</code><br>        <code>&lt;script type="module"&gt;</code><br>          <code>import { createApp } from "https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js";</code><br>      <code>` </code>createApp().mount("#vue-app");<code>`&lt;/script&gt;</code><br>      <code>&lt;/template&gt;</code></li>
<li>The counter should now be hydrated. We may now click to our heart’s content.</li>
</ul>
<h4 id="heading-preact">Preact</h4>
<p>Preact is a fast 3kB alternative to React with the same modern API, and it can be used in the browser without any transpiration steps.</p>
<p>Let’s go ahead and create a similar demo using Preact, as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 demos/preact.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Preact mini-island demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-string">"../mini-island.js"</span>;
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Preact<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a preact counter<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>By default, this button is not rendered or hydrated<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">mini-island</span> <span class="hljs-attr">client:idle</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"preact-app"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>
          &gt;</span>The counter island will be rendered and hydrated just above this mark
          when the browser is idle<span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>
        &gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">data-island</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>&gt;</span><span class="javascript">
          <span class="hljs-keyword">import</span> { h, Component, render } <span class="hljs-keyword">from</span> <span class="hljs-string">"https://esm.sh/preact"</span>;
          <span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"https://esm.sh/preact/hooks"</span>;
          <span class="hljs-keyword">import</span> htm <span class="hljs-keyword">from</span> <span class="hljs-string">"https://esm.sh/htm"</span>;

          <span class="hljs-comment">// Initialize htm with Preact</span>
          <span class="hljs-keyword">const</span> html = htm.bind(h);

          <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params">props</span>) </span>{
            <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

            <span class="hljs-keyword">const</span> increment = <span class="hljs-function">() =&gt;</span>
              setCount(<span class="hljs-function">(<span class="hljs-params">currentCount</span>) =&gt;</span> currentCount + <span class="hljs-number">1</span>);

            <span class="hljs-keyword">return</span> html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=</span></span><span class="hljs-subst">${() =&gt; increment()}</span><span class="xml"><span class="hljs-tag">&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>⬆️ <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Preact<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span></span><span class="hljs-subst">${count}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>-<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>clicks<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>`</span>;
          }

          render(html`<span class="xml"><span class="hljs-tag">&lt;</span></span><span class="hljs-subst">${App}</span><span class="xml"><span class="hljs-tag"> /&gt;</span>`</span>, <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"preact-app"</span>));
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">mini-island</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>The document must be completely loaded<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>The large image below must complete loading<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"https://raw.githubusercontent.com/ohansemmanuel/larder/main/large_image.jpeg"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"34MB large satellite image from Effigis."</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The code above behaves differently from the previous framework examples.</p>
<p>Here’s how this works:</p>
<ul>
<li>The HTML markup is rendered after loading and parsing the HTML.</li>
<li>The counter, however, is not rendered or hydrated. This is because <code>mini-island</code> has a <code>client: idle</code> loading condition.</li>
<li>The counter will be rendered and hydrated when the browser is idle. For this to be the case, the large image in the document must complete loading.</li>
<li>Once this is loaded (including other associated document resources), Preact renders and hydrates the counter when the browser is idle.</li>
<li>The counter should now be hydrated; we may now click to our heart’s content.</li>
</ul>
<h2 id="heading-wrapping-up-this-chapter-2">Wrapping Up This Chapter</h2>
<p>When it comes to performance and deciding what rendering solution works for your application, no single solution fits all applications. </p>
<p>Depending on the application, we always have to make tradeoffs. But the island architecture provides very performant client applications without sacrificing rich interactivity.</p>
<p>The main goal of this chapter was to peel back the perceived layer of complexity and strip down component islands to a fundamental digestible unit with <code>&lt;mini-island&gt;</code>.</p>
<p>Now, we will take this knowledge into exploring component islands in Astro, and (almost) nothing will surprise you. That’s the definition of proper understanding.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-136.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter four.</em></p>
<h1 id="heading-chapter-4-the-secret-life-of-astro-component-islands-1">Chapter 4: The Secret Life of Astro Component Islands</h1>
<p>Component islands are the secret to Astro’s super-fast narrative. It’s time to learn everything about them.</p>
<h2 id="heading-what-youll-learn-3">What You’ll Learn</h2>
<ul>
<li>Hands-on experience working with framework components in Astro.</li>
<li>Responsible hydration and why it matters.</li>
<li>How component islands work in Astro.</li>
<li>Why islands are essential.</li>
</ul>
<h2 id="heading-how-islands-work-in-astro">How Islands Work in Astro</h2>
<p>Assume we’ve got an Astro application with static content: a navigation bar, some main content, a footer, and a side pane.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-6.png" alt="A static astro page structure" width="600" height="400" loading="lazy">
<em>A static astro page structure</em></p>
<p>If we need to introduce some interactivity content in the side pane of the application, how could we achieve this?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/b-1.png" alt="Adding interactive content to the static page" width="600" height="400" loading="lazy">
<em>Adding interactive content to the static page</em></p>
<p>Astro provides the following ways to do this:</p>
<ul>
<li>We've seen how this works: introduce a <code>&lt;script&gt;</code> element to handle interactivity within your Astro component.</li>
<li>Use a supported framework component, and leverage a component island.</li>
</ul>
<p>The second option is the focus of this chapter.</p>
<p>At the time of writing, Astro lets you use components built with <code>React</code>, <code>Preact</code>, <code>Svelte</code>, <code>Vue</code>, <code>SolidJS</code>, <code>AlpineJS</code> or <code>Lit</code> in your Astro components. Moving on, I’ll refer to these as <strong>framework components</strong>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/framework-components.png" alt="Leveraging framework components in Astro." width="600" height="400" loading="lazy">
<em>Leveraging framework components in Astro.</em></p>
<p>So, why would we use framework components and not just provide native support via a <code>&lt;script&gt;</code> element?</p>
<p>It would be best to stick with a <code>&lt;script&gt;</code> element in cases where you can get by with vanilla JavaScript or TypeScript. But there are cases where we may favour a framework component. For example:</p>
<ul>
<li><strong>Design systems</strong>: using a pre-existing design system in an Astro project can save time, depending on the use case. It also helps keep all your applications looking and feeling the same way.</li>
<li><strong>Open-source</strong>: we might consider utilising a feature-rich open-source framework component already existing instead of building some highly interactive component from scratch. This way, we can easily use an open-source framework component in Astro.</li>
<li><strong>Ease of development</strong>: we may find building richer stateful user interfaces easier, more manageable, and faster to implement via framework components than vanilla JavaScript / TypeScript provided in <code>&lt;script&gt;</code>.</li>
</ul>
<p>To use a framework component in Astro, we leverage component islands.</p>
<p>Let’s return to our example application.</p>
<p>Assuming we’ve weighed the pros and cons and decided to introduce a framework component, the following section highlights the steps to take.</p>
<h3 id="heading-step-1-build-an-astro-site">Step 1: Build an Astro site</h3>
<p>We can’t use framework components without having some Astro site to use them in.</p>
<p>We’ve already seen how to build static sites with Astro, so creating a new static project is unnecessary. Instead, let’s start a new Astro with a project I’ve prepared.</p>
<p>Clone the project:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/understanding-astro/astro-islands-visual-example.git
</code></pre>
<p>Then, install dependencies and start the application via the following:</p>
<pre><code class="lang-bash">npm install 
npm run start
</code></pre>
<p>This will run the project in one of your local ports.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-11-at-14.06.52@2x.png" alt="The astro islands visual example project " width="600" height="400" loading="lazy">
<em>The astro islands visual example project</em></p>
<p>The project takes the same form as our hypothetical example — it’s got a navigation, main content, footer, and side pane.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/a-1-1.png" alt="A static astro page structure" width="600" height="400" loading="lazy">
<em>A static astro page structure</em></p>
<p>Within the side pane, there’s a <code>slot</code> to render our interactive content via a framework component.</p>
<p>In <code>src/pages/index.astro</code>, you’ll find the code responsible for rendering the page as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span> /&gt;</span></span>
</code></pre>
<p><code>DefaultIslandLayout</code> provides the layout for the entire page and includes a <code>slot</code> for rendering whatever children elements are passed to it. Initialise the project locally and take a look.</p>
<h3 id="heading-step-2-install-the-framework-integration">Step 2: Install the framework integration</h3>
<p>Astro provides official integrations for the supported framework components. In this example, we’ll use the <code>react</code> framework.</p>
<p>It’s important to note that the steps described here are the same regardless of the framework component of your choosing. Therefore, I’m sticking to <code>react</code> as many more developers arguably use it.</p>
<p>The most convenient way to add your framework integration is to use the <code>astro add</code> command, for example to add <code>react</code>, run the following commands:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># using NPM</span>
npx astro add react
<span class="hljs-comment"># Using Yarn</span>
yarn astro add react
<span class="hljs-comment"># Using PNPM</span>
pnpm astro add react
</code></pre>
<p>This will automatically add the relevant framework dependencies to our project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-11-at-14.56.20@2x.png" alt="Running astro add react." width="600" height="400" loading="lazy">
<em>Running astro add react.</em></p>
<p>The command will also automatically update our project configuration, <code>astro.config.mjs</code>, to include the framework integration.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-11-at-14.57.00@2x.png" alt="Updating the project config file." width="600" height="400" loading="lazy">
<em>Updating the project config file.</em></p>
<p>Essentially, this breaks down the installation of a framework into our Astro project into two distinct processes:</p>
<ol>
<li>Install the framework dependencies.</li>
<li>Add the relevant framework integration in the project config file.</li>
</ol>
<p>If we didn’t use the <code>Astro add</code> command, we could achieve the same results manually by installing the framework dependencies and adding the framework integration in our project configuration file.</p>
<h3 id="heading-step-3-write-the-component-framework">Step 3: Write the component framework</h3>
<p>Our framework component will be a glorified counter. Assuming the page consists of an article a reader can upvote, we’ll build an upvote button.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/upvote-counter.png" alt="The upvote counter illustrated." width="600" height="400" loading="lazy">
<em>The upvote counter illustrated.</em></p>
<p>Here’s the annotated <code>UpvoteContent</code> React component:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/components/UpvoteContent.tsx --&gt;

<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-comment">// The maximum number of upvotes available </span>
<span class="hljs-keyword">const</span> MAX_COUNT = <span class="hljs-number">50</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UpvoteContent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// the initial state of the upvote counter </span>
  <span class="hljs-keyword">const</span> [upvoteCount, setUpvoteCount] = useState(<span class="hljs-number">0</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
       // <span class="hljs-attr">update</span> <span class="hljs-attr">state</span> <span class="hljs-attr">when</span> <span class="hljs-attr">a</span> <span class="hljs-attr">user</span> <span class="hljs-attr">clicks</span> <span class="hljs-attr">the</span> <span class="hljs-attr">counter.</span> <span class="hljs-attr">check</span> <span class="hljs-attr">if</span>
       //<span class="hljs-attr">The</span> <span class="hljs-attr">maximum</span> <span class="hljs-attr">count</span> <span class="hljs-attr">value</span> <span class="hljs-attr">was</span> <span class="hljs-attr">reached</span> <span class="hljs-attr">first.</span> 
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
          setUpvoteCount((prevCount) =&gt;
            prevCount <span class="hljs-tag">&lt; <span class="hljs-attr">MAX_COUNT</span> ? <span class="hljs-attr">prevCount</span> + <span class="hljs-attr">1</span> <span class="hljs-attr">:</span> <span class="hljs-attr">prevCount</span>
          );
        }}
      &gt;</span>
       { /** Upvote counter SVG icon. shortened for brevity **/}
        <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> /&gt;</span>
        Upvote
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{`${upvoteCount} upvotes`}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        {/** show a growing visual bar based on the upvote count **/}
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
            <span class="hljs-attr">width:</span> `${<span class="hljs-attr">upvoteCount</span>}%`,
          }}
        /&gt;</span>

        {/** show a warning if the maximum count has been reached**/}
        {upvoteCount === MAX_COUNT &amp;&amp; (
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            Max upvote reached
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};
</code></pre>
<p>Don’t worry if you don’t understand <code>react</code>. The goal here is to know how to work with framework components in Astro. We could build the same component using any other framework we choose, like Vue or Svelte.</p>
<h3 id="heading-step-4-render-the-component-framework">Step 4: Render the component framework</h3>
<p>Let’s go ahead and render the framework component as shown below:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/pages/none.astro --&gt;
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<ul>
<li>Create a new page in <code>src/pages/none.astro</code></li>
<li>Render the <code>UpvoteContent</code> component as a child of <code>DefaultIslandLayout</code>, that is:<br>      <code>&lt;DefaultIslandLayout&gt;</code><br>        <code>&lt;UpvoteContent /&gt;</code><br>      <code>&lt;/DefaultIslandLayout&gt;</code></li>
<li><code>DefaultIslandLayout</code> takes the <code>UpvoteContent</code> child component and renders it within its layout slot.</li>
</ul>
<p>Now, open the <code>/none</code> page in the browser, and we should have the rendered <code>UpvoteContent</code> component rendered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-13-at-12.59.52@2x.png" alt="Rendering the framework component." width="600" height="400" loading="lazy">
<em>Rendering the framework component.</em></p>
<p>The upvote counter is successfully rendered, but clicking the button doesn’t increase the count!</p>
<p>What’s going on? 🥹</p>
<h4 id="heading-its-not-a-bug-its-a-feature">It’s not a bug. It’s a feature.</h4>
<p>By default, when you render a framework component, Astro automatically renders it to HTML ahead of time, that is Astro strips out all of the component JavaScript.</p>
<p>Essentially, you get no interactivity from framework components by default.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/no-js-by-default.png" alt="If Astro launched a Twitter campaign, #NoJavscriptByDefault would make an excellent hashtag." width="600" height="400" loading="lazy">
<em>If Astro launched a Twitter campaign, #NoJavscriptByDefault would make an excellent hashtag.</em></p>
<p>As it stands, what we currently have is technically not an island. We have the component markup rendered with no interactivity.</p>
<h2 id="heading-responsible-hydration">Responsible Hydration</h2>
<p>Astro helps you minimise JavaScript bloat when using framework components by leveraging responsible hydration.</p>
<p>If Astro renders your framework component to <code>100%</code> HTML, how do you hydrate (make interactive) the framework component?</p>
<p>In the context of Astro development, responsible hydration refers to Astro making no decision on when to hydrate your framework component and leaving that decision entirely up to the developer.</p>
<p>This is powerful but comes with the burden of decision resting on us — developers.</p>
<p>When technical decisions such as this need to be made, they must be made against specific requirements. In this case, the decision lies in evaluating two criteria, namely <strong>priority</strong> and <strong>interactivity</strong>.</p>
<ul>
<li>Priority: is this a high or low-priority user interface element?</li>
<li>Interactivity: should this element be interactive as soon as possible?</li>
</ul>
<p>We may represent this on a 2d plane as follows:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/hydration-plane.png" alt="Representing priority and interactivity on a 2d plane." width="600" height="400" loading="lazy">
<em>Representing priority and interactivity on a 2d plane.</em></p>
<p>There are four attributes you can pass to your rendered framework component, for example:</p>
<pre><code class="lang-js">&lt;ReactComponent attribute /&gt;
</code></pre>
<p>These attributes are called client directives (or, more generically, template directives). Here are the five client directives that control the hydration of your framework component:</p>
<ul>
<li><code>client:load</code></li>
<li><code>client:only</code></li>
<li><code>client:visible</code></li>
<li><code>client:media</code></li>
<li><code>client:idle</code></li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/responsible-hydration-astro-plane.png" alt="Representing the client template directives on a priority - interactivity plane." width="600" height="400" loading="lazy">
<em>Representing the client template directives on a priority - interactivity plane.</em></p>
<h3 id="heading-clientload"><code>client:load</code></h3>
<p><code>client:load</code> should be used for high-priority interface elements that must be interactive as soon as possible.</p>
<ul>
<li>Priority: high</li>
<li>Interactivity: high</li>
</ul>
<p>We may go ahead and render our <code>UpvoteContent</code> component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:load</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<p>Here are the hydration steps:</p>
<ol>
<li>Render the component HTML (not hydrated).</li>
<li>Wait for the page to load.</li>
<li>Load component JavaScript.</li>
<li>Hydrate component.</li>
</ol>
<p>The load event is fired when the page has loaded, including all dependent resources such as stylesheets, scripts, iframes, and images.</p>
<p>It’s important to note that clicking the upvote button will not trigger any upvotes before hydration.</p>
<h3 id="heading-clientonly"><code>client:only</code></h3>
<p><code>client:only</code> behaves similarly to <code>client:load</code>. It should be used for elements where you want to skip server-side rendering (the component will not be initially rendered to HTML) but make it interactive as soon as it’s shown to the user on the client.</p>
<ul>
<li>Priority: medium (we’re okay not showing the initial component HTML)</li>
<li>Interactivity: high (as soon as it’s shown to the user)</li>
</ul>
<p>We may go ahead and render our <code>UpvoteContent</code> component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:only</span>=<span class="hljs-string">"react"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<p>It’s essential to pass the framework name as shown above. Otherwise, Astro doesn’t know what framework's JavaScript to load. This is because this isn’t determined on the server.</p>
<pre><code class="lang-js">&lt;ReactComponent client:only=<span class="hljs-string">"react"</span> /&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">PreactComponent</span> <span class="hljs-attr">client:only</span>=<span class="hljs-string">"preact"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SvelteComponent</span> <span class="hljs-attr">client:only</span>=<span class="hljs-string">"svelte"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">VueComponent</span> <span class="hljs-attr">client:only</span>=<span class="hljs-string">"vue"</span> /&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SolidComponent</span> <span class="hljs-attr">client:only</span>=<span class="hljs-string">"solid-js"</span> /&gt;</span></span>
</code></pre>
<p>Here are the hydration steps:</p>
<ol>
<li>Do not render component HTML.</li>
<li>Wait for the page to load.</li>
<li>Load component JavaScript.</li>
<li>Hydrate component.</li>
</ol>
<p>The difference between <code>client:only</code> and <code>client:load</code> is whether to render a static component HTML before the element is interactive. <code>client:only</code> is particularly handy when rendering components requiring client (browser) APIs.</p>
<h3 id="heading-clientvisible"><code>client:visible</code></h3>
<p><code>client:visible</code> should be used for low-priority interface elements below the fold (far down the page) or resource-intensive. You don’t want to load them if the user never sees the component.</p>
<ul>
<li>Priority: low</li>
<li>Interactivity: low</li>
</ul>
<p>We may go ahead and render our <code>UpvoteContent</code> component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> LargeMainContentLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/LargeMainContentLayout.astro"</span>;
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">LargeMainContentLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:visible</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">LargeMainContentLayout</span>&gt;</span></span>
</code></pre>
<p>Note that I’m importing a different <code>LargeMainContentLayout</code> layout in the code block above. The layout is responsible for pushing the island off the initial viewport.</p>
<p>Here are the hydration steps:</p>
<ol>
<li>Render component HTML.</li>
<li>Wait for the element to be visible (uses <code>IntersectionObserver</code> ).</li>
<li>Load component JavaScript.</li>
<li>Hydrate component.</li>
</ol>
<h3 id="heading-clientmedia"><code>client:media</code></h3>
<p><code>client:media</code> should be used for low-priority interface elements only visible on specific screen sizes, for example sidebar toggles.</p>
<ul>
<li>Priority: low</li>
<li>Interactivity: low</li>
</ul>
<p>We may go ahead and render our <code>UpvoteContent</code> component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:media</span>=<span class="hljs-string">"(max-width: 30em)"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<p>Here are the hydration steps:</p>
<ol>
<li>Render component HTML</li>
<li>Check if the media query matches</li>
<li>Load component JavaScript</li>
<li>Hydrate component</li>
</ol>
<h3 id="heading-clientidle"><code>client:idle</code></h3>
<p><code>client:idle</code> should be used for low-priority interface elements that don’t need to be immediately interactive.</p>
<ul>
<li>Priority: medium</li>
<li>Interactivity: medium</li>
</ul>
<p>We may go ahead and render our <code>UpvoteContent</code> component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:idle</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<p>Here’s the hydration step visualised:</p>
<ol>
<li>Render component HTML.</li>
<li>Wait for the page to load.</li>
<li>Wait for the <code>requestIdleCallback</code> event to be fired. If <code>requestIdleCallback</code> isn’t supported, use only the document <code>load</code> event.</li>
<li>Load component JavaScript.</li>
<li>Hydrate component.</li>
</ol>
<h2 id="heading-how-to-use-multiple-frameworks">How to Use Multiple Frameworks</h2>
<p>Theoretically, we can use multiple framework components in an Astro application. This is a powerful feature, but it shouldn’t be abused.</p>
<p>It does make for powerful demos of what’s possible with Astro. But there are only a few real-world cases where we might want to do this, like composing autonomous micro frontends on an Astro page.</p>
<p>Within an Astro component, the following is valid:</p>
<pre><code class="lang-js">---
 <span class="hljs-comment">// import different framework components </span>
 <span class="hljs-keyword">import</span> SpecialReactComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/
SpecialReactComponent.jsx'</span> 

 <span class="hljs-keyword">import</span> SpecialVueComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/
SpecialVueComponent.jsx'</span> 


<span class="hljs-keyword">import</span> SpecialSvelteComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/
SpecialSvelteComponent.jsx'</span> 
---

&lt;!-- render the components --&gt; 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SpecialReactComponent</span> <span class="hljs-attr">client:load</span>/&gt;</span></span> 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SpecialVueComponent</span> <span class="hljs-attr">client:idle</span>/&gt;</span></span> 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">SpecialSvelteComponent</span> <span class="hljs-attr">client:load</span>/&gt;</span></span>
</code></pre>
<p>Let’s see a real example in practice.</p>
<h3 id="heading-an-upvote-counter-in-vue">An upvote counter in Vue</h3>
<p>Recall that we built the initial <code>UpvoteContent</code> component using React. We’ll now create the <code>UpvoteContent</code> component using Vue and render both components in our Astro project.</p>
<p>Here’s the annotated implementation:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/components/UpvoteContent.vue --&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data() {
   <span class="hljs-comment">// data properties used in the UI template </span>
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">upvoteCount</span>: <span class="hljs-number">0</span>,
      <span class="hljs-attr">maxUpvoteCount</span>: <span class="hljs-number">50</span>,
    };
  },
  <span class="hljs-attr">methods</span>: {
    <span class="hljs-comment">// method called when you click the upvote button</span>
    upvote() {
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.upvoteCount &lt; <span class="hljs-built_in">this</span>.maxUpvoteCount) {
        <span class="hljs-built_in">this</span>.upvoteCount++;
      }
    },
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
      // <span class="hljs-attr">Attach</span> <span class="hljs-attr">a</span> <span class="hljs-attr">click</span> <span class="hljs-attr">event</span> <span class="hljs-attr">handler</span> <span class="hljs-attr">and</span> <span class="hljs-attr">invoke</span> "<span class="hljs-attr">upvote.</span>"
      @<span class="hljs-attr">click</span>=<span class="hljs-string">"upvote"</span>
    &gt;</span>    
     {/** Collapsed svg for brevity **/}      
      <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">..</span>/&gt;</span>
      Upvote
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        Vue
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{{ `${upvoteCount} upvotes` }}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

       {/** Increase the width of the div by "count percentage"**/}   
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">:style</span>=<span class="hljs-string">"{ width: `${upvoteCount}%` }"</span> /&gt;</span>

        {/** Render this section only if 
          the count is equal to the max count  **/}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
        <span class="hljs-attr">v-if</span>=<span class="hljs-string">"upvoteCount === maxUpvoteCount"</span>
      &gt;</span>
        Max upvote reached
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span>
</code></pre>
<p>And that’s it!</p>
<h3 id="heading-how-to-render-different-framework-components">How to render different framework components</h3>
<p>The rendering process for framework components is essentially the same. Let’s go ahead and render the React and Vue <code>UpvoteContent</code> components on a new page, as shown below:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/pages/multiple-frameworks.astro --&gt;
---
<span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>;
<span class="hljs-keyword">import</span> UpvoteContentVue <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.vue"</span>;
<span class="hljs-keyword">import</span> DefaultIslandLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"../layouts/DefaultIslandLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:load</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContentVue</span> <span class="hljs-attr">client:load</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span></span>
</code></pre>
<ul>
<li>We create a new page in <code>pages/multiple-frameworks.astro</code>.</li>
<li>We import both React and Vue components.</li>
<li>We render both components in an identical pattern and with the same client directive, <code>client:load</code>.</li>
</ul>
<p>It’s also essential to add Vue support to the project by running the following:</p>
<pre><code class="lang-js">npx astro add vue
</code></pre>
<p>This will install the relevant Vue dependencies and add the integration support in the Astro config file.</p>
<p>Once that’s done, we may view the running application on route <code>/multiple-frameworks</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-13-at-15.39.40@2x.png" alt="The React and Vue component rendered in a single Astro page Route." width="600" height="400" loading="lazy">
<em>The React and Vue component rendered in a single Astro page Route.</em></p>
<p>As expected, both components are rendered and work just as expected.</p>
<h2 id="heading-how-to-share-state-between-component-islands">How to Share State Between Component Islands</h2>
<p>As we work with component islands in Astro, you will inevitably need to share certain application states between component islands.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/islands-share-state.png" alt="Sharing state between two upvote islands." width="600" height="400" loading="lazy">
<em>Sharing state between two upvote islands.</em></p>
<p>For example, let’s assume we want our <code>UpvoteContent</code> components to share the same counter values.</p>
<p>Regardless of the component framework, every framework has its construct for sharing UI state between components, for example between React or Vue components.</p>
<p>But when working within Astro components, we need a solution that works framework agnostic, that is it's not tied to a single framework.</p>
<p>Here are some tremendous framework-agnostic solutions we can choose from:</p>
<ul>
<li><strong>Signals</strong>: These are great for expressing state based on reactive principles. We may use <a target="_blank" href="https://github.com/preactjs/signals">signals from Preact</a>, <a target="_blank" href="https://github.com/tldraw/signia">signia from tldraw</a> or <a target="_blank" href="https://www.solidjs.com/docs/latest">Solid signals</a> outside a component context.</li>
<li><strong><a target="_blank" href="https://vuejs.org/guide/scaling-up/state-management.html#simple-state-management-with-reactivity-api">Vue’s reactivity API</a></strong>: This can be an excellent ready-to-use solution if you already utilise Vue components in your Astro project.</li>
<li><strong><a target="_blank" href="https://svelte.dev/tutorial/writable-stores">Svelte’s stores</a></strong>: This can also be a great out-of-the-box solution if you already use Svelte components in your Astro project.</li>
<li><strong><a target="_blank" href="https://github.com/nanostores/nanostores">Nano stores</a></strong>: This is a tiny framework-agnostic library for state management.</li>
</ul>
<p>In this example, we’ll use Nano stores mainly because they are lightweight (less than 1kb) and don’t add a lot of JavaScript footprint to our application.</p>
<h3 id="heading-how-nano-store-works">How nano store works</h3>
<p>At a high level, what we’re trying to achieve is to remove the state values from within our framework components and manage them via <code>nanastores</code>.</p>
<p>We’ll create a new <code>upvoteCounter</code> state variable within nanostore. We will then propagate changes to this state variable to our framework components.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/nanostore-share-variable.png" alt="Propagating state variables from nanostore." width="600" height="400" loading="lazy">
<em>Propagating state variables from nanostore.</em></p>
<h3 id="heading-install-nano-store">Install nano store</h3>
<p>To use nano store, we must install the library into our project. Run the following installation command:</p>
<pre><code class="lang-bash">npm install nanostores @nanostores/vue @nanostores/react
</code></pre>
<ul>
<li><code>nanostores</code> represents the base library for creating and managing our state values</li>
<li>To guarantee that the framework component is re-rendered whenever a state value changes, we will use the React and Vue integrations for nano stores through <code>@nanostores/react</code> and <code>@nanostores/vue</code>, respectively.</li>
</ul>
<h3 id="heading-create-the-state-value">Create the state value</h3>
<p>Our example includes sharing the upvote count value across multiple framework components.</p>
<p>To create a state value, nano stores use atoms to store strings, numbers, and arrays.</p>
<p>Let’s create an atom to hold the counter state variable:</p>
<pre><code class="lang-js">&lt;!-- 📂 src/stores/upvote.ts --&gt;
<span class="hljs-keyword">import</span> { atom } <span class="hljs-keyword">from</span> <span class="hljs-string">"nanostores"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> upvoteCountStore = atom(<span class="hljs-number">0</span>);
</code></pre>
<ul>
<li>We create a new file in <code>src/stores/upvote.ts</code>.</li>
<li>We import <code>atom</code> from <code>nanostore</code>.</li>
<li>We create a new state number value called <code>upvoteCountStore</code>.</li>
</ul>
<p>We may think of atoms as small pieces of state to be shared across components in our application.</p>
<h3 id="heading-how-to-use-the-state-value-in-framework-components">How to use the state value in framework components</h3>
<p>In the React component, we will leverage the <code>useStore</code> hook provided in <code>@nanostores/react</code> to retrieve the state value from the <code>upvoteCountStore</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/UpvoteContent.tsx</span>

<span class="hljs-keyword">import</span> { useStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@nanostores/react"</span>;
<span class="hljs-keyword">import</span> { upvoteCountStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/upvote"</span>;

<span class="hljs-keyword">const</span> MAX_COUNT = <span class="hljs-number">50</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UpvoteContent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Get the state value from the created store </span>
  <span class="hljs-keyword">const</span> upvoteCount = useStore(upvoteCountStore);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
          if (upvoteCount <span class="hljs-tag">&lt; <span class="hljs-attr">MAX_COUNT</span>) {
            //<span class="hljs-attr">Update</span> <span class="hljs-attr">the</span> <span class="hljs-attr">store</span> <span class="hljs-attr">via</span> <span class="hljs-attr">the</span> <span class="hljs-attr">set</span> <span class="hljs-attr">method</span>
            <span class="hljs-attr">upvoteCountStore.set</span>(<span class="hljs-attr">upvoteCount</span> + <span class="hljs-attr">1</span>);
          }
        }}
      &gt;</span>
      { /** The rest of the code stays the same **/}
        Upvote
      <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      { /** The rest of the code stays the same **/}
     <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};
</code></pre>
<p>I've annotated the code to make it easier to understand. Take a look.</p>
<p>With the Vue component, we may leverage <code>props</code> for reactivity as shown below:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> { useStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@nanostores/vue"</span>;
<span class="hljs-keyword">import</span> { upvoteCountStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"../stores/upvote"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-comment">// setup props to be used in the UI template </span>
  setup(props) {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-comment">// Set the value of the upvoteCount from the store</span>
      <span class="hljs-attr">upvoteCount</span>: useStore(upvoteCountStore),
      <span class="hljs-attr">maxUpvoteCount</span>: <span class="hljs-number">50</span>,
    };
  },

  <span class="hljs-attr">methods</span>: {
    upvote() {
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.upvoteCount &lt; <span class="hljs-built_in">this</span>.maxUpvoteCount) {
        <span class="hljs-comment">// Update the store via the set method </span>
        upvoteCountStore.set(<span class="hljs-built_in">this</span>.upvoteCount + <span class="hljs-number">1</span>);
      }
    },
  },
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
  { /** The rest of the code stays the same **/}
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>Lovely!</p>
<p>Now, if we try the application, both framework components should have synced upvote values:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-15-at-07.20.20.png" alt="Synced upvote state values via nanostores." width="600" height="400" loading="lazy">
<em>Synced upvote state values via nanostores.</em></p>
<h2 id="heading-how-to-pass-props-and-children-to-framework-components">How to Pass Props and Children to Framework Components</h2>
<p>Most framework components support receiving data via props and children. These are equally supported when rendering framework components in Astro.</p>
<p>For example, we currently have the upvote button label hardcoded.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-19-at-18.06.54@2x.png" alt="The upvote label." width="600" height="400" loading="lazy">
<em>The upvote label.</em></p>
<p>We could make this dynamic via props as shown below:</p>
<pre><code class="lang-html">// 📂 src/pages/load.astro
---
import { UpvoteContent } from "../components/UpvoteContent.jsx";
import DefaultIslandLayout from "../layouts/DefaultIslandLayout.astro";
---

<span class="hljs-tag">&lt;<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:load</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Click"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DefaultIslandLayout</span>&gt;</span>
</code></pre>
<p>We’d then handle the prop in the <code>UpvoteContent</code> React component as usual:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// 📂 src/components/UpvoteContent.tsx</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UpvoteContent = <span class="hljs-function">(<span class="hljs-params">props: { label: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
   <span class="hljs-comment">// ... render props.label </span>
}
</code></pre>
<p>It’s important to note that we can pass any primitive as props, and they will work as expected.</p>
<p>But be careful with function props. Function props will only work during server rendering and fail when used in a hydrated client component, for example as an event handler. This is because functions cannot be serialised (transferred from the server to the client).</p>
<p>Children are often treated as a prop type – depending on the framework component used. For example, React, Preact, and Solid use the special <code>children</code> prop, while Svelte and Vue use the <code>&lt;slot /&gt;</code> element. These are both supported when working with framework components in Astro.</p>
<p>For example, with our React <code>&lt;UpvoteContent /&gt;</code> component, we could go ahead and receive a component description as <code>children</code>:</p>
<pre><code class="lang-js">&lt;UpvoteContent client:load&gt; 
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>An upvote counter created using React<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span></span>
&lt;/UpvoteContent&gt;
</code></pre>
<p>This will change nothing until we explicitly handle the <code>children</code> prop within the <code>&lt;UpvoteContent&gt;</code> component, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// The component accepts props as an argument</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UpvoteContent = <span class="hljs-function">(<span class="hljs-params">props: PropsWithChildren&lt;{}&gt;</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> upvoteCount = useStore(upvoteCountStore);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
     {/** Render the content of the children prop**/}
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{props.children}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {/** The rest of the component goes here**/}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
};
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-15-at-12.50.27.png" alt="Rendering the React component child element." width="600" height="400" loading="lazy">
<em>Rendering the React component child element.</em></p>
<p>With our Vue <code>&lt;UpvoteContent /&gt;</code> component, we could equally receive a component description as children:</p>
<pre><code class="lang-js"> &lt;UpvoteContentVue client:load&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>An upvote counter created using Vue<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span></span>
  &lt;/UpvoteContentVue&gt;
</code></pre>
<p>But we must reference this via a <code>&lt;slot&gt;</code> element. This is a fundamental difference in how libraries like React / Preact and Vue / Svelte deal with references to the children prop.</p>
<p>Here’s how to reference the children element in <code>UpvoteContentVue</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/UpvoteContent.vue</span>
&lt;template&gt;
 <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- the slot element renders the children element --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span> 
   <span class="hljs-comment">&lt;!-- The rest of the template goes here --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/template&gt;
</code></pre>
<p>Also, we may use multiple slots to group and reference children within our framework components.</p>
<p>Consider the following example with multiple children elements:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">import</span> { UpvoteContent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/UpvoteContent.jsx"</span>
---


<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"social-links"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://twitter.com/understanding-astro"</span>&gt;</span>Twitter<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://github.com/understanding-astro"</span>&gt;</span>GitHub<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">em</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"description"</span>&gt;</span>An upvote counter created using React<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">UpvoteContent</span>&gt;</span></span>
</code></pre>
<p>Note that we have two children nodes referenced by the slot names <code>social-links</code> and <code>description</code>, respectively.</p>
<p>Within <code>&lt;UpvoteContent /&gt;</code>, we may reference these separately as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UpvoteContent = <span class="hljs-function">(<span class="hljs-params">{props}</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{props.description}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{props.socialLinks}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {/** ... **/}
    <span class="hljs-tag">&lt;/&gt;</span></span>
  );
};
</code></pre>
<p>It is important to note that the <code>kebab-case</code> slot names in the Astro component are referenced as <code>camelCase</code> values on the <code>props</code> object.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/kebab_to_camel_case.png" alt="Reference the kebab-case slot names as camelCase in React or Preact." width="600" height="400" loading="lazy">
<em>Reference the kebab-case slot names as camelCase in React or Preact.</em></p>
<p>In Svelte and Vue, the slots will be referenced using a <code>&lt;slot&gt;</code> element with a <code>name</code> attribute. Here’s the implementation in <code>&lt;UpvoteContentVue /&gt;</code> :</p>
<pre><code class="lang-js">&lt;template&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> /&gt;</span></span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">slot</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"social-links"</span> /&gt;</span></span>
&lt;/template&gt;
</code></pre>
<p>Note how the slot <code>kebab-case</code> names are preserved.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-16-at-08.07.52.png" alt="Rendering the React and Vue component children elements." width="600" height="400" loading="lazy">
<em>Rendering the React and Vue component children elements.</em></p>
<h2 id="heading-nested-framework-components">Nested Framework Components</h2>
<p>In an Astro file, we may also nest framework components, that. ispass framework components as children. For example, the following is valid:</p>
<pre><code class="lang-js">&lt;DefaultIslandLayout&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:load</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"description"</span>&gt;</span>
     <span class="hljs-comment">&lt;!-- This is a nested &lt;UpvoteContent /&gt; component --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">UpvoteContent</span> <span class="hljs-attr">client:load</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">em</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"description"</span>&gt;</span>This is the nested component<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">UpvoteContent</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">UpvoteContent</span>&gt;</span></span>
&lt;/DefaultIslandLayout&gt;
</code></pre>
<p>As expected, this renders the nested <code>UpvoteContent</code> component:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-03-16-at-09.00.38.png" alt="Rendering nested framework components." width="600" height="400" loading="lazy">
<em>Rendering nested framework components.</em></p>
<p>Recursively rendering the same component is rarely the goal we want to achieve. But rendering nested framework components is powerful because we can compose an entire framework component application as we see fit.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/nesting-framework-component.png" alt="Nesting multiple child components to make a more significant application." width="600" height="400" loading="lazy">
<em>Nesting multiple child components to make a more significant application.</em></p>
<h2 id="heading-astro-island-gotchas">Astro Island Gotchas</h2>
<p>As developers, we are often responsible for inadvertently breaking things. Although debugging can be an enjoyable challenge, consider the following boundaries with Astro Islands.</p>
<h3 id="heading-1-do-not-use-an-astro-component-in-a-framework-component">1. Do not use an Astro component in a framework component</h3>
<p>Consider the following example of importing a <code>.astro</code> component and rendering it within a React component:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { OurAstroComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/OurAstroComponent"</span>

<span class="hljs-keyword">const</span> OurReactComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">OurAstroComponent</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
}
</code></pre>
<pre><code class="lang-js">&lt;OurReactComponent client:load /&gt;
</code></pre>
<p>This is an invalid use. The reason is that the React component is rendered a React “island”. Consequently, the island should contain only valid React code. This is the same for other framework component islands.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/wrong-astro-island-composition.png" alt="Do not render an Astro component as a framework component child without a <slot>." width="600" height="400" loading="lazy">
<em>Do not render an Astro component as a framework component child without a &lt;slot&gt;.</em></p>
<p>To overcome this, consider using the slot pattern earlier discussed to pass static content from an Astro component:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">import</span> { OurReactComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/OurReactComponent"</span>
<span class="hljs-keyword">import</span> { OurAstroComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/OurAstroComponent"</span>
--- 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">OurReactComponent</span> <span class="hljs-attr">client:load</span>&gt;</span>
 <span class="hljs-comment">&lt;!-- pass Astro component as a child via a named slot --&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">OurAstroComponent</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"description"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">OurReactComponent</span>&gt;</span></span>
</code></pre>
<h3 id="heading-2-do-not-hydrate-an-astro-component">2. Do not hydrate an Astro component</h3>
<p>Consider the following naive example to hydrate an Astro component using a client directive:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">import</span> { OurAstroComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/OurAstroComponent"</span>
--- 

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">OurAstroComponent</span> <span class="hljs-attr">client:load</span> /&gt;</span></span>
</code></pre>
<p>This is invalid. Astro components have no client-side runtime. So use a <code>&lt;script&gt;</code> tag if you need to interactivity.</p>
<h2 id="heading-why-use-islands">Why Use Islands?</h2>
<p>Typically, most resources would place this section at the start of the chapter. But there are certain instances where it's more beneficial to showcase practical use cases before diving into the reasons behind them. Also, this approach could foster an intuitive understanding, which is what I've adopted here.</p>
<p>So, why focus on islands? What advantages do they offer?</p>
<h3 id="heading-1-performance">1. Performance</h3>
<p>One of the main advantages is improved performance. We can significantly enhance our site’s speed by converting most of our website to static HTML and selectively loading JavaScript through islands only when necessary. This is because JavaScript is one of the slowest assets to load per byte.</p>
<h3 id="heading-2-responsible-hydration">2. Responsible hydration</h3>
<p>If JavaScript is expensive to parse and execute, the decision to load it should be carefully taken (from a performance perspective). Also, no one solution fits all application types and use cases. As such, controlling when a component island is hydrated puts you in charge of your website's performance.</p>
<h3 id="heading-3-parallel-loading">3. Parallel loading</h3>
<p>Lastly, it’s essential to utilise parallel loading. This means that when we load several islands, they won’t have to wait for each other to become hydrated. Instead, each island is considered a distinct unit that loads and becomes hydrated independently, in isolation.</p>
<h2 id="heading-wrapping-up-this-chapter-3">Wrapping Up This Chapter</h2>
<p>In this chapter, we learned about component islands in Astro and how they work. We also explored why framework components are sometimes preferred over vanilla JavaScript or TypeScript via a <code>&lt;script&gt;</code> element.</p>
<p>We also went through the steps to use a framework component in an Astro application, including building a static site, installing the framework, and writing the component. </p>
<p>Finally, we experimented using a React and Vue component to demonstrate the use of framework components. See you in the next chapter!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-137.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter five</em></p>
<h1 id="heading-chapter-5-oh-my-react-how-to-build-a-react-documentation-site-clone">Chapter 5: Oh my React! (How to Build a React Documentation Site Clone)</h1>
<p>In this chapter, we'll cover everything you need to know to develop rich content websites with real-world best practices. </p>
<p>This is a practical section best served with you coding along. To view the complete application, see the <a target="_blank" href="https://github.com/understanding-astro/react.dev-astro">GitHub repo</a>. </p>
<h2 id="heading-what-youll-learn-4">What You’ll Learn</h2>
<ul>
<li>How to style Astro projects with Tailwind.</li>
<li>Several syntax highlighting solutions for Astro.</li>
<li>How to leverage content collections for scalable and type-safe development.</li>
<li>Understand dynamic routing in Astro.</li>
</ul>
<h2 id="heading-set-up-the-starter-project">Set Up the Starter Project</h2>
<p>We’ve spent ample time learning the ins and outs of building static websites with Astro. So, in this chapter, we will not start from scratch.</p>
<p>Instead, we’ll begin with a basic static project we’ll build upon throughout the chapter.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/project-shell.png" alt="Building from a starter project " width="600" height="400" loading="lazy">
<em>Building from a starter project</em></p>
<p>In this chapter, we will adopt a solution-oriented approach similar to the kind that detectives use. We aim to solve various <code>TODOs</code> scattered throughout the starter project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/todos.png" alt="Solving small isolated problems" width="600" height="400" loading="lazy">
<em>Solving small isolated problems</em></p>
<p>The reason for this is to ignore the concepts you've already learned and focus on learning new concepts or consolidating older concepts via practice — solving isolated problems.</p>
<p>To get started, go ahead and clone the project:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/understanding-astro/react.dev-astro.git
</code></pre>
<p>Then change directories:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> react.dev-astro
</code></pre>
<p>Finally, checkout to the <code>clean-slate</code> branch I’ve prepared so we can systematically build upon the base application.</p>
<pre><code class="lang-bash">git checkout clean-slate
</code></pre>
<h2 id="heading-install-the-dependencies">Install the Dependencies</h2>
<p>Go ahead and install the project’s dependencies via the following:</p>
<pre><code class="lang-bash">npm install
</code></pre>
<p>Then install the Astro <code>react</code> integration:</p>
<pre><code class="lang-bash">npx astro add react
</code></pre>
<p>When prompted, type “y” to accept each prompt. “y” means “yes”!</p>
<p>The complete installation will add all relevant React dependencies and updates the <code>astro.config.mjs</code> project configuration file.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-23-at-08.11.48.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing the React integration and dependencies</em></p>
<p>Finally, go ahead and install the <code>mdx</code> integration. I’ll describe the what and why later in the chapter. For now, go ahead and install the integration by running the following:</p>
<pre><code class="lang-bash">npx astro add mdx
</code></pre>
<p>This will install the <code>@astrojs/mdx</code> integration and also update the <code>astro.config.mjs</code> project configuration file.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-23-at-08.13.42.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing the MDX integration</em></p>
<p>Now run the application:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>This will run the application in an available local port – the default <code>localhost:3000</code>.</p>
<p>Visit the local server and you’ll find the base unstyled application running in the browser as shown below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-23-at-08.16.14.png" alt="The unstyled homepage" width="600" height="400" loading="lazy">
<em>The unstyled homepage</em></p>
<p>I’ve got to say that’s one ugly-looking page.</p>
<p>We’ll fix that next.</p>
<h2 id="heading-how-to-style-astro-projects-with-tailwind">How to Style Astro Projects with Tailwind</h2>
<p>Love or hate it, CSS is how we make beautiful web applications.</p>
<p>In Chapter One, we wrote the styles for the personal website by hand, that is by writing out every CSS declaration. But in this chapter, we will use a CSS framework called Tailwind.</p>
<p>So, what’s Tailwind?</p>
<p>An overly simple definition would be, Tailwind is the modern <a target="_blank" href="https://getbootstrap.com/">bootstrap</a>. Never used Bootstrap? Then think of Tailwind as a utility-first CSS framework that provides class names like <code>flex</code>, <code>text-lg</code>, <code>items-center</code> and many more that you can apply to your markup for styles.</p>
<p>Tailwind will enable us to build modern-looking websites — fast.</p>
<h3 id="heading-how-to-install-tailwind">How to install Tailwind</h3>
<p>Keep the project running in your terminal and open another terminal tab. Run the following install command:</p>
<pre><code class="lang-bash">npx astro add tailwind
</code></pre>
<p>This will install the Astro tailwind integration in the project and update the project configuration.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-24-at-08.16.12.png" alt="Image" width="600" height="400" loading="lazy">
<em>Installing the Astro Tailwind integration</em></p>
<p>Once the installation is complete, the existing application styles will now take effect. Visit the application on your local port to see the styled application.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-24-at-08.17.17.png" alt="Image" width="600" height="400" loading="lazy">
<em>The styled application</em></p>
<p>What a difference styling makes!</p>
<p>Take your time and browse the different pages of the styled application.</p>
<h3 id="heading-how-does-tailwind-work">How does Tailwind work?</h3>
<p>Using Tailwind in Astro is straightforward. Install the Tailwind integration and provide a <code>class</code> attribute with Tailwind utility classes in your component markup.</p>
<p>For example, consider the styled text “The library for web and native user interfaces” on the project homepage:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-03-at-06.50.11@2x.png" alt="Image" width="600" height="400" loading="lazy">
<em>The homepage subtitle</em></p>
<p>Now, consider the code responsible for the styles:</p>
<pre><code class="lang-js"><span class="hljs-comment">// pages/index.astro</span>
<span class="hljs-comment">// ...</span>
&lt;p
   <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"max-w-lg py-1 text-center font-display text-4xl leading-snug text-secondary dark:text-primary-dark md:max-w-full"</span>
 &gt;
   The library <span class="hljs-keyword">for</span> web and native user interfaces
&lt;/p&gt;
</code></pre>
<p>In the example above, the classes applied are as shown below:</p>
<pre><code class="lang-html">"max-w-lg py-1 text-center font-display text-4xl leading-snug text-secondary dark:text-primary-dark md:max-w-full"
</code></pre>
<p>While this is not a Tailwind book, it’s only fair to give a general explanation of what’s going on here.</p>
<p>Firstly, most Tailwind utility classes are well-named and you can infer what they do. Others are not so well-named.</p>
<p>If you’re coding along in VSCode, I recommend installing the official Tailwind integration:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-03-at-06.55.50@2x.png" alt="Installing the official VSCode Tailwind plugin" width="600" height="400" loading="lazy">
<em>Installing the official VSCode Tailwind plugin</em></p>
<p>If you’re not using VSCode, consider finding your <a target="_blank" href="https://tailwindcss.com/docs/editor-setup">editor setup</a> in the official Tailwind docs.</p>
<p>Installing the integration brings a lot of benefits. The important benefit I want to highlight here is you can hover over any of the Tailwind utility classes to see the exact CSS property value the class corresponds to.</p>
<p>For example, hovering over the <code>max-w-lg</code> displays the CSS property value for the utility class as shown below:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.max-w-lg</span> {
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">32rem</span><span class="hljs-comment">/* 512px */</span>;
}
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-03-at-06.58.37@2x.png" alt="Image" width="600" height="400" loading="lazy">
<em>Hovering over Tailwind classes</em></p>
<p>This is very helpful because you can now inspect whatever classes are added to any markup in the project.</p>
<h3 id="heading-tailwind-configuration">Tailwind configuration</h3>
<p>Upon installing Tailwind, it ships with its default theme.</p>
<p>It’s not a bad theme, but when you build projects, you likely want control over the project theme.</p>
<p>In our example, we want a theme that's modeled after the official React documentation theme.</p>
<p>To customise Tailwind, we can provide a <code>tailwind.config.js</code> file where we can define our project’s fonts, colour palette, type scale, border radius values, breakpoints and much more.</p>
<p>Look at the <code>tailwind.config.cjs</code> file in the project’s root. This is where the project’s Tailwind configuration magic happens.</p>
<p>For more details on customising Tailwind, you can consult the <a target="_blank" href="https://tailwindcss.com/docs/theme">official documentation</a>.</p>
<h2 id="heading-typescript-import-alias">Typescript Import Alias</h2>
<p>Let’s be honest, no one likes those ugly relative imports, eh?</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> MyComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../../components/MyComponent.astro</span>
</code></pre>
<p>Ugh!!</p>
<p>C’mon, we can do better.</p>
<p>This is where import aliases come in. The easiest way to get this set up in an Astro project is to define the aliases in the <code>tsconfig.json</code> file.</p>
<p>For example, we may do the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 tsconfig.json </span>

{
  <span class="hljs-string">"compilerOptions"</span>: {
    <span class="hljs-string">"baseUrl"</span>: <span class="hljs-string">"."</span>,
    <span class="hljs-string">"paths"</span>: {
      <span class="hljs-string">"@components/*"</span>: [<span class="hljs-string">"src/components/*"</span>],
    }
  }
}
</code></pre>
<p>We’re essentially mapping any directories in the <code>src/components</code> import path to <code>@components</code>.</p>
<p>Now, wait for it.</p>
<p>The result of this is we can take our previous ugly import path and turn it into a work of art as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Before</span>
<span class="hljs-keyword">import</span> MyComponent <span class="hljs-keyword">from</span> <span class="hljs-string">'../../components/MyComponent.astro

// After 
import MyComponent from '</span>@components/MyComponent.astro<span class="hljs-string">'</span>
</code></pre>
<p>Beautiful and clean, isn’t it?</p>
<p>The reason I mention this is the starter project has been set up to use import aliases. So, don’t get confused.</p>
<p>Go ahead and look in the <code>tsconfig.json</code> file where you’ll find the following import aliases:</p>
<pre><code class="lang-js"><span class="hljs-string">"paths"</span>: {
   <span class="hljs-string">"@components/*"</span>: [<span class="hljs-string">"src/components/*"</span>],
   <span class="hljs-string">"@layouts/*"</span>: [<span class="hljs-string">"src/layouts/*"</span>],
   <span class="hljs-string">"@utils/*"</span>: [<span class="hljs-string">"src/utils/*"</span>]
}
</code></pre>
<p>You’re welcome 😉</p>
<h2 id="heading-islands-and-colocating-page-components">Islands and Colocating Page Components</h2>
<p>We’ve learned that appropriate file types in the <code>src/pages</code> directory get transformed into HTML pages.</p>
<p>But what if we need to have some files collocated in the <code>src/pages</code> directory without being transformed into accompanying <code>HTML</code> pages?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/exclude_page_intro.png" alt="Colocating files in the pages directory" width="600" height="400" loading="lazy">
<em>Colocating files in the pages directory</em></p>
<p>This can be helpful for collocating tests, utilities, and components along the associating pages.</p>
<p>Well, there’s a solution for that.</p>
<p>To exclude a valid page file type in the <code>src/pages</code> directory from being compiled into an associating HTML page, prefix the file name with an underscore <code>_</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/prefix_exclude_page.png" alt="Prefix file name with a underscore to not transform into HTML pages" width="600" height="400" loading="lazy">
<em>Prefix file name with a underscore to not transform into HTML pages</em></p>
<p>For example, take a look at the <code>pages/_components/Home</code> directory in the project.</p>
<p>This directory contains a handful of components that aren’t meant to be reusable across the project. They only exist to be used on the project’s homepage.</p>
<p>To exclude these from being separate browser pages, note how the <code>_components</code> directory is named.</p>
<p>As an example, if you visited <code>/_components/Home/Code</code> in the browser, this will return a <code>404</code>. Even though the <code>Code</code> components exist, it is not a page.</p>
<p>Now, let’s bring our knowledge of collocated components and Astro islands together to solve our first TODO in the project.</p>
<p>Take a look at the <code>index.astro</code> and consider the <code>TODO</code> to render the <code>Video</code> React component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
❗️ &lt;Code <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"text-white"</span>&gt;TODO:&lt;/Code&gt; (Astro Island): Render the ...
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-05-at-08.40.18@2x.png" alt="TODO: Render the Video React component island" width="600" height="400" loading="lazy">
<em>TODO: Render the Video React component island</em></p>
<p>Now consider the annotated solution below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
=== 
<span class="hljs-comment">// Import the Video component from "_components ..." </span>
<span class="hljs-keyword">import</span> { Video } <span class="hljs-keyword">from</span> <span class="hljs-string">"./_components/home/Video"</span>;
<span class="hljs-comment">// ...</span>
--- 
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ExampleResultPanel</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"right-content"</span>&gt;</span>
  {/** Render the Video component. NB: this is a React component **/}
   <span class="hljs-tag">&lt;<span class="hljs-name">Video</span>
     <span class="hljs-attr">client:visible</span> {/** 👈 <span class="hljs-attr">Add</span> <span class="hljs-attr">the</span> <span class="hljs-attr">client</span> <span class="hljs-attr">directive</span> **/}
     <span class="hljs-attr">video</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">title:</span> "<span class="hljs-attr">My</span> <span class="hljs-attr">video</span>", <span class="hljs-attr">description:</span> "<span class="hljs-attr">Video</span> <span class="hljs-attr">description</span>" }}
    /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ExampleResultPanel</span>&gt;</span></span>
</code></pre>
<ul>
<li>Render the <code>Video</code> React component</li>
<li>Pass a <code>client:visible</code> attribute to hydrate the island as soon as the component is visible</li>
<li>Finally pass the required <code>video</code> object props to the <code>Video</code> component: <code>{title: "my video", description: "Video description"}</code>.</li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-05-at-08.39.19@2x.png" alt="The rendered video island" width="600" height="400" loading="lazy">
<em>The rendered video island</em></p>
<p>Similarly, let’s resolve the second TODO. This time around we’ll render multiple <code>Video</code> components.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro </span>
❗️ &lt;Code <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"text-white"</span>&gt;TODO:&lt;/Code&gt; (Astro Island): Render two ...
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-05-at-08.43.18@2x.png" alt="TODO: Render two React component islands " width="600" height="400" loading="lazy">
<em>TODO: Render two React component islands</em></p>
<p>Consider the solution below:</p>
<pre><code class="lang-js">&lt;ExampleResultPanel slot=<span class="hljs-string">"right-content"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex w-full flex-col gap-4"</span>&gt;</span>
    {/** ... **/}
    {/** Render both islands **/}
    <span class="hljs-tag">&lt;<span class="hljs-name">Video</span>
      <span class="hljs-attr">client:visible</span>
      <span class="hljs-attr">video</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">title:</span> "<span class="hljs-attr">My</span> <span class="hljs-attr">video</span>", <span class="hljs-attr">description:</span> "<span class="hljs-attr">Video</span> <span class="hljs-attr">description</span>" }}
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Video</span>
      <span class="hljs-attr">client:visible</span>
      <span class="hljs-attr">video</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">title:</span> "<span class="hljs-attr">My</span> <span class="hljs-attr">video</span>", <span class="hljs-attr">description:</span> "<span class="hljs-attr">Video</span> <span class="hljs-attr">description</span>" }}
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/ExampleResultPanel&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-05-at-08.45.15@2x.png" alt="The rendered Astro islands" width="600" height="400" loading="lazy">
<em>The rendered Astro islands</em></p>
<h2 id="heading-syntax-highlighting">Syntax Highlighting</h2>
<p>I never understood the intricacies of syntax highlighting until I started researching this section of the book. It’s great how much is abstracted in libraries.</p>
<p>Anyway, I’ll skip the nuances and provide what I believe to be the most important bits.</p>
<p>So, how do we tackle syntax highlighting in an Astro application?</p>
<p>By default, Astro uses <a target="_blank" href="https://github.com/shikijs/shiki">Shiki</a> – a syntax highlighting library under the hood. Broadly speaking, there are two ways to go about syntax highlighting your code blocks in an Astro component.</p>
<p>Let’s have a look at these.</p>
<h3 id="heading-the-default-code-component">The default Code component</h3>
<p>Astro ships with a <code>&lt;Code /&gt;</code> component that provides syntax highlights at build time.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/code_component.png" alt="The Code component renders to HTML and inline styles without any Javascript" width="600" height="400" loading="lazy">
<em>The Code component renders to HTML and inline styles without any Javascript</em></p>
<p>By implication, there’s no runtime overhead to this method of syntax highlighting as no computations are done at runtime and the eventual result is a bunch of elements with inline styles.</p>
<p>This is powered by Shiki.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-08.35.52.png" alt="Sample syntax highlighted DOM output" width="600" height="400" loading="lazy">
<em>Sample syntax highlighted DOM output</em></p>
<p>Let’s go back to our starter project and resolve another TODO.</p>
<pre><code class="lang-js">📂 src/pages/index.astro

<span class="hljs-comment">// ...</span>
❗️ &lt;Code <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"text-white"</span>&gt;TODO:&lt;/Code&gt; Replace <span class="hljs-keyword">with</span> Syntax highlighted code
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-05-at-16.06.25@2x.png" alt="TODO: Add syntax highlighted code block" width="600" height="400" loading="lazy">
<em>TODO: Add syntax highlighted code block</em></p>
<p>The goal here is to provide syntax-highlighted code within the component markup.</p>
<p>To solve this, we’ll leverage the <code>Code</code> component from Astro as shown in the annotated code block below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-comment">// import Code from "astro/components" </span>
<span class="hljs-keyword">import</span> { Code <span class="hljs-keyword">as</span> AstroCode } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/components"</span>;
<span class="hljs-comment">//... other imports </span>
---

<span class="hljs-comment">// ...Render the component and pass the code and lang string props</span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">slot</span>=<span class="hljs-string">"left-content"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">AstroCode</span>
            <span class="hljs-attr">code</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">function</span> <span class="hljs-attr">Video</span>({ <span class="hljs-attr">video</span> }) {
  <span class="hljs-attr">return</span> (
    &lt;<span class="hljs-attr">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Thumbnail</span> <span class="hljs-attr">video</span>=<span class="hljs-string">{video}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{video.url}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{video.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{video.description}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">LikeButton</span> <span class="hljs-attr">video</span>=<span class="hljs-string">{video}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  );
}`}
    lang="jsx" {/** 👈 code language for syntax highlighting **/}
   /&gt;
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-13.04.02@2x.png" alt="The syntax highlighted code block" width="600" height="400" loading="lazy">
<em>The syntax highlighted code block</em></p>
<p>Since the code snippets are just good old HTML DOM nodes, we can apply some styles on the parent <code>div</code> to style them further as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
&lt;div
   slot=<span class="hljs-string">"left-content"</span>
   <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"[&amp;_pre]:!bg-transparent [&amp;_pre]:!text-sm [&amp;_pre]:!leading-6"</span>&gt;
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AstroCode</span> <span class="hljs-attr">...</span> /&gt;</span></span>
&lt;/div&gt;
</code></pre>
<p>This will reduce the size of the font, reduce the type leading and make the code background transparent. Note that the square braces are how we write arbitrary <a target="_blank" href="https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values">custom styles</a> in Tailwind.</p>
<p>See the results below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-13.03.25@2x.png" alt="Better styled syntax highlighted code block" width="600" height="400" loading="lazy">
<em>Better styled syntax highlighted code block</em></p>
<p>Much better eh?</p>
<p>We can go ahead and do the same for the other <code>TODO</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
❗️ &lt;Code <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"text-white"</span>&gt;TODO:&lt;/Code&gt; Replace <span class="hljs-keyword">with</span> Syntax highlighted code
</code></pre>
<p>Consider the identical solution below:</p>
<pre><code class="lang-js">&lt;div
   slot=<span class="hljs-string">"left-content"</span>
   {<span class="hljs-comment">/** Similar style as before. Leverages Tailwind **/</span>}
   <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"[&amp;_pre]:!bg-transparent [&amp;_pre]:!text-sm [&amp;_pre]:!leading-6"</span>
        &gt;
          <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AstroCode</span>
            <span class="hljs-attr">code</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">function</span> <span class="hljs-attr">VideoList</span>({ <span class="hljs-attr">videos</span>, <span class="hljs-attr">emptyHeading</span> }) {
  <span class="hljs-attr">const</span> <span class="hljs-attr">count</span> = <span class="hljs-string">videos.length;</span>
  <span class="hljs-attr">let</span> <span class="hljs-attr">heading</span> = <span class="hljs-string">emptyHeading;</span>
  <span class="hljs-attr">if</span> (<span class="hljs-attr">count</span> &gt;</span> 0) {
    const noun = count &gt; 1 ? 'Videos' : 'Video';
    heading = count + ' ' + noun;
  }
  return (
    <span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{heading}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      {videos.map(video =&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">Video</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{video.id}</span> <span class="hljs-attr">video</span>=<span class="hljs-string">{video}</span> /&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
  );
}`}
   lang="jsx"
 /&gt;</span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-13.05.02@2x.png" alt="The syntax highlighted code block" width="600" height="400" loading="lazy">
<em>The syntax highlighted code block</em></p>
<p>The default <code>Code</code> component also supports all the official Shiki <a target="_blank" href="https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes">themes</a>. For example, we can change the component theme to <code>poimandres</code> as shown below:</p>
<pre><code class="lang-js">&lt;AstroCode
    <span class="hljs-comment">// ...</span>
   lang=<span class="hljs-string">"jsx"</span>
   theme=<span class="hljs-string">"poimandres"</span>
 /&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-13.15.33@2x.png" alt="The poimandres theme" width="600" height="400" loading="lazy">
<em>The poimandres theme</em></p>
<p>Let’s consider the pros and cons of using the default <code>Code</code> component provided by Astro.</p>
<h4 id="heading-pros">Pros</h4>
<ul>
<li>Easy to use</li>
<li>Great results for low effort</li>
<li>Lots of available themes by default</li>
</ul>
<h4 id="heading-cons">Cons</h4>
<ul>
<li>More work is required to customise your themes, for example our www.react.dev clone requires its custom theme</li>
<li>No default support for dark and light theme</li>
</ul>
<h3 id="heading-bring-your-theme">Bring your theme</h3>
<p>Using your specific syntax themes is probably not the top of your list.</p>
<p>But Shiki supports the same syntax for VSCode themes. For example, we could load some custom open-source VSCode theme (or build on top of it) for our code blocks.</p>
<p>Let’s take a look at <a target="_blank" href="https://github.com/sdras/night-owl-vscode-theme">Nightowl</a>, a VS Code dark theme for contrast for nighttime coding.</p>
<p>Go ahead and copy the code <a target="_blank" href="https://raw.githubusercontent.com/sdras/night-owl-vscode-theme/main/themes/Night%20Owl-color-theme.json">snippet theme</a> to a <code>src/snippet-theme.json</code> file.</p>
<p>Next, we’ll write a simple component to load our custom theme as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/Shiki.astro</span>

---
<span class="hljs-keyword">import</span> type { Lang } <span class="hljs-keyword">from</span> <span class="hljs-string">"shiki"</span>;

<span class="hljs-comment">// Similar to Astro's Code component, this is built on shiki</span>
<span class="hljs-keyword">import</span> shiki, { getHighlighter } <span class="hljs-keyword">from</span> <span class="hljs-string">"shiki"</span>;

<span class="hljs-comment">// Similar to Astro's Code component, receive lang and code as props</span>
type Props = {
  <span class="hljs-attr">lang</span>: Lang;
  code: string;
};

<span class="hljs-keyword">const</span> { code = <span class="hljs-string">""</span>, lang = <span class="hljs-string">"jsx"</span> } = Astro.props;

<span class="hljs-comment">// 👀 Load the custom theme</span>
<span class="hljs-keyword">const</span> theme = <span class="hljs-keyword">await</span> shiki.loadTheme(<span class="hljs-string">"../../snippet-theme.json"</span>);

<span class="hljs-keyword">const</span> highlighter = <span class="hljs-keyword">await</span> getHighlighter({
  theme,
  <span class="hljs-attr">langs</span>: [lang],
});
---

{<span class="hljs-comment">/** 
  A fragment is an available Astro component. Use Fragment to prevent unnecessary markup.
The set:html directive is used to inject an HTML string into an element e.g., similar to el.innerHTML.
**/</span>}
&lt;Fragment
  <span class="hljs-attr">set</span>:html={highlighter.codeToHtml(code, {
    lang,
  })}
/&gt;
</code></pre>
<p>Import and use the new component:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-keyword">import</span> Shiki <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/Shiki.astro"</span>;
<span class="hljs-comment">// ... </span>
---

<span class="hljs-comment">// Change AstroCode to Shiki (new component) </span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Shiki</span>
 <span class="hljs-attr">code</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">function</span> <span class="hljs-attr">Video</span>({ <span class="hljs-attr">video</span> }) {
  <span class="hljs-attr">return</span> (
    &lt;<span class="hljs-attr">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Thumbnail</span> <span class="hljs-attr">video</span>=<span class="hljs-string">{video}</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{video.url}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{video.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{video.description}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">LikeButton</span> <span class="hljs-attr">video</span>=<span class="hljs-string">{video}</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  );
}`}
  lang="jsx"
/&gt;</span>
</code></pre>
<p>And there we go! We’ve successfully loaded a custom theme.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-06-25-at-13.55.54@2x.png" alt="Comparing the previous highlighted code with the new Night Owl theme" width="600" height="400" loading="lazy">
<em>Comparing the previous highlighted code with the new Night Owl theme</em></p>
<p>For more customisations, we could spend time tweaking the different theme tokens in the <code>snippet-theme.json</code> file.</p>
<h4 id="heading-pros-1">Pros</h4>
<ul>
<li>Flexibility: we can customise the theme tokens as granularly as needed</li>
</ul>
<h4 id="heading-cons-1">Cons</h4>
<ul>
<li>Requires more work</li>
<li>Support for dark and light theme</li>
</ul>
<h3 id="heading-handling-light-and-dark-themes">Handling light and dark themes</h3>
<p>Supporting light and dark themes in Shiki (the underlying Astro syntax highlighter) is tricky because Shiki generates themes at build time.</p>
<p>At the time a user toggles the site theme, no changes will be made to the syntax highlighting since it was generated at build time.</p>
<p>When working with Astro components, a simple solution is to leverage CSS variables.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { Code <span class="hljs-keyword">as</span> AstroCode } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/components"</span>;
---

<span class="hljs-comment">// Among, other properties, pass a "css-variables" theme prop to the Code component </span>
 <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AstroCode</span> <span class="hljs-attr">theme</span>=<span class="hljs-string">"css-variables"</span> /&gt;</span></span>
</code></pre>
<p>Then provide style tokens for both dark and light themes. Remember that this should be global. For example, we may do this in the <code>Baselayout.astro</code> layout component as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/BaseLayout.astro</span>
&lt;style is:<span class="hljs-built_in">global</span>&gt;
  @media (prefers-color-scheme: dark) {
    :root {
      --astro-code-color-text: #ffffff;
      --astro-code-color-background: black;
      --astro-code-token-constant: #<span class="hljs-number">86</span>d9ca;
      --astro-code-token-string: #<span class="hljs-number">977</span>cdc;
      --astro-code-token-comment: #<span class="hljs-number">757575</span>;
      --astro-code-token-keyword: #<span class="hljs-number">77</span>b7d7;
      --astro-code-token-parameter: #ffffff;
      --astro-code-token-<span class="hljs-function"><span class="hljs-keyword">function</span>: #86<span class="hljs-title">d9ca</span>;
      --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">string</span>-<span class="hljs-title">expression</span>: #<span class="hljs-title">c64640</span>;
      --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">punctuation</span>: #<span class="hljs-title">ffffff</span>;
      --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">link</span>: #977<span class="hljs-title">cdc</span>;
    }
  }

  :<span class="hljs-title">root</span> </span>{
    --astro-code-color-text: #<span class="hljs-number">24292</span>e;
    --astro-code-color-background: #ffffff;
    --astro-code-token-constant: #<span class="hljs-number">032</span>f62;
    --astro-code-token-string: #<span class="hljs-number">032</span>f62;
    --astro-code-token-comment: #<span class="hljs-number">6</span>a737d;
    --astro-code-token-keyword: #d73a49;
    --astro-code-token-parameter: #<span class="hljs-number">24292</span>e;
    --astro-code-token-<span class="hljs-function"><span class="hljs-keyword">function</span>: #6<span class="hljs-title">f42c1</span>;
    --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">string</span>-<span class="hljs-title">expression</span>: #<span class="hljs-title">c64640</span>;
    --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">punctuation</span>: #<span class="hljs-title">ffffff</span>;
    --<span class="hljs-title">astro</span>-<span class="hljs-title">code</span>-<span class="hljs-title">token</span>-<span class="hljs-title">link</span>: #977<span class="hljs-title">cdc</span>;
  }
&lt;/<span class="hljs-title">style</span>&gt;</span>
</code></pre>
<p>If dark and light theme syntax highlighting is critical for your application, take a look at the <a target="_blank" href="https://github.com/shikijs/shiki/blob/main/docs/themes.md#theming-with-css-variables">official documentation</a> for more information.</p>
<h2 id="heading-how-to-get-started-with-content-collections">How to Get Started with Content Collections</h2>
<p>Consider building a large application driven by a lot of content – whether that’s Markdown (<code>/md</code>), MDX (<code>.mdx</code>), JSON (<code>.json</code>) or YAML (<code>.yaml</code>) files.</p>
<p>One solution to best organise the project’s content could be to save the content data in a database where we can validate the document schema and make sure the required content fits the data model we desire.</p>
<p>We may visually model these as collections of data saved in a database with a predefined data schema.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/predefined_schema_db.png" alt="Modelling data with a predefined schema in a database" width="600" height="400" loading="lazy">
<em>Modelling data with a predefined schema in a database</em></p>
<p>With Astro projects, we don’t particularly need a database to store and enforce our content data models.</p>
<p>Enter content collections.</p>
<p>Regardless of the size of the Astro project, content collections are the best way to organise our content document, validate the structure of the document and also enjoy out-of-the-box TypeScript support when querying or manipulating the content collection.</p>
<p>So, what’s a content collection?</p>
<p>A content collection is any top-level directory in the <code>src/content</code> folder of an Astro project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/content_collections.png" alt="Content collections - top directories in src/content" width="600" height="400" loading="lazy">
<em>Content collections - top directories in src/content</em></p>
<p>Note that the <code>src/content</code> directory is strictly reserved for content collections. Don’t use this directory for anything else.</p>
<p>Now that we know what a content collection is, the individual documents or entries within a collection are referred to as collection entries.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/collection_entries.png" alt="Collection entries within a single collection" width="600" height="400" loading="lazy">
<em>Collection entries within a single collection</em></p>
<p>Collection entries are documents in formats such as Markdown or MDX. They can also be in data formats such as JSON or YAML. For consistency, you’ll find most collection entries with a consistent naming pattern, for example kebab-case.</p>
<h3 id="heading-what-problems-do-content-collections-solve">What problems do content collections solve?</h3>
<p>Littering a project with different content documents and no clear structure is a surefire way to create a mess.</p>
<p>The better solution: use content collections.</p>
<p>Now, content collections aim to address three main problems:</p>
<ol>
<li>Organising documents.</li>
<li>Validating the document structure (for example validating the frontmatter properties of a markdown file).</li>
<li>Providing strong type safety while querying and working with content collections.</li>
</ol>
<h3 id="heading-how-to-organize-content-collections">How to organize content collections</h3>
<p>When working with content collections, note that only top-level directories in <code>src/content</code> count as collections. </p>
<p>For example, with multiple collections such as <code>blogs</code>, <code>authors</code> and <code>comments</code>, we could accurately represent these distinct content types with three top-level directories within <code>src/content</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/content_collection_example.png" alt="Organising different content collections " width="600" height="400" loading="lazy">
<em>Organising different content collections</em></p>
<p>If there’s a need to further organise content via subdirectories within a collection, that’s entirely acceptable! For example, the <code>blogs</code> content collection may have subdirectories to organise content via languages for example <code>en</code>, <code>fr</code>, and so on.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/collection_subdirectories.png" alt="Subdirectories within content collections" width="600" height="400" loading="lazy">
<em>Subdirectories within content collections</em></p>
<h3 id="heading-how-to-authorize-content-with-mdx">How to authorize content with MDX</h3>
<p>Take a look at the existing content collection in the project.</p>
<p>What do you see?</p>
<p>You should find a <code>blog</code> collection in <code>src/content/blog</code> with a handful of <code>.mdx</code> files.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-11-at-06.44.39.png" alt="Entries in the blog collection" width="600" height="400" loading="lazy">
<em>Entries in the blog collection</em></p>
<p>Each <code>mdx</code> file refers to the collection entry for the blog collection. But what is an <code>mdx</code> file?</p>
<p>MDX touts itself as the markdown for the component era. Think, what if we could use components in markdown? Well, with <code>MDX</code>, we can.</p>
<p>In these files, we can import components and embed them within our standard markdown content.</p>
<p>In the installation section of this chapter, we installed the Astro MDX plugin by running <code>npx astro add mdx</code>.</p>
<p>It’s about time we got started utilising MDX.</p>
<h3 id="heading-how-to-configure-content-collections">How to configure content collections</h3>
<p>A big part of content collections is ensuring a consistent collection entry format for every content collection.</p>
<p>For example, assuming a number markdown or MDX collection entries, we can go ahead and ensure that every collection entry has the same frontmatter properties. As you can imagine, this protects the integrity of each collection entry and breeds confidence that no surprising bug will spring at us when working with the entries.</p>
<p>So, how do we ensure such consistency?</p>
<p>The way we do this is by creating collection schemas.</p>
<p>A schema enforces consistent collection entry data within a collection. This is also what powers the TypeScript support we’ll get when working with the collection entries.</p>
<p>To create our collection schema, go ahead and create a <code>src/content/config.ts</code> file with the following content:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Import utilities from astro:content</span>
<span class="hljs-keyword">import</span> { z, defineCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:content"</span>;

<span class="hljs-comment">// Define the type and schema for one or more collections</span>
<span class="hljs-keyword">const</span> blogCollection = defineCollection({
  <span class="hljs-attr">type</span>: <span class="hljs-string">'content'</span>,
  <span class="hljs-comment">// an object of strings - title, year, month, day, and intro</span>
  <span class="hljs-attr">schema</span>: z.object({
    <span class="hljs-attr">title</span>: z.string(),
    <span class="hljs-attr">year</span>: z.string(),
    <span class="hljs-attr">month</span>: z.string(),
    <span class="hljs-attr">day</span>: z.string(),
    <span class="hljs-attr">intro</span>: z.string(),
  }),
});

<span class="hljs-comment">// Export a single collections object to register the collections </span>
<span class="hljs-comment">// The key should match the collection directory name in "src/content"</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> collections = {
  <span class="hljs-attr">blog</span>: blogCollection, <span class="hljs-comment">// add the blog collection </span>
};
</code></pre>
<p>Take a look at the annotated code above.</p>
<p>You don’t need to memorise how to do this, as you can always refer to the official documentation. But remember that the schema for a project’s content collections is defined in a <code>src/content/config.ts</code> (or <code>.js</code> and <code>.mjs</code>) file.</p>
<p>If we break down what goes on in a collection configuration file, we have three main actions:</p>
<ol>
<li>Import utilities from <code>astro:content</code>.</li>
<li>Define the content collection(s) schema via the <code>z</code> utility.</li>
<li>Export a single object of collection name key and schema value.</li>
</ol>
<p>The schema is the brain behind guaranteeing our content contains the right data and also provides TypeScript support — autocompletion and type-checking when querying the collection.</p>
<p>I know the question you’re likely asking.</p>
<p>What’s the <code>z</code> utility exported from <code>astro:content</code>?</p>
<p>The <code>z</code> utility re-exports the widely popular <a target="_blank" href="https://github.com/colinhacks/zod">zod</a> library — a TypeScript-first schema validation library with static type inference. The <code>z</code> variable in the <code>config</code> is a convenient export from <code>zod</code>.</p>
<h4 id="heading-quick-intro-to-zod">Quick intro to Zod</h4>
<p>While this is not a Zod book, the truth remains that if we will be defining schemas with Zod, it pays to understand the basics.</p>
<p>So, here’s a quick intro.</p>
<p>First, consider the schema for our <code>blog</code> collection:</p>
<pre><code class="lang-js">z.object({
  <span class="hljs-attr">title</span>: z.string(),
  <span class="hljs-attr">year</span>: z.string(),
  <span class="hljs-attr">month</span>: z.string(),
  <span class="hljs-attr">day</span>: z.string(),
  <span class="hljs-attr">intro</span>: z.string(),
})
</code></pre>
<p>Let’s deconstruct this.</p>
<p>Creating a schema starts with importing Zod. With, Astro that’s done via the import from <code>astro:content</code></p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {z} <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>
</code></pre>
<p>To create a schema for a string property, use the <code>string</code> method as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> stringSchema = z.string()
</code></pre>
<p>To create an object schema, you guessed right. We use the <code>object</code> method as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> myObjectSchema = z.object({

})
</code></pre>
<p>Now, within this object, we may define properties as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> myObjectSchema = z.object({
    <span class="hljs-attr">someString</span>: z.string()
})
</code></pre>
<p>In our blog collection schema, we’re essentially saying that the markdown (and MDX) files within the <code>blog</code> collection must have string front matter properties of <code>title</code>, <code>year</code>, <code>month</code>, <code>day</code> and <code>intro</code>.</p>
<p>The frontmatter is represented by the object schema and its properties, the object keys.</p>
<p>Now, go ahead and view all the collection entries in the <code>blog</code> collection and note how they all have defined properties.</p>
<h4 id="heading-the-astro-folder">The <code>.astro</code> folder</h4>
<p>As you create and work with content collections, Astro creates a <code>.astro</code> directory in the root of our project to keep track of important metadata for our content collections — mostly generated type information.</p>
<p>It’s safe to ignore this directory.</p>
<p>The <code>.astro</code> directory is updated automatically as we run <code>astro dev</code> or <code>astro build</code> commands. But if we find the type information not in sync, we can manually run <code>astro sync</code> at any time to update the <code>.astro</code> directory manually.</p>
<h2 id="heading-how-to-query-and-render-content-collections">How to Query and Render Content Collections</h2>
<p>So, we know how to create content collections and define their schemas. What next?</p>
<p>Content collections exist to be consumed in some way — typically by querying and rendering the collections.</p>
<p>So, how do we get started with this?</p>
<p>A collection consists of one or more collection entries. So, to query an entire collection, Astro provides the <code>getCollection()</code> method.</p>
<p>Consider how we may fetch all blog posts in our project:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>

<span class="hljs-comment">// Get all entries from the blog collection </span>
<span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">'blog'</span>)
---
</code></pre>
<p>To filter the collection entries, we may pass a second function argument to <code>getCollection</code> as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>

<span class="hljs-comment">// Get all entries from the blog collection </span>
<span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">'blog'</span>, <span class="hljs-function">(<span class="hljs-params">{data}</span>) =&gt;</span> {
  <span class="hljs-comment">// return only blogs from a certain year</span>
  <span class="hljs-keyword">return</span> data.year === <span class="hljs-string">'2023'</span>
})
---
</code></pre>
<p>Note that in our case, the <code>data</code> above refers to the frontmatter properties of our <code>MDX</code> blog entries.</p>
<p>How about getting a single collection entry?</p>
<p>Your first inclination may be to filter as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>

<span class="hljs-comment">// Get all entries from the blog collection </span>
<span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">'blog'</span>, <span class="hljs-function">(<span class="hljs-params">{data}</span>) =&gt;</span> {
  <span class="hljs-comment">// return only a specific title</span>
  <span class="hljs-keyword">return</span> data.title === <span class="hljs-string">'my-single-blog-title"
})
---</span>
</code></pre>
<p>The above is technically valid. But Astro provides a <code>getEntry()</code> method specifically for this case.</p>
<p>Consider the usage below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {getEntry} <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>

<span class="hljs-comment">// Get a single blog entry with the entry slug </span>
<span class="hljs-keyword">const</span> blog = <span class="hljs-keyword">await</span> getEntry(<span class="hljs-string">'blog'</span>, <span class="hljs-string">'introduction-to-react'</span>)
</code></pre>
<p>The example above will fetch the entry in the <code>src/content/blog/introduction-to-react.mdx</code> route.</p>
<p>Note that both <code>getCollection</code> and <code>getEntry</code> return a <a target="_blank" href="https://docs.astro.build/en/reference/api-reference/#collection-entry-type">CollectionEntry</a> type.</p>
<p>Enough with the theory, let’s get back to building our project.</p>
<p>Find the next TODO on the <code>blog/index.astro</code> page:</p>
<pre><code class="lang-js">📂 src/pages/blog/index.astro

&lt;!-- ❗️TODO: List and render (all) blog post cards --&gt;
</code></pre>
<p>The goal is to fetch all the blogs in the blog content collection and render visual cards for each entry. Also, note that clicking each card should point to the actual blog.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-10-at-05.49.23.png" alt="Rendering blog post cards. " width="600" height="400" loading="lazy">
<em>Rendering blog post cards.</em></p>
<p>Consider the solution below:</p>
<pre><code class="lang-js">📂 src/pages/blog/index.astro

---
<span class="hljs-comment">// Import getCollection from astro:content </span>
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:content"</span>;
<span class="hljs-comment">// Import the BlogCard visual component </span>
<span class="hljs-keyword">import</span> BlogCard <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/BlogCard.astro"</span>;
<span class="hljs-comment">// Import the getMonthName utility </span>
<span class="hljs-keyword">import</span> { getMonthName } <span class="hljs-keyword">from</span> <span class="hljs-string">"@utils/getMonthName"</span>;

<span class="hljs-comment">// Fetch all the blog posts </span>
<span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">"blog"</span>);
---

{<span class="hljs-comment">/** render all blog posts **/</span>}
  &lt;div <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"mt-12 flex flex-col gap-5 px-5 sm:-mx-5 lg:px-4"</span>&gt;
    {
      allBlogPosts.map(<span class="hljs-function">(<span class="hljs-params">{ data, slug }</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> url = <span class="hljs-string">`/blog/<span class="hljs-subst">${data.year}</span>/<span class="hljs-subst">${data.month}</span>/<span class="hljs-subst">${data.day}</span>/<span class="hljs-subst">${slug}</span>`</span>;

        <span class="hljs-keyword">return</span> (
          <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogCard</span>
            <span class="hljs-attr">url</span>=<span class="hljs-string">{url}</span>
            <span class="hljs-attr">date</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">getMonthName</span>(+<span class="hljs-attr">data.month</span>)} ${<span class="hljs-attr">data.day</span>}, ${<span class="hljs-attr">data.year</span>}`}
            <span class="hljs-attr">title</span>=<span class="hljs-string">{data.title}</span>
          &gt;</span>
            {data.intro}
          <span class="hljs-tag">&lt;/<span class="hljs-name">BlogCard</span>&gt;</span></span>
        );
      })
    }
  &lt;/div&gt;
</code></pre>
<p>Note the URL of each blog constructed in the solution above:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> url = <span class="hljs-string">`/blog/<span class="hljs-subst">${data.year}</span>/<span class="hljs-subst">${data.month}</span>/<span class="hljs-subst">${data.day}</span>/<span class="hljs-subst">${slug}</span>`</span>;
</code></pre>
<p>For example, the blog collection entry <code>data-fetching-with-react-server-components.mdx</code> will have the path: <code>/blog/2020/12/21/data-fetching-with-react-server-components</code>.</p>
<p>Go ahead and click any of the blog cards. At the moment, they should lead to an empty page.</p>
<p>Let’s resolve that.</p>
<h2 id="heading-dynamic-routing">Dynamic Routing</h2>
<p>Static routes are arguably easy to reason about. For example, <code>.astro</code>, <code>.md</code> and <code>.mdx</code> files in <code>src/pages</code> will automatically become pages on our website.</p>
<p>But sometimes we require dynamic routes to prevent repetition. This typically happens when we have different routes with minimal UI changes between them.</p>
<p>For example, consider our current project. The blogs will have different routes, but each blog’s look and feel are identical.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// example routes for different blogs </span>
/blog/<span class="hljs-number">2020</span>/<span class="hljs-number">12</span>/<span class="hljs-number">21</span>/data-fetching-<span class="hljs-keyword">with</span>-react-server-components
/blog/<span class="hljs-number">2023</span>/<span class="hljs-number">04</span>/<span class="hljs-number">24</span>/some-other-blog-title
/blog/<span class="hljs-number">2023</span>/<span class="hljs-number">07</span>/<span class="hljs-number">12</span>/getting-started-<span class="hljs-keyword">with</span>-react
</code></pre>
<pre><code class="lang-ts"><span class="hljs-comment">// 👀 Manually creating multiple pages for each blog </span>
/pages/<span class="hljs-number">2020</span>/<span class="hljs-number">12</span>/<span class="hljs-number">21</span>/data-fetching-<span class="hljs-keyword">with</span>-react-server-components.astro
/pages/<span class="hljs-number">2023</span>/<span class="hljs-number">04</span>/<span class="hljs-number">24</span>/some-other-blog-title.astro
/pages/<span class="hljs-number">2023</span>/<span class="hljs-number">07</span>/<span class="hljs-number">12</span>/getting-started-<span class="hljs-keyword">with</span>-react.astro
</code></pre>
<p>Manually providing multiple pages for each blog is arguably tedious.</p>
<p>Instead of manually creating different pages to represent each blog, we may dynamically handle the routing in one of two ways.</p>
<h3 id="heading-1-named-parameters">1. Named parameters</h3>
<p>The URL structure of the blogs could be represented by <code>/${year}/${month}/${day}/${title}</code> where <code>title</code> represents the blog’s title and <code>year</code>, <code>month</code> and <code>day</code>, describe when the blog was published.</p>
<p>We could represent the variables in the route path with named parameters surrounded by square brackets.</p>
<p>For example, we can create a file in the <code>pages/blog</code> directory with the following file name:</p>
<pre><code class="lang-md">/[year]/[month]/[day]/[title].astro
</code></pre>
<p>Since our pages are statically built, for example when we run the build script, all the routes must be determined at build time.</p>
<p>To achieve this, we must export a <code>getStaticPaths</code> function that returns an array of objects that correspond to each route. Here’s how:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/blog/[year]/[month]/[day]/[title].astro</span>
---
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BlogLayout.astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">title</span>: <span class="hljs-string">"data-fetching-with-react-server-components"</span>,
                <span class="hljs-attr">year</span>: <span class="hljs-string">"2020"</span>,
                <span class="hljs-attr">month</span>: <span class="hljs-string">"12"</span>,
                <span class="hljs-attr">day</span>: <span class="hljs-string">"21"</span>,
            },
        },
    ];
}
---
</code></pre>
<p>Note that <code>getStaticPaths</code> specifically returns an object with a <code>params</code> field that defines all the variables in the route path that is <code>title</code>, <code>year</code>, <code>month</code> and <code>day</code></p>
<p>To add another blog route, simply add another object with its <code>params</code> property:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/blog/[year]/[month]/[day]/[title].astro</span>
---
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">title</span>: <span class="hljs-string">"data-fetching-with-react-server-components"</span>,
                <span class="hljs-attr">year</span>: <span class="hljs-string">"2020"</span>,
                <span class="hljs-attr">month</span>: <span class="hljs-string">"12"</span>,
                <span class="hljs-attr">day</span>: <span class="hljs-string">"21"</span>,
            },
        },
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">title</span>: <span class="hljs-string">"introducing-react-dev"</span>,
                <span class="hljs-attr">year</span>: <span class="hljs-string">"2023"</span>,
                <span class="hljs-attr">month</span>: <span class="hljs-string">"03"</span>,
                <span class="hljs-attr">day</span>: <span class="hljs-string">"16"</span>,
            },
        },
    ];
}
---
</code></pre>
<p>With the route <code>params</code> defined, we then grab the variables and render each blog as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/blog/[year]/[month]/[day]/[title].astro</span>
---
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BlogLayout.astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">title</span>: <span class="hljs-string">"data-fetching-with-react-server-components"</span>,
                <span class="hljs-attr">year</span>: <span class="hljs-string">"2020"</span>,
                <span class="hljs-attr">month</span>: <span class="hljs-string">"12"</span>,
                <span class="hljs-attr">day</span>: <span class="hljs-string">"21"</span>,
            },
        },
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">title</span>: <span class="hljs-string">"introducing-react-dev"</span>,
                <span class="hljs-attr">year</span>: <span class="hljs-string">"2023"</span>,
                <span class="hljs-attr">month</span>: <span class="hljs-string">"03"</span>,
                <span class="hljs-attr">day</span>: <span class="hljs-string">"16"</span>,
            },
        },
    ];
}

<span class="hljs-comment">// Get the path variables from Astro.params</span>
<span class="hljs-keyword">const</span> { title, year, month, day } = Astro.params;
---

<span class="hljs-comment">// Provide markup for each matched page </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React Blog - React"</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"React Blog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{year}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{month}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{day}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BlogLayout</span>&gt;</span></span>
</code></pre>
<p>Clicking on the <em>data fetching with react server components</em> and <em>introducing react dev blog</em> cards should now render their accompanying page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-07.41.17.png" alt="Rendered blog markup" width="600" height="400" loading="lazy">
<em>Rendered blog markup</em></p>
<h3 id="heading-2-rest-parameters">2. Rest parameters</h3>
<p>Rest parameters provide ultimate flexibility in our URL routing. For example, we may use <code>[...path]</code> to match file paths <strong>of any depth</strong>. Where <code>path</code> could be represented by any string, for example <code>[...file]</code> or <code>[...somestring]</code>.</p>
<p>Following our existing example, how may we reduce the path <code>pages/blog/[year]/[month]/[day]/[title].astro</code> to simply <code>pages/blog/[...path].astro</code>.</p>
<p>Delete the previous directories and file that made up <code>[year]/[month]/[day]/[title].astro</code> and create a single <code>blog/[...path].astro</code>.</p>
<p>This new file will match the blog route.</p>
<p>Similarly, we need to provide a <code>getStaticPaths</code> function, but the variable to be provided here is <code>path</code> as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BlogLayout.astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> [
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">path</span>: <span class="hljs-string">"2020/12/21/data-fetching-with-react-server-components"</span>,
            },
        },
        {
            <span class="hljs-attr">params</span>: {
                <span class="hljs-attr">path</span>: <span class="hljs-string">"2023/03/16/introducing-react-dev"</span>,
            },
        },
    ];
}

<span class="hljs-keyword">const</span> { path } = Astro.params;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React Blog - React"</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"React Blog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{path}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BlogLayout</span>&gt;</span></span>
</code></pre>
<p>Clicking on the <em>data fetching with react server components</em> and <em>introducing react dev blog</em> cards should now render their accompanying page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-07.40.03.png" alt="Rendered blog markup" width="600" height="400" loading="lazy">
<em>Rendered blog markup</em></p>
<h3 id="heading-priority-order">Priority order</h3>
<p>As we’ve discussed, URL paths can be matched in different ways. So what happens when different file paths match the same URL path in our project?</p>
<p>Well, Astro needs to make a decision, and that’s following the priority list below:</p>
<ol>
<li>Static routes, that is those without path parameters, have the highest priority, for example <code>/pages/products/this-is-a-product</code>.</li>
<li>Dynamic routes with named parameters have the next priority, for example <code>/pages/products/[id]</code>.</li>
<li>Dynamic routes with rest parameters have the lowest priority, for example <code>/pages/products/[...path]</code>.</li>
<li>Following the above, any ties will be resolved alphabetically.</li>
</ol>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/route_priority.png" alt="Route priority order from first to last." width="600" height="400" loading="lazy">
<em>Route priority order from first to last.</em></p>
<p>A decent example is to note that even though the dynamic path <code>[...path.astro]</code> matches the root path <code>/blog</code>, the static route <code>blog/index.astro</code> always takes priority while the dynamic route <code>[...path.astro]</code> kicks in for each blog page.</p>
<h2 id="heading-how-to-generate-routes-with-content-collections">How to Generate Routes with Content Collections</h2>
<p>Right now, we’re manually adding objects to the exported <code>getStaticPaths</code> function to define our blog paths.</p>
<p>But our desired solution is to generate these from the blog content collection.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/auto_entry_route.png" alt="Automatically generate routes for 
each collection entry" width="600" height="400" loading="lazy">
<em>Automatically generate routes for each collection entry</em></p>
<p>To achieve this, we need to rework the <code>getStaticPaths</code> implementation to fetch all blog posts from the content collection and generate the required paths.</p>
<p>Consider the solution below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:content"</span>;
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BlogLayout.astro"</span>;

<span class="hljs-comment">// Make the function async</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Fetch all blog posts </span>
    <span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">"blog"</span>);
    <span class="hljs-comment">// Dynamically construct the blog paths</span>
    <span class="hljs-keyword">const</span> paths = allBlogPosts.map(<span class="hljs-function">(<span class="hljs-params">blogEntry</span>) =&gt;</span> ({
        <span class="hljs-comment">// construct params</span>
        <span class="hljs-attr">params</span>: {
            <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${blogEntry.data.year}</span>/<span class="hljs-subst">${blogEntry.data.month}</span>/<span class="hljs-subst">${blogEntry.data.day}</span>/<span class="hljs-subst">${blogEntry.slug}</span>`</span>,
        },
    }));

    <span class="hljs-comment">// Eventually return the constructed paths</span>
    <span class="hljs-keyword">return</span> paths;
}

<span class="hljs-keyword">const</span> { path } = Astro.params;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React Blog - React"</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"React Blog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{path}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BlogLayout</span>&gt;</span></span>
</code></pre>
<p>Now, every single blog entry now has an associating path defined. Give this a try by clicking any blog link from the home page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-07.51.47.png" alt="All blog paths now automatically handled " width="600" height="400" loading="lazy">
<em>All blog paths now automatically handled</em></p>
<h3 id="heading-how-to-render-each-blog-content">How to render each blog content</h3>
<p>Just rendering the path of the blog was great for simplifying the previous concepts, but that’s not quite our result.</p>
<p>Let’s properly render each blog content. First here’s the solution:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { getCollection } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro:content"</span>;
<span class="hljs-keyword">import</span> BlogLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BlogLayout.astro"</span>;

<span class="hljs-comment">// Make the function async</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> allBlogPosts = <span class="hljs-keyword">await</span> getCollection(<span class="hljs-string">"blog"</span>);
    <span class="hljs-comment">// dynamically construct the blog paths</span>
    <span class="hljs-keyword">const</span> paths = allBlogPosts.map(<span class="hljs-function">(<span class="hljs-params">blogEntry</span>) =&gt;</span> ({
        <span class="hljs-comment">// construct params</span>
        <span class="hljs-attr">params</span>: {
            <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${blogEntry.data.year}</span>/<span class="hljs-subst">${blogEntry.data.month}</span>/<span class="hljs-subst">${blogEntry.data.day}</span>/<span class="hljs-subst">${blogEntry.slug}</span>`</span>,
        },
        <span class="hljs-comment">// 👀 Pass blogEntry as props to be later accessed in the markup via Astro.props</span>
        <span class="hljs-attr">props</span>: {
            blogEntry,
        },
    }));

    <span class="hljs-comment">//Eventually return the constructed paths</span>
    <span class="hljs-keyword">return</span> paths;
}

<span class="hljs-comment">// Get the blog entry from the props</span>
<span class="hljs-keyword">const</span> { blogEntry } = Astro.props;

<span class="hljs-comment">// get blog content via entry.render()</span>
<span class="hljs-keyword">const</span> { Content } = <span class="hljs-keyword">await</span> blogEntry.render();
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React Blog - React"</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"React Blog"</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Render the Content --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Content</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BlogLayout</span>&gt;</span></span>
</code></pre>
<p>Let’s deconstruct this solution.</p>
<p>The most important piece to the solution puzzle is passing every single blog entry as a <code>prop</code> in the <code>getStaticPath</code> function.</p>
<p>Doing this allows us to reference each entry in the component markup section via <code>Astro.props</code>.</p>
<p>Secondly, every queried collection entry has a <code>render()</code> method that renders the entry to <code>HTML</code>. The solution utilises this to render each blog.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { Content } = <span class="hljs-keyword">await</span> blogEntry.render();
<span class="hljs-comment">//...</span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Content</span> /&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-08.48.36.png" alt="The rendered blog content" width="600" height="400" loading="lazy">
<em>The rendered blog content</em></p>
<h2 id="heading-mdx-components">MDX Components</h2>
<p>Let’s get back to MDX.</p>
<p>The most impressive feature of MDX is the ability to use components with standard markdown content.</p>
<p>Let’s consider practical examples.</p>
<h3 id="heading-customised-html-elements">Customised HTML elements</h3>
<p>When MDX content is rendered to HTML, the eventual output uses standard HTML elements.</p>
<p>For example, if we had the following MDX content:</p>
<pre><code class="lang-js"># Title 

This is a paragraph
</code></pre>
<p>This will yield an HTML result similar to the following:</p>
<pre><code class="lang-js">&lt;h1&gt;Title&lt;/h1&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a paragraph<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
</code></pre>
<p>The good news is, instead of relying on standard HTML elements, we can specific components to be used instead of HTML elements. </p>
<p>For example, we may provide our own styled header and paragraph components in place of the standard <code>h1</code> and <code>p</code> HTML elements.</p>
<p>To do this, create an object of HTML element to custom component mapping.</p>
<pre><code class="lang-js"><span class="hljs-comment">// sample MDX component map </span>

<span class="hljs-comment">// Provide custom header and paragraph</span>
<span class="hljs-keyword">import</span> H1 <span class="hljs-keyword">from</span> <span class="hljs-string">"./H1.astro"</span>; <span class="hljs-comment">// custom Astro component</span>
<span class="hljs-keyword">import</span> P <span class="hljs-keyword">from</span> <span class="hljs-string">"./P.astro"</span> <span class="hljs-comment">// custom paragraph component</span>

<span class="hljs-comment">// map of HTML element to custom component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> mdxComponents = {
  <span class="hljs-attr">h1</span>: H1,
  <span class="hljs-attr">p</span>: P,
}
</code></pre>
<p>Now, when the MDX content is rendered to HTML, pass the component map as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> {getEntry} <span class="hljs-keyword">from</span> <span class="hljs-string">'astro:content'</span>
<span class="hljs-comment">// import the component map </span>
<span class="hljs-keyword">import</span> { mdxComponents } <span class="hljs-keyword">from</span> <span class="hljs-string">'../mdxComponents'</span>

<span class="hljs-comment">// Get a collection entry</span>
<span class="hljs-keyword">const</span> blogCollection = <span class="hljs-keyword">await</span> getEntry(<span class="hljs-string">'blog'</span>, <span class="hljs-string">'some-title'</span>)
<span class="hljs-comment">// Get the entry Content</span>
<span class="hljs-keyword">const</span> { Content } = <span class="hljs-keyword">await</span> blogEntry.render();
---

{<span class="hljs-comment">/** Render to HTML and pass the components map**/</span>}
&lt;Content components={mdxComponents} /&gt;
</code></pre>
<p>Let’s put this into action.</p>
<p>Take a look at the <code>src/components/mdxComponents.ts</code> file in the project. It contains a list of HTML elements and associated custom Astro components.</p>
<p>We’ll import this object and pass it to the blog entry <code>&lt;Content /&gt;</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/blog/[...path].astro</span>
---
<span class="hljs-keyword">import</span> { mdxComponents } <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/mdxComponents"</span>;
<span class="hljs-comment">// ... other imports </span>
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BlogLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"React Blog - React"</span> <span class="hljs-attr">header</span>=<span class="hljs-string">"React Blog"</span>&gt;</span>
    {/** 👀 pass the components down to Content **/}
    <span class="hljs-tag">&lt;<span class="hljs-name">Content</span> <span class="hljs-attr">components</span>=<span class="hljs-string">{mdxComponents}</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">BlogLayout</span>&gt;</span></span>
</code></pre>
<p>With this, we should now have properly styled components in place of the bland HTML elements.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-10-at-06.39.24.png" alt="Leveraging custom components for the MDX HTML output" width="600" height="400" loading="lazy">
<em>Leveraging custom components for the MDX HTML output</em></p>
<p>Consider the full list of available HTML elements that can be overwritten with custom components in the <a target="_blank" href="https://www.freecodecamp.org/news/p/3c1efa5a-f575-4365-9958-d220b339bc38/[https://mdxjs.com/table-of-components/]">official MDX documentation</a>.</p>
<h3 id="heading-internal-components">Internal components</h3>
<p>Components can also be imported and directly rendered within MDX. That’s part of the fun!</p>
<p>Go ahead and open the first blog route in <code>/blog/2020/12/21/data-fetching-with-react-server-components</code> and find the first <code>TODO</code> on the page.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-10-at-06.42.47.png" alt="TODO: add the Intro component" width="600" height="400" loading="lazy">
<em>TODO: add the Intro component</em></p>
<p>To resolve this TODO, we need to import and render the <code>Intro</code> component in <code>src/components/Intro.astro</code>.</p>
<p>Consider the solution below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/content/blog/data-fetching-with-react-server-components.mdx</span>
---

<span class="hljs-keyword">import</span> Intro <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/Intro.astro"</span>;

{<span class="hljs-comment">/** First content after the frontmatter and other imports**/</span>}
&lt;Intro&gt;
  <span class="hljs-number">2020</span> has been a long year. As it comes to an end we wanted to share a special
  Holiday Update on our research into zero-bundle-size **React Server
  Components**.
&lt;/Intro&gt;
---
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-09.07.29.png" alt="The rendered Intro component" width="600" height="400" loading="lazy">
<em>The rendered Intro component</em></p>
<p>We imported and rendered an Astro component right in the MDX file. How amazing!</p>
<p>Note that the <code>---</code> syntax represents dividers (as seen in 1 and 2 above) and not code fences as used to define markdown frontmatter.</p>
<p>There’s no limit to how many components we can import and render in an MDX file. So, we can go further and render another component as shown below:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** Import the Note component **/</span>}
<span class="hljs-keyword">import</span> Note <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/Note.astro"</span>;

{<span class="hljs-comment">/** Render at the bottom of the file **/</span>}
&lt;Note&gt;React Server Components are still <span class="hljs-keyword">in</span> research and development.&lt;/Note&gt;
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-02-at-10.43.07.png" alt="The rendered Note component" width="600" height="400" loading="lazy">
<em>The rendered Note component</em></p>
<p>Note that, unlike JavaScript imports that must be at the top of the file, we can import components in an MDX file anywhere aside from the frontmatter section.</p>
<p>I typically prefer to keep the imports at the top of the document right after the frontmatter, but you may also colocate the imports close to where they are rendered. Both options work!</p>
<h3 id="heading-external-imports">External imports</h3>
<p>We’ve seen different imported components in our MDX documents. Luckily, it gets even more fun.</p>
<p>We can also import and render external components, for example from NPM in MDX.</p>
<p>Go ahead and install <code>astro-embed</code></p>
<pre><code>npm install astro-embed
</code></pre><p><code>astro-embed</code> lets us embed components such as Tweets and Youtube videos in an Astro project.</p>
<p>In the same blog in <code>/blog/2020/12/21/data-fetching-with-react-server-components</code> consider the next TODO:</p>
<pre><code class="lang-md"><span class="hljs-section">## Reference</span>

To introduce React Server Components, we have prepared a talk 
and a demo. If you want, you can check them out during the. 
holidays, or later when work picks back up in the new year.

❗️TODO: Add Youtube video embed here
</code></pre>
<p>To resolve this, go ahead and import the <code>Youtube</code> component from <code>astro-embed</code> and render the component with an <code>id</code> prop as shown below:</p>
<pre><code class="lang-md"><span class="hljs-section">## Reference</span>

To introduce React Server Components, we have prepared a talk and a demo. If you want, you can check them out during the holidays, or later when work picks back up in the new year.

import { YouTube } from "astro-embed";

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">YouTube</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"https://youtu.be/TQQPAU21ZUw"</span> /&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-10-at-07.05.09.png" alt="The rendered Youtube component" width="600" height="400" loading="lazy">
<em>The rendered Youtube component</em></p>
<p>Note that we’re colocating the import statement close to the component render. But we may move the import higher up the file as well.</p>
<pre><code class="lang-md">{/<span class="hljs-strong">** ✅ This is correct **</span>/}

import { YouTube } from "astro-embed";

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">YouTube</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"https://youtu.be/TQQPAU21ZUw"</span> /&gt;</span></span>
</code></pre>
<pre><code class="lang-md">{/<span class="hljs-strong">** ✅ This is equally correct **</span>/}

{/<span class="hljs-strong">** Keep all imports on top, right after the frontmatter **</span>/}

import Intro from "@components/Intro.astro";
import { YouTube } from "astro-embed";

{/<span class="hljs-strong">** Render other content ... and component much later **</span>/}

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">YouTube</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"https://youtu.be/TQQPAU21ZUw"</span> /&gt;</span></span>
</code></pre>
<h3 id="heading-autoimport">AutoImport</h3>
<p>The <code>Youtube</code>, <code>Intro</code> and <code>Note</code> components are used across all the blogs. Right now, importing the components every single time seems repetitive.</p>
<p>With components we want to be reused across our entire MDX files, how about we automatically import these – that is, without manually duplicating the import in every MDX document?</p>
<p>To achieve this, we will leverage the <code>astro-auto-import</code> package.</p>
<p>With <code>astro-auto-import</code>, we can easily import components or modules automatically and utilize them in MDX files without the need for manual importing.</p>
<p>First, install <code>astro-auto-import</code>:</p>
<pre><code class="lang-md">npm install astro-auto-import
</code></pre>
<p><code>astro-auto-import</code> works as an Astro integration. To use it, we must update the project <code>astro.config.mjs</code> file as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// other imports ...</span>
<span class="hljs-comment">// import AutoImport</span>
<span class="hljs-keyword">import</span> AutoImport <span class="hljs-keyword">from</span> <span class="hljs-string">"astro-auto-import"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [
   <span class="hljs-comment">// Pass AutoImport in the integrations array </span>
    AutoImport({
      <span class="hljs-attr">imports</span>: [
        <span class="hljs-comment">/**
         * Generates:
         * import Intro from './src/components/Intro.astro';
         */</span>
        <span class="hljs-string">"./src/components/Intro.astro"</span>,
        <span class="hljs-string">"./src/components/Note.astro"</span>,
        <span class="hljs-comment">/**
         * Generates:
         * import { YouTube } from 'astro-embed';
         */</span>
        { <span class="hljs-string">"astro-embed"</span>: [<span class="hljs-string">"YouTube"</span>] },
      ],
    }),
    react(),
    tailwind(),
    mdx(),
  ],
});
</code></pre>
<p>To use <code>AutoImport</code> we pass it into the <code>integrations</code> array and invoke <code>AutoImport</code> with an imports list:</p>
<pre><code class="lang-js">AutoImport({
   <span class="hljs-attr">imports</span>: [
     <span class="hljs-string">"./src/components/Intro.astro"</span>,
     <span class="hljs-string">"./src/components/Note.astro"</span>,
     { <span class="hljs-string">"astro-embed"</span>: [<span class="hljs-string">"YouTube"</span>] },
   ],
})
</code></pre>
<p>The <code>imports</code> represents a list of imports to be automatically added to our MDX files.</p>
<p>A string with the path of the import such as <code>"./src/components/Intro.astro"</code> will generate a default import such as <code>import Intro from './src/components/Intro.astro'</code>.</p>
<p>An object such as <code>{ "astro-embed": ["YouTube"] }</code> generates a named import such as <code>import { Tweet, YouTube } from 'astro-embed'</code>.</p>
<p>With these in place, we must now remove the manual imports in the MDX files and rely on the <code>AutoImport</code> magic ✨</p>
<p>Neat!</p>
<h2 id="heading-integration-spotlight-astro-seo">Integration Spotlight: Astro SEO</h2>
<p>You’ve seen a lot of Astro integrations already. Think <code>@astrojs/react</code> for having React islands in an Astro project, or the official <code>@astrojs/tailwind</code> integration for using tailwind in Astro.</p>
<p>Generally speaking, integrations add new functionality and behaviour to an Astro project, usually with just a few lines of code.</p>
<p>Sounds like a win!</p>
<p>In this section, let’s discuss <code>astro-seo</code>, an integration that makes it straightforward to add SEO-relevant information to any Astro app.</p>
<p>You know the rodeo.</p>
<p>First, install the integration:</p>
<pre><code class="lang-js">npm install astro-seo
</code></pre>
<p>To use <code>astro-seo</code>, we import the <code>SEO</code> component and pass it relevant props as seen below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/BaseLayout.astro</span>
---
<span class="hljs-keyword">import</span> { SEO } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro-seo"</span>;
<span class="hljs-comment">// ...</span>
---
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">SEO</span>
      <span class="hljs-attr">title</span>=<span class="hljs-string">{title}</span>
      <span class="hljs-attr">description</span>=<span class="hljs-string">{description}</span>
      <span class="hljs-attr">openGraph</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">basic:</span> {
          <span class="hljs-attr">title</span>,
          <span class="hljs-attr">type:</span> "<span class="hljs-attr">website</span>",
          <span class="hljs-attr">image:</span> "<span class="hljs-attr">https:</span>//<span class="hljs-attr">react.dev</span>/<span class="hljs-attr">images</span>/<span class="hljs-attr">og-home.png</span>",
        },
      }}
      <span class="hljs-attr">twitter</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">creator:</span> "@<span class="hljs-attr">reactjs</span>",
      }}
      <span class="hljs-attr">extend</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">meta:</span> [
          {
            <span class="hljs-attr">name:</span> "<span class="hljs-attr">twitter:image</span>",
            <span class="hljs-attr">content:</span> "<span class="hljs-attr">https:</span>//<span class="hljs-attr">react.dev</span>/<span class="hljs-attr">images</span>/<span class="hljs-attr">og-home.png</span>",
          },
          { <span class="hljs-attr">name:</span> "<span class="hljs-attr">twitter:title</span>", <span class="hljs-attr">content:</span> "@<span class="hljs-attr">reactjs</span>" },
          {
            <span class="hljs-attr">name:</span> "<span class="hljs-attr">twitter:description</span>",
            <span class="hljs-attr">content:</span> <span class="hljs-attr">description</span>,
          },
        ],
      }}
    /&gt;</span>
  {/** ... **/}
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
{/** ... **/}
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>This will generate relevant meta tags including open-graph meta tags for a more SEO-compliant application.</p>
<h2 id="heading-how-to-create-custom-404-pages-in-astro">How to Create Custom 404 Pages in Astro</h2>
<p>Custom 404 pages are easy to reason about in Astro. Create a <code>404.astro</code> or any other relevant page file ending in <code>src/pages</code>. This will build a <code>404.html</code> page that most deployment services will use if an invalid page is requested and not found.</p>
<p>Let’s do this for our project.</p>
<p>Create a <code>404.astro</code> page in <code>src/pages</code> with the following content:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/404.astro</span>
---
<span class="hljs-keyword">import</span> BaseLayout <span class="hljs-keyword">from</span> <span class="hljs-string">"@layouts/BaseLayout.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BaseLayout</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Redirecting ..."</span> <span class="hljs-attr">page</span>=<span class="hljs-string">"index"</span> /&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">is:inline</span>&gt;</span><span class="javascript">
<span class="hljs-comment">// lazy redirect. This is better done server-side: discussed in the next book's chapter</span>
<span class="hljs-keyword">const</span> { pathname } = <span class="hljs-built_in">window</span>.location;

<span class="hljs-built_in">window</span>.location.replace(<span class="hljs-string">`https://www.react.dev<span class="hljs-subst">${pathname}</span>`</span>);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
</code></pre>
<p>Our <code>404</code> page comes with a twist.</p>
<p>It renders a blank page via <code>&lt;BaseLayout /&gt;</code> and automatically redirects the user to the accompanying path on <code>www.react.dev</code>. Violà!</p>
<p>Give this a try by visiting the API reference link on the homepage.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/07/CleanShot-2023-07-10-at-07.28.40.png" alt="The API reference link" width="600" height="400" loading="lazy">
<em>The API reference link</em></p>
<h2 id="heading-wrapping-up-this-chapter-4">Wrapping Up This Chapter</h2>
<p>Building rich content applications is right up Astro’s alley. With content collections, we can build large content-driven applications with organisation and confidence.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-138.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter six.</em></p>
<h1 id="heading-chapter-6-server-side-rendering-ssr-in-astro-1">Chapter 6: Server-Side Rendering (SSR) in Astro</h1>
<p>This chapter will show you how to enable SSR in an Astro project. We will also discuss a detailed overview of the extensive features a server-side rendered Astro project offers.</p>
<h2 id="heading-what-youll-learn-5">What You’ll Learn</h2>
<ul>
<li>Enable SSR in an Astro project.</li>
<li>Leverage environment variables to store secrets.</li>
<li>Provide flexible server routing via dynamic routes.</li>
<li>Understand the request-response cycle and its relevant properties.</li>
<li>Take advantage of Astro API routes to power robust applications.</li>
</ul>
<h2 id="heading-when-do-you-need-ssr">When Do You Need SSR?</h2>
<p>In an earlier chapter, we discussed several rendering techniques for a frontend application. The reason was so we could make effective decisions for when to choose one rendering technique over the other.</p>
<p>I’ll briefly summarise why we may need SSR in an Astro project. Remember that your mileage may vary – so always refer to the basics discussed in Chapter 3: Build Your Own Component Island.</p>
<p>Now, the following are pointers to when we may need to enable SSR in an Astro project:</p>
<ul>
<li><strong>Content that is subject to frequent changes.</strong>: We may need SSR if a page’s content frequently changes, rather than using a statically built page which would require a rebuild for every new change.</li>
<li><strong>The need for API endpoints</strong>: SSR allows us to create API endpoints while keeping sensitive data hidden from clients. We’ll see how to do this later in the chapter.</li>
<li><strong>Creating pages with restricted access</strong>: To limit access to a page, enable server rendering for server-side handling of user privileges.</li>
</ul>
<h2 id="heading-how-to-enable-ssr-in-astro">How to Enable SSR in Astro</h2>
<p>Okay, here’s how it all begins. To enable SSR in an Astro project, set the <code>output</code> configuration option to <code>server</code> in the <code>astro.config.mjs</code> file.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// 📂 astro.config.mjs </span>

<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-comment">//This will enable SSR</span>
  output: <span class="hljs-string">'server'</span>
})
</code></pre>
<p>And that’s it!</p>
<p>Let’s see this in action by starting a new project with the following command:</p>
<pre><code class="lang-ts">npm create astro<span class="hljs-meta">@latest</span> --  --template=minimal --yes --skip-houston ssr
</code></pre>
<p>This will use the <code>minimal</code> template, <code>--skip-houston</code> will skip the Houston animation, and the <code>--yes</code> option will skip all prompts and accept the defaults.</p>
<p>Now, change directories into <code>ssr</code> and start the project:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ssr &amp;&amp; npm start
</code></pre>
<p>The app should run on a local server with a single <code>index.astro</code> page.</p>
<p>If we build the application for production via <code>npm build</code>, we should have the single <code>index.astro</code> page pre-rendered, that is statically built.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-22-at-07.13.56.png" alt="Statically rendering the index.astro page." width="600" height="400" loading="lazy">
<em>Statically rendering the index.astro page.</em></p>
<p>To re-iterate, a pre-rendered application is essentially a static site, that is – not server-side rendered.</p>
<p>To initiate server-side rendering, let’s change the configuration to include the <code>output</code> property as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">output</span>: <span class="hljs-string">'server'</span>
});
</code></pre>
<p>If we rerun the production build, we will have an error in the console.</p>
<pre><code class="lang-she">[error] Cannot use `output: 'server'` without an adapter. Please install and configure the appropriate server adapter for your final deployment.
</code></pre>
<h2 id="heading-how-to-deploy-an-ssr-project">How to Deploy an SSR Project</h2>
<p>The root cause of the error above is that to build your application for server-side rendering, the Astro build command must know what server you’ll eventually be deploying to.</p>
<p>SSR requires a server runtime, that is the code running within the server that renders our Astro pages. To achieve this, Astro provides adapters that match our deployment runtime.</p>
<p>An adapter allows Astro to do two things. First, determine the server runtime environment. Second, output a script that runs the SSR code on the specified runtime.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/astro_adapter_needs.png" alt="The Astro adapter needs." width="600" height="400" loading="lazy">
<em>The Astro adapter needs.</em></p>
<p>At the time of writing, the available Astro adapters are Cloudfare, Deno, Netlify, NodeJS, and Vercel.</p>
<p>We may deploy our SSR project to any of these runtimes with natively supported adapters.</p>
<p>To install any of these adapters, use the command:</p>
<pre><code class="lang-bash">npx astro add [name-of-adapter]
</code></pre>
<p><code>[name-of-adapter]</code> could be <code>cloudfare</code>, <code>deno</code>, <code>netlify</code>, <code>node</code> or <code>vercel</code>.</p>
<p>I recommend looking at the <a target="_blank" href="https://docs.astro.build/en/guides/deploy/">official reference</a> for any adapters you need in your project, as it would be unreasonable to cover all of these in the book. Here, we will stick to <code>netlify</code>.</p>
<p>To add the <code>netlify</code> adapter, go ahead and enter the following command in the terminal:</p>
<pre><code class="lang-bash">npx astro add netlify
</code></pre>
<p>This will go ahead and install the adapter and update our configuration file to the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;
<span class="hljs-comment">// 👀 look here</span>
<span class="hljs-keyword">import</span> netlify <span class="hljs-keyword">from</span> <span class="hljs-string">"@astrojs/netlify/functions"</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">output</span>: <span class="hljs-string">"server"</span>,
  <span class="hljs-comment">// 👀 look here</span>
  <span class="hljs-attr">adapter</span>: netlify()
});
</code></pre>
<p>Essentially, the adapter is imported in the second line of the config and added to the <code>adapter</code> property.</p>
<p>Now re-run the build command:</p>
<pre><code class="lang-js">npm run build
</code></pre>
<p>This will successfully build our SSR project for production by outputting <code>netlify</code> specific code snippets in the <code>dist</code> and <code>.netlify</code> directory.</p>
<p>Now, we’re in business 🚀</p>
<h2 id="heading-use-the-correct-adapter">Use the Correct Adapter</h2>
<p>It goes without saying that, after adding an adapter, the project should be deployed to the specified adapter (here, <code>netlify</code>) and not some other provider (like <code>vercel</code>).</p>
<p>Use the correct adapter for your deployment runtime.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/adapter_deploy.png" alt="Deploying a Vercel adapter to Netlify is wrong." width="600" height="400" loading="lazy">
<em>Deploying a Vercel adapter to Netlify is wrong.</em></p>
<p>Our actual deployment steps will vary depending on the server runtime being deployed. For example, for Netlify, we may follow the steps described in the deploy a static site in Chapter 1. These steps will be identical for similar runtimes like Vercel.</p>
<p>For other runtimes, the official Astro <a target="_blank" href="https://docs.astro.build/en/guides/deploy/">deployment guides</a> do an excellent job of explaining the deployment steps required.</p>
<h2 id="heading-ssr-with-static-pages">SSR with Static Pages</h2>
<p>With the <code>output</code> configuration property set to <code>server</code>, every page in our Astro project will be server-side rendered. But there’s a great chance we may want one or more pages to be statically generated at build time, that is some pages server-side rendered and others pre-rendered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/hybrid_rendering.png" alt="Having a mix of server and statically rendered pages. " width="600" height="400" loading="lazy">
<em>Having a mix of server and statically rendered pages.</em></p>
<p>In such cases, we can opt-in to pre-rendering by adding <code>export const prerender = true</code> to any page that supports exporting variables, e.g., <code>.astro</code>, <code>.mdx</code> <code>, .ts</code> and <code>.js</code>.</p>
<p>Let’s try this out by creating a new <code>about.astro</code> page with the following content:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/about.astro </span>

---
<span class="hljs-comment">// 👀 note the prerender export</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> prerender = <span class="hljs-literal">true</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>About us<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>With the <code>prerender</code> export, the <code>about</code> page will be statically rendered at build time, while the <code>index</code> page remains server-side rendered.</p>
<p>Run <code>npm run build</code> to see this in action.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-22-at-08.33.08.png" alt="Static and server-side generated pages in the same project." width="600" height="400" loading="lazy">
<em>Static and server-side generated pages in the same project.</em></p>
<h2 id="heading-from-request-to-response">From Request to Response</h2>
<p>The interaction between a client and server may be simplified in two steps:</p>
<ul>
<li>the client makes a <strong>request</strong>.</li>
<li>the server sends a <strong>response</strong>.</li>
</ul>
<p>The two main entities in this simplified interaction are the client request and the server response. Luckily, with server-side rendering, we may access details of the request and response object.</p>
<h3 id="heading-the-request-object">The Request object</h3>
<p>The <code>Request</code> object may be accessed on the <code>Astro</code> global as shown below:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">const</span> request = Astro.request
---
</code></pre>
<p>The object holds information about the current request and is represented by the standard <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a> interface of the fetch API.</p>
<pre><code class="lang-js">interface Request <span class="hljs-keyword">extends</span> Body {
    readonly cache: RequestCache
    readonly credentials: RequestCredentials;
    readonly destination: RequestDestination;
    readonly headers: Headers;
    readonly integrity: string;
    readonly keepalive: boolean;
    readonly method: string;
    readonly mode: RequestMode;
    readonly redirect: RequestRedirect;
    readonly referrer: string;
    readonly referrerPolicy: ReferrerPolicy;
    readonly signal: AbortSignal;
    readonly url: string;
    clone(): Request;
}
</code></pre>
<p>For example, we may access the request headers via <code>Astro.request.headers</code> and the current request URL as a string via <code>Astro.request.url</code>.</p>
<h3 id="heading-the-response-object">The Response object</h3>
<p>The <code>Response</code> object is the corresponding interface representing the response to a request. This is also represented by the standard <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Response">Response</a> interface of the Fetch API.</p>
<p>As opposed to accessing the object on the <code>Astro</code> object, the <code>Response</code> object is created using the <code>Response()</code> constructor.</p>
<p>The <code>Response()</code> constructor has the following signature:</p>
<pre><code class="lang-js"><span class="hljs-keyword">new</span> Response(body, options)
</code></pre>
<p>Where <code>body</code> defines the body for the response and <code>options</code> is an object containing custom settings to apply to the response, that is <code>status</code>, <code>statusText</code> and <code>headers</code>.</p>
<p>For example, we could update our <code>index</code> page to return a new response if we were presumably in beta – represented by a simple variable.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> isBeta = <span class="hljs-literal">true</span>;

<span class="hljs-keyword">if</span> (isBeta) {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-string">"app not available - check back"</span>, {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    <span class="hljs-attr">statusText</span>: <span class="hljs-string">"OK!"</span>,
  });
} 
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>We're live!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Instead of returning the <code>HTML</code> page, we should now have a simple text response sent to the client.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-22-at-10.43.19.png" alt="Returning a simple text response to the client." width="600" height="400" loading="lazy">
<em>Returning a simple text response to the client.</em></p>
<p>There’s also a <code>response</code> object on the <code>Astro</code> global. Blimey!</p>
<p>But it’s important to note that this is not the same as the <code>Response</code> object constructor. So, rewriting our example to use <code>Astro.response</code> will fail.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> isBeta = <span class="hljs-literal">true</span>;

<span class="hljs-keyword">if</span> (isBeta) {
  <span class="hljs-comment">// ❌ This is wrong and will fail</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Astro.response(<span class="hljs-string">"app not available - check back"</span>, {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    <span class="hljs-attr">statusText</span>: <span class="hljs-string">"Excellent!"</span>,
  });
}
---
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-22-at-11.28.52.png" alt="Error: Astro.response is not a constructor." width="600" height="400" loading="lazy">
<em>Error: Astro.response is not a constructor.</em></p>
<p>This is because <code>Astro.response</code> represents the response object initialiser. It’s used to set the <code>options</code> on the server response, i.e., <code>status</code>, <code>statusText</code> and <code>headers</code>.</p>
<p>For example, to set a custom header on the server response, we could do the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro </span>
---
Astro.response.headers.set(<span class="hljs-string">"beta_id"</span>, <span class="hljs-string">"some_header_value"</span>);
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>We're live!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>The server will return the <code>HTML</code> page and our custom <code>beta_id</code> header.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-22-at-11.31.50.png" alt="Setting a custom header on the server response." width="600" height="400" loading="lazy">
<em>Setting a custom header on the server response.</em></p>
<h3 id="heading-redirect-response">Redirect response</h3>
<p>It is pretty common to receive a client request and perform a redirect on the server.</p>
<p>There are two ways to achieve this in Astro.</p>
<p>The first is to leverage the standard <code>Response</code> object via <code>Response.redirect</code>.</p>
<p>Consider a case where we want to redirect a user to another page if they are not logged in, as shown below:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** 📂 src/index.astro **/</span>}
---
<span class="hljs-keyword">const</span> getIsLoggedOut = <span class="hljs-function">() =&gt;</span> <span class="hljs-literal">true</span>;
<span class="hljs-keyword">const</span> isLoggedOut = getIsLoggedOut();

<span class="hljs-keyword">if</span> (isLoggedOut) {
  <span class="hljs-keyword">return</span> Response.redirect(<span class="hljs-string">`<span class="hljs-subst">${Astro.request.url}</span>about`</span>, <span class="hljs-number">307</span>);
}
---
</code></pre>
<p>In this example, we call <code>Response.redirect</code> while passing it a redirect URL and a status code, that is:</p>
<pre><code class="lang-js">Response.redirect(URL, status)
</code></pre>
<p>It’s important to note that the <code>URL</code> in this case is an absolute path. So constructing from <code>Astro.request.url</code> that points to the absolute base path, for example <code>http://localhost:3001/</code>.</p>
<p>When logged out, the user will be redirected to the <code>about</code> page and the optional status code <code>307</code> indicates a temporary redirect.</p>
<p>As we’ve seen above, constructing the absolute URL could get unnecessarily complex. Luckily, there’s an alternative way to perform a redirect.</p>
<p>We may also leverage the <code>Astro.redirect</code> method to redirect to another page. For example, we could rewrite our solution to use <code>Astro.redirect</code> as shown below:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> getIsLoggedOut = <span class="hljs-function">() =&gt;</span> <span class="hljs-literal">true</span>;
<span class="hljs-keyword">const</span> isLoggedOut = getIsLoggedOut();

<span class="hljs-keyword">if</span> (isLoggedOut) {
  <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/about"</span>, <span class="hljs-number">307</span>);
}
---
</code></pre>
<p>We have a much simpler API here. We can redirect by just passing the relative path to redirect to. The status code is also optional here.</p>
<p>It’s important to note that redirects should be done in page components, that is not inside other components like layouts or base components.</p>
<h3 id="heading-utilities-for-manipulating-cookies">Utilities for manipulating cookies</h3>
<p>In SSR mode, we may need to read or manipulate cookies. Well, Astro’s got us covered with <code>Astro.cookies</code>. This contains utilities for reading and using cookies in SSR mode.</p>
<p>Consider the examples of retrieving a cookie:</p>
<pre><code class="lang-js"><span class="hljs-comment">//Get an AstroCookie object </span>
<span class="hljs-keyword">const</span> cookieObject = Astro.cookies.get(<span class="hljs-string">"coooookiee"</span>)

<span class="hljs-comment">// Get the string value of the cookie </span>
<span class="hljs-keyword">const</span> cookieValue = cookieObject.value 

<span class="hljs-comment">// Parse the cookie value via JSON.parse. Returns an object if the cookie is a valid JSON. It throws an error otherwise. </span>

<span class="hljs-keyword">const</span> cookieJSON = cookieObject.json()

<span class="hljs-comment">// Parse the cookie value as a Number </span>
<span class="hljs-keyword">const</span> cookieNumber = cookieObject.number() 

<span class="hljs-comment">// Parse the cookie as a boolean </span>
<span class="hljs-keyword">const</span> cookieBoolean = cookieObject.boolean()
</code></pre>
<p>That’s a lot of flexibility!</p>
<p>We may also check if a cookie exists with the <code>has</code> method, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// check if the "cooooookies" cookie exists. returns a boolean</span>
<span class="hljs-keyword">const</span> hasCookie = Astro.cookies.has(<span class="hljs-string">'cooooookies'</span>)
</code></pre>
<p>It is also possible to set a cookie as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Set a cookie </span>
Astro.cookies.set(<span class="hljs-string">"cooookiees"</span>, <span class="hljs-string">"the-cookie-value"</span>)
</code></pre>
<p>The signature for <code>Astro.cookies.set</code> is shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Astro.set(key, value, options)</span>
<span class="hljs-attr">key</span>: string, 
<span class="hljs-attr">value</span>: string | number | boolean | object,
options?: CookieOptions) =&gt; <span class="hljs-keyword">void</span>
</code></pre>
<p>Note how different cookie value types may be set and additional cookie <a target="_blank" href="https://www.npmjs.com/package/cookie#options-1">options</a> passed if needed, for example <code>domain</code>, <code>encode</code>, <code>expires</code>, <code>maxAge</code> or <code>httpOnly</code>.</p>
<h3 id="heading-the-request-ip-address">The request IP address</h3>
<p>Understanding <a target="_blank" href="https://www.freecodecamp.org/news/ipv4-vs-ipv6-what-is-the-difference-between-ip-addressing-schemes/">IP addresses</a> is beyond the scope of this book. But, we may gain access to the request’s IP address on the server via the <code>Astro.clientAddress</code> property.</p>
<p>Below’s a simple example:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> ip = Astro.clientAddress;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Your IP address is: {ip}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<h2 id="heading-environment-variables">Environment Variables</h2>
<p>If you’re completely new to environment variables, you might the thinking, <em>"Oi, what are Environment variables, and why should I care?"</em></p>
<p>Generally speaking, environment variables help us store important information like API keys or sensitive data without ever having to reveal them to clients accessing your application.</p>
<p>Like any secret, environment variables can be arguably slightly tricky to handle. You need to know exactly where to find them, how to use them, and most importantly, how to keep them safe from prying eyes.</p>
<h3 id="heading-how-to-get-environment-variables">How to get environment variables</h3>
<p>In Astro, environment variables are accessed on the <code>import.meta.env</code> object.</p>
<p>So, for example, if we had a <code>CAT_API_TOKEN</code> value, we would access it as follows:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span>.meta.env.CAT_API_TOKEN
---
</code></pre>
<p>If you’re conversant with environment variables in node environments, you’ll notice that this differs from the classic <code>process.env</code> object. Astro leverages Vite, which uses the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta">import.meta</a> JavaScript feature.</p>
<h3 id="heading-default-environment-variables">Default environment variables</h3>
<p>We all have secrets.</p>
<p>Well, I’m not quite sure of that. Let me rephrase: most people have secrets.</p>
<p>Similarly, every Astro project has some default secrets, aka environment variables, out of the box. Consider the defaults below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Get the mode the Astro site is running in: "development" | "production" </span>
<span class="hljs-keyword">import</span>.meta.env.MODE 

<span class="hljs-comment">// Is the site running in production? returns true or false </span>
<span class="hljs-keyword">import</span>.meta.env.PROD 

<span class="hljs-comment">// Is the site running in development? returns true or false </span>
<span class="hljs-keyword">import</span>.meta.env.DEV 

<span class="hljs-comment">// The base URL of the Astro site </span>
<span class="hljs-keyword">import</span>.meta.env.BASE_URL

<span class="hljs-comment">// Get the final deployed URL of the Astro site</span>
<span class="hljs-keyword">import</span>.meta.env.SITE

<span class="hljs-comment">// Get prefix for Astro-generated asset links </span>
<span class="hljs-keyword">import</span>.meta.env.ASSETS_PREFIX
</code></pre>
<p>For <code>import.meta.env.BASE_URL</code>, it’s important to note that this will default to <code>/</code> except explicitly stated in the project configuration. For example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
   <span class="hljs-attr">base</span>: <span class="hljs-string">'/docs'</span>
})
</code></pre>
<p>Astro will now use <code>/docs</code> as the root for our pages and assets in the development and production build.</p>
<p>Similarly, <code>import.meta.env.SITE</code> relies on the <code>site</code> property set in the astro config, for example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
   <span class="hljs-attr">site</span>: <span class="hljs-string">'https://www.ohansemmanuel.com'</span>
})
</code></pre>
<p>Astro will use this full URL to generate the site’s sitemap and canonical URLs where relevant.</p>
<p><code>import.meta.env.ASSETS_PREFIX</code> also relies on the <code>build.assetsPrefix</code> option set in the project’s config, for example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span>  defineConfig  <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">build</span>: {
    <span class="hljs-attr">assetsPrefix</span>: <span class="hljs-string">'https://cdn.example.com'</span>
  }
})
</code></pre>
<p>This can be used if assets are served from a different domain than the current site. For example with the <code>https://cdn.example.com</code> prefix, assets will be fetched from <code>https://cdn.example.com/_astro/...</code>. This implies the files in the default astro build directory <code>./dist/astro</code> must be uploaded to the CDN directory to serve the assets.</p>
<p>Phew! Out with the secrets.</p>
<h3 id="heading-how-to-create-environment-variables">How to create environment variables</h3>
<p>It doesn’t do a lot of good if we can’t create our own secrets. Heck, it helps with the mystic.</p>
<p>The most common way to create environment variables is to use <code>.env</code> files.</p>
<p>For example, let’s go ahead and create a <code>.env</code> file in the root directory of our project directory with the following content:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/.env </span>
CAT_API_TOKEN=<span class="hljs-string">"this-is-the-cat-production-token"</span>
</code></pre>
<p>We may then access the secret server-side via <code>import.meta.env.CAT_API_TOKEN</code>.</p>
<p>I must mention that exposing certain environment variables to the client (browser) is possible. To do this, prefix the environment variable with a <code>PUBLIC_</code>, for example:</p>
<pre><code class="lang-js">PUBLIC_INSENSITIVE_TOKEN=<span class="hljs-string">"this-is-public"</span>
</code></pre>
<p><code>PUBLIC_INSENSITIVE_TOKEN</code> will now be accessible both on the server and client. That’s an open secret. Anyone, and I mean anyone, can see your dirty laundry here. Only use this for insensitive environment variables.</p>
<p>Remember that environment variables are only available in server-side code by default. Prefix environment variables with <code>PUBLIC_</code> to expose them to the client.</p>
<p>It is also possible to run your project and provide environment variables from the CLI, as shown below:</p>
<pre><code class="lang-bash">CAT_API_TOKEN=<span class="hljs-string">"this-is-the-cat-production-token npm run dev"</span>
</code></pre>
<p>In this case, <code>CAT_API_TOKEN</code> will be available both server-side and client-side. Use with caution. We only tell people we trust secrets and never blindly trust a client, like a user browser.</p>
<h3 id="heading-typescript-intellisense">TypeScript IntelliSense</h3>
<p>We don't get TypeScript IntelliSense support if we attempt to access <code>CAT_API_TOKEN</code> in <code>pages/index.astro</code> after creating the <code>.env</code> file.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-23-at-09.44.07.png" alt="No Typescript IntelliSense for our custom environment variable." width="600" height="400" loading="lazy">
<em>No Typescript IntelliSense for our custom environment variable.</em></p>
<p>We’re pro developers, so come on – let’s fix this.</p>
<p>We’ll find a <code>src/env.d.ts</code> file with projects started with an Astro template. Otherwise, go ahead and create one.</p>
<p>Here’s the initial content of the file if it already exists:</p>
<pre><code class="lang-ts"><span class="hljs-comment">/// &lt;reference types="astro/client" /&gt;</span>
</code></pre>
<p>Let’s extend the default <code>ImportMeta</code> interface that provides type definitions for <code>import.meta.env</code> by adding the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> ImportMetaEnv {
  <span class="hljs-keyword">readonly</span> CAT_API_TOKEN: <span class="hljs-built_in">string</span>;
  <span class="hljs-comment">// add other custom env variables...</span>
}
</code></pre>
<p>And voilà! TypeScript knows our secrets – for the better.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-23-at-09.50.10.png" alt="Typescript IntelliSense activated." width="600" height="400" loading="lazy">
<em>Typescript IntelliSense activated.</em></p>
<h2 id="heading-dynamic-routes">Dynamic Routes</h2>
<p>Static routes are arguably easy to reason about. For example, <code>.astro</code>, <code>.md</code> and <code>.mdx</code> files in <code>src/pages</code> will automatically become pages on our website.</p>
<p>But sometimes we require dynamic routes to prevent repetition. This typically happens when we have different routes with minimal UI changes between them.</p>
<p>For example, if we were selling products on our website, we would have a different route for each product.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// example routes for different products </span>
www.example.com/product/understanding-astro
www.example.com/product/astro-a-to-z
www.example.com/product/astro-<span class="hljs-keyword">for</span>-beginners
www.example.com/product/fullstack-astro
</code></pre>
<pre><code class="lang-ts"><span class="hljs-comment">// ❌ Providing multiple pages for each product</span>
/pages/understanding-astro.astro
/pages/astro-a-to-z
/pages/astro-<span class="hljs-keyword">for</span>-beginners
/pages/fullstack-astro
</code></pre>
<p>The URL structure of the product pages could be represented by <code>www.example.com/product/${name}</code> where <code>name</code> means the product’s name.</p>
<p>Instead of creating different pages to represent each product, we may dynamically handle the product routing in one of two ways.</p>
<h3 id="heading-1-named-parameters-1">1. Named parameters</h3>
<p>We could represent the variables in the route path with a named parameter surrounded by square brackets. For example, creating a file in the <code>pages</code> directory as follows:</p>
<pre><code class="lang-js">/pages/products/[product].astro
</code></pre>
<p>We may then grab the <code>product</code> path value on the page as follows:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** 📂 src/pages/[product].astro **/</span>}
&lt;h1&gt;{Astro.params.product}&lt;/h1&gt;
</code></pre>
<p>Alternatively:</p>
<pre><code class="lang-js">---
 <span class="hljs-keyword">const</span> {product} = Astro.params 
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{product}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>Now if we visit the <code>/products/understanding-astro</code> page, we should have the title of the product displayed.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-24-at-10.25.23.png" alt="Grabbing dynamic route path values." width="600" height="400" loading="lazy">
<em>Grabbing dynamic route path values.</em></p>
<p>In most cases, our variable path parameter will include a unique identifier, for example <code>/pages/products/[id].astro</code>.</p>
<p>The same routing works.</p>
<p>It is also possible to leverage multiple named parameters in the route path, as shown below:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** /products/[product]_[id].astro **/</span>}
&lt;h1&gt;Product name: {Astro.params.product}&lt;/h1&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Product id: {Astro.params.id}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>This will be matched with a URL similar to <code>/products/understanding-astro_09u34359534530903453450</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-24-at-10.31.22.png" alt="Matching multiple route named parameters." width="600" height="400" loading="lazy">
<em>Matching multiple route named parameters.</em></p>
<h3 id="heading-2-rest-parameters-1">2. Rest parameters</h3>
<p>Rest parameters provide ultimate flexibility in our URL routing. For example, we may use <code>[...path]</code> to match file paths of any depth. Where <code>path</code> could be represented by any string, like <code>[...file]</code> or <code>[...somestring]</code>.</p>
<p>Consider the following product pages:</p>
<pre><code class="lang-js">/products/product-id
/products/category/product-id/
<span class="hljs-regexp">/products/</span>types/category/product-id
</code></pre>
<p>The routes above will all be matched by the page <code>pages/product/[...path].astro</code>, and we can access the full dynamic string path within our code.</p>
<p>For example, create a file in <code>/pages/product/[...path].astro</code> with the following content:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> { path } = Astro.params;
<span class="hljs-built_in">console</span>.log({ path });
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hello there<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>For the paths above, the <code>path</code> variable corresponds to <code>product-id</code>, <code>category/product-id</code> and <code>types/category/product-id</code>.</p>
<p>With much power comes much responsibility.</p>
<p>With the increased flexibility rest path parameters provide comes the responsibility of handling the paths in our code. For example, consider how we may handle the multiple product paths below:</p>
<pre><code class="lang-js">---
<span class="hljs-comment">// Get the dynamic route path </span>
<span class="hljs-keyword">const</span> { path } = Astro.params;

<span class="hljs-comment">// Hold a list of all expected paths and corresponding data, e.g., title.</span>
<span class="hljs-keyword">const</span> page = [
  {
    <span class="hljs-attr">path</span>: <span class="hljs-literal">undefined</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"View all products"</span>
  },
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">"product-id"</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Some Product"</span>,
  },
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">"category/product-id"</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Some Product Category Item"</span>,
  },
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">"types/category/product-id"</span>,
    <span class="hljs-attr">title</span>: <span class="hljs-string">"Some Product Type Category Item"</span>,
  },
];

<span class="hljs-comment">//Is this a valid path? i.e., exists in our list? </span>
<span class="hljs-keyword">const</span> relevantPageDetails = page.find(<span class="hljs-function">(<span class="hljs-params">v</span>) =&gt;</span> v.path === path);

<span class="hljs-keyword">if</span> (!relevantPageDetails) {
  <span class="hljs-comment">// redirect if the dynamic page isn't valid.</span>
  <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/404"</span>);
}
---

<span class="hljs-comment">// render the title of the page </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{relevantPageDetails.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-24-at-12.42.28@2x.png" alt="Rendering rest parameter routes." width="600" height="400" loading="lazy">
<em>Rendering rest parameter routes.</em></p>
<p>It’s important to note that if the <code>path</code> is undefined, the root path will be matched, that is it corresponds to <code>pages/product</code>.</p>
<p>While this demonstrates using rest paths in server-side rendered pages, it is a contrived example where we’ve assumed the literal string “product-id”.</p>
<p>In the real world, the literal string will be represented by different product id strings rather than <code>product-id</code> – and we might not know what these are ahead of time.</p>
<p>As we’ve done in the previous solution, keeping a massive list of all product IDs in our application becomes unmaintainable.</p>
<p>For this use case, one way to achieve this would be to update our solution to have sufficiently complex matching logic, for example via regular expressions, because we don’t know the product IDs beforehand.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">const</span> { path = <span class="hljs-string">"index"</span> } = Astro.params;

<span class="hljs-keyword">const</span> page = [
  {
    <span class="hljs-attr">match</span>: <span class="hljs-regexp">/some-regex/</span>,
    title: <span class="hljs-string">"View all products"</span>,
  },
  {
    <span class="hljs-attr">match</span>: <span class="hljs-regexp">/some-regex/</span>,
    title: <span class="hljs-string">"Some Product"</span>,
  },
  {
    <span class="hljs-attr">match</span>: <span class="hljs-regexp">/some-regex/</span>,
    title: <span class="hljs-string">"Some Product Category Item"</span>,
  },
  {
    <span class="hljs-attr">match</span>: <span class="hljs-regexp">/some-regex/</span>,
    title: <span class="hljs-string">"Some Product Type Category Item"</span>,
  },
];

<span class="hljs-keyword">const</span> relevantPageDetails = page.find(<span class="hljs-function">(<span class="hljs-params">v</span>) =&gt;</span> path.match(v.match));

<span class="hljs-keyword">if</span> (!relevantPageDetails) {
  <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/404"</span>);
}
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{relevantPageDetails.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>As a matter of personal preference, I’ve sworn a blood oath to avoid path rest parameters for multiple SSR page paths when I can’t deterministically determine the path variables beforehand.</p>
<p>Simple is sometimes better.</p>
<p>In this case, I suggest separating the pages, that is creating multiple directories and letting the default Astro automatic routing kick in.</p>
<p>For example, match the path <code>category/product-id</code> by creating a page in <code>category/[id]</code> and <code>types/category/[id]</code> to match the route <code>types/category/product-id</code>.</p>
<p>They can also be composed with a common layout or shared components if they have identical user interfaces.</p>
<h3 id="heading-priority-order-1">Priority order</h3>
<p>As we’ve discussed above, URL paths can be matched in different ways, so what happens when different file paths match the same URL path in our project?</p>
<p>Well, Astro needs to make a decision, so lets review the priority list below:</p>
<ol>
<li>Static routes, that is those without path parameters, have the highest priority, for example <code>/pages/products/this-is-a-product</code>.</li>
<li>Dynamic routes with named parameters have the next priority, for example <code>/pages/products/[id]</code>.</li>
<li>Dynamic routes with rest parameters have the lowest priority, for example <code>/pages/products/[...path]</code>.</li>
<li>Following the above, any ties will be resolved alphabetically.</li>
</ol>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/route_priority.png" alt="Route priority order from first to last." width="600" height="400" loading="lazy">
<em>Route priority order from first to last.</em></p>
<h2 id="heading-server-endpoints">Server Endpoints</h2>
<p>Server endpoints are like the secret weapons in our arsenal when running server-side functions.</p>
<p>They can be used as REST API endpoints to run functions such as database access, authentications, and verifications without exposing sensitive data to the client, that is we can securely execute code on the server at runtime in these functions.</p>
<p>Consider the current state of our project with a <code>page/products</code> directory. What if we wanted to create an API route to handle some client requests? How would we do this?</p>
<h3 id="heading-how-to-create-server-endpoints">How to create server endpoints</h3>
<p>To create an API route in the <code>server</code> output mode, create a <code>.ts</code> or <code>.js</code> file within the <code>pages</code> directory. Optionally, you may see endpoints created with the type of data the endpoint returns in the file name, for example <code>.json.ts</code>.</p>
<p>I prefer to keep server endpoints simple and omit additional file names. Let’s go ahead and create an <code>api.ts</code> file and handle incoming <code>GET</code> requests as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/products/api</span>
<span class="hljs-keyword">import</span> type { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-function">(<span class="hljs-params">ctx</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Hello world"</span>,
    }),
  };
};
</code></pre>
<ul>
<li>Note the <code>APIRoute</code> type used on the <code>get</code> function. This represents the API route function type definition.</li>
<li>Every API route function receives a context object, for example represented by <code>ctx</code>. The <a target="_blank" href="https://docs.astro.build/en/reference/api-reference/#endpoint-context">context object</a> contains relevant properties we’ll take a look at shortly.</li>
<li>As shown above, an API route function can return a response with a <code>body</code>. The complete response form is shown below:<br>      <code>{</code><br>         <code>body: string</code><br>         <code>encoding?: 'ascii' | 'utf8' | 'utf-8' | 'utf16le' |</code><br>               <code>'ucs2' | 'ucs-2' | 'base64' | 'base64url' |</code><br>                <code>'latin1' | 'binary' | 'hex'</code><br>      <code>}</code><br>We may also return a standard response via the Response object as shown below:<br>      <code>import type { APIRoute } from "astro";</code><br>      <code>` </code>export const get: APIRoute = (ctx) =&gt; {<code>`return new Response(JSON.stringify({</code><br>          <code>message: "Hello world"</code><br>          <code>}), {</code><br>           <code>status: 200,</code><br>        <code>});</code><br>      <code>};</code></li>
</ul>
<h3 id="heading-request-details">Request details</h3>
<p>Accessing details of the request object is a breeze with API routes. For example, we may access the request object on the context object to check its headers, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-function">(<span class="hljs-params">ctx</span>) =&gt;</span> {
  <span class="hljs-comment">// check for an Authorization header on the request</span>
  <span class="hljs-keyword">const</span> auth = ctx.request.headers.get(<span class="hljs-string">"Authorization"</span>);

  <span class="hljs-comment">// The user is unauthorised to get this resource</span>
  <span class="hljs-keyword">if</span> (!auth) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Unauthorized"</span> }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">401</span>,
    });
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Hello world"</span> }), {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
  });
};
</code></pre>
<p>We could also destructure properties of the context object, for example the request object, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> {
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>While getting the <code>request</code> object is great, consider the complete list of properties available on the endpoint context object:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-function">(<span class="hljs-params">{
  url,
  site,
  params,
  request,
  cookies,
  generator,
  redirect,
  clientAddress,
}</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Hello world"</span> }), {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
  });
};
</code></pre>
<p>Some of these should be familiar from discussing the request and response objects on the <code>Astro</code> global; however, here’s a quick breakdown:</p>
<table>
    <thead>
        <tr>
            <th>
                Property
            </th>
            <th>
                 What?
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br>url
            </td>
            <td>
                <br>A standard <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL">URL</a> interface. <br>
            </td>
        </tr>
        <tr>
            <td>
                <br>site
            </td>
            <td>
                <br>The site property from the astro configuration file.<br>
            </td>
        </tr>
        <tr>
            <td>
                <br>params
            </td>
            <td>
                <p><br>An object containing values of the dynamic </p>

                <p>path segments matched by the request.</p>
            </td>
        </tr>
        <tr>
            <td>
                <br>request
            </td>
            <td>
                <br>A standard <a href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a> interface of the Fetch API.<br>
            </td>
        </tr>
        <tr>
            <td>
                <br>cookies
            </td>
            <td>
                <br>Similar to Astro.cookies. It contains utilities <br>for reading and manipulating cookies.
            </td>
        </tr>
        <tr>
            <td>
                <br>generator
            </td>
            <td>
                <br>Indicates the version of Astro our project is running.<br>
            </td>
        </tr>
        <tr>
            <td>
                <br>redirect
            </td>
            <td>
                <br>Similar to Astro.redirect. <br>
            </td>
        </tr>
        <tr>
            <td>
                <br>clientAddress
            </td>
            <td>
                <br>Specifies the IP address of the request. <br>Similar to Astro.clientAddress
            </td>
        </tr>
    </tbody>
</table>

<p>The alien properties here are <code>generator</code>, <code>url</code> and <code>params</code>.</p>
<p><code>generator</code> is easy to reason about, while <code>url</code> represents a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/URL">URL</a> object constructed from <code>request.url</code>, that is identical to <code>new URL(request.url)</code>.</p>
<p>It’s worth mentioning that a similar object may be accessed on the <code>Astro</code> global via <code>Astro.url</code>. This could come in handy in static pages.</p>
<p>What about <code>params</code>? Well, that requires a separate section when we discuss dynamic routes.</p>
<h3 id="heading-dynamic-api-routes">Dynamic API routes</h3>
<p>The dynamic route fabric on pages works the same magic on API endpoints.</p>
<p>For example, our API endpoint is in the <code>pages/products/api</code> file. What if we wanted client requests to be made in the format: <code>GET /api/products/${id}</code>?</p>
<p>Did you notice the variable <code>id</code>?</p>
<p>In this case, we may leverage dynamic routes as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/api/products/[id]</span>

<span class="hljs-keyword">import</span> type { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-comment">// Get the product ID </span>
  <span class="hljs-keyword">const</span> productId = ctx.params.id;

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://fakestoreapi.com/products/1"</span>);
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
     ...data, 
     <span class="hljs-comment">// Add the ID in the response body</span>
     <span class="hljs-attr">id</span>: productId 
    }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
        <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred."</span> 
      }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">500</span>,
    });
  }
};
</code></pre>
<p>I might have sprung a surprise on you in the code block above. But the main difference here is we’re reaching out to some external API (think fetching data from a database) and sending the response back to the client.</p>
<p>Another critical point is to notice how the specific id is retrieved from <code>ctx.params.id</code>, where <code>ctx</code> represents the context object.</p>
<p>If we make a GET request to <code>api/products/astro-book-001</code>, we should have some data returned to the client.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-25-at-08.57.00@2x.png" alt="Testing the product API on hopscotch.io" width="600" height="400" loading="lazy">
<em>Testing the product API on hopscotch.io</em></p>
<p>Note how whatever “id” is passed in the request path is rightly retrieved, for example <code>astro-book-001</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-25-at-08.49.31@2x.png" alt="The product ID returned in the JSON response." width="600" height="400" loading="lazy">
<em>The product ID returned in the JSON response.</em></p>
<p>To re-iterate, we can get the path segments in the dynamic route pattern via <code>context.params</code> and voilà! We have our use case resolved.</p>
<p>Passing query parameters to <code>GET</code> requests is not unheard of in the real world. Heck, it’s quite an everyday use case in fact.</p>
<p>Assuming the following client request <code>GET api/products/astro-book-001?version=2&amp;publishedDate=2023-06-12</code>, how would we handle this?</p>
<p>It’s important to note that <code>version</code> and <code>publishedDate</code> will not be present in <code>context.params</code>. But we can grab these from the <code>URL</code> object as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 pages/api/products/[id]</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-keyword">const</span> productId = ctx.params.id;

  <span class="hljs-comment">// retrieve relevant search parameters, aka URL query parameters</span>
  <span class="hljs-keyword">const</span> searchParams = ctx.url.searchParams;
  <span class="hljs-keyword">const</span> version = searchParams.get(<span class="hljs-string">"version"</span>);
  <span class="hljs-keyword">const</span> publishedDate = searchParams.get(<span class="hljs-string">"publishedDate"</span>);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://fakestoreapi.com/products/1"</span>);
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-comment">// Return a new response with the retrieved </span>
    <span class="hljs-comment">// "version" and "publishedDate"</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
      <span class="hljs-built_in">JSON</span>.stringify({ 
        ...data, 
        version, 
        publishedDate, 
        <span class="hljs-attr">id</span>: productId 
       }),
      {
        <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
      }
    );
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
      <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span> }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">500</span>,
    });
  }
};
</code></pre>
<p>The crux of the solution is the following:</p>
<pre><code class="lang-js"> <span class="hljs-comment">// retrieve relevant search parameters, aka URL query parameters</span>
  <span class="hljs-keyword">const</span> searchParams = ctx.url.searchParams;
  <span class="hljs-keyword">const</span> version = searchParams.get(<span class="hljs-string">"version"</span>);
  <span class="hljs-keyword">const</span> publishedDate = searchParams.get(<span class="hljs-string">"publishedDate"</span>);
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-25-at-09.13.04@2x.png" alt="Retrieving query parameters in a server endpoint." width="600" height="400" loading="lazy">
<em>Retrieving query parameters in a server endpoint.</em></p>
<h3 id="heading-dedicated-api-directory">Dedicated API directory</h3>
<p>At the time of writing, API routes must live in the <code>pages</code> directory with appropriate file endings, for example <code>.ts</code> or <code>.js</code>.</p>
<p>For example, you can have <code>pages/anyFileName.js</code> act as a server endpoint.</p>
<p>But I find it easier (and better) to have my server API routes in a dedicated <code>pages/api</code> directory instead of mixing these in other page routes.</p>
<p>One advantage to this is potentially making it easier to redirect a subdomain to a single path for all API routes, for example redirect <code>api.my-website.com/...</code> to <code>my-website.com/api/...</code>.</p>
<p>On the flip side, an arguable downside is we break the collocation of other routes, for example standard pages such as <code>pages/products/...</code> will have their associated API route in <code>api/products/...</code>. This is a downside and a trade-off I happily make in production applications.</p>
<h3 id="heading-how-to-support-other-http-methods">How to support other HTTP methods</h3>
<p>All our examples so far have used the get method within our API routes. But Astro does support all the other HTTP methods, such as post or delete.</p>
<p>Consider the following example that extends our <code>api/products/${id}</code> endpoint to include more methods:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-comment">// Handle client GET requests </span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> get: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-keyword">const</span> productId = ctx.params.id;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// fetch remote resource </span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://fakestoreapi.com/products/1"</span>);
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-comment">// return data, and the id param</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
      ...data, 
      <span class="hljs-attr">id</span>: productId 
    }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
      <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span> }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">500</span>,
    });
  }
};

<span class="hljs-comment">/**
 * Handle "DELETE" requests
 * "delete" is a reserved word in Javascript. Hence, the function name "del"
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> del: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-keyword">const</span> productId = ctx.params.id;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://fakestoreapi.com/products/1"</span>, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"DELETE"</span>,
    });
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
      <span class="hljs-built_in">JSON</span>.stringify({ 
        <span class="hljs-attr">id</span>: productId, 
        <span class="hljs-attr">message</span>: <span class="hljs-string">"deleted"</span>, 
        <span class="hljs-attr">title</span>: data.title }),
      {
        <span class="hljs-attr">status</span>: <span class="hljs-number">202</span>,
      }
    );
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
      <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span> }), {
      <span class="hljs-attr">status</span>: <span class="hljs-number">500</span>,
    });
  }
};

<span class="hljs-comment">/**
 * Handle "POST" requests
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> post: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-comment">// Get the POST body data</span>
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> ctx.request.json();

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify({ 
    <span class="hljs-attr">message</span>: <span class="hljs-string">"Created"</span>, data 
  }));
};
</code></pre>
<p>Go ahead and give these a try!</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-25-at-08.53.33@2x.png" alt="Making a POST request to our server endpoint." width="600" height="400" loading="lazy">
<em>Making a POST request to our server endpoint.</em></p>
<p>As a fallback to handle other HTTP methods, we can provide an <code>all</code> function to match methods that don’t have a corresponding exported function. Consider the example below:</p>
<pre><code class="lang-js">... 
export <span class="hljs-keyword">const</span> all: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-comment">// Get the request method</span>
  <span class="hljs-keyword">const</span> method = ctx.request.method;

  <span class="hljs-comment">// Return a response</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
    <span class="hljs-built_in">JSON</span>.stringify({
      method,
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Unsupported HTTP method"</span>,
    }),
    {
      <span class="hljs-attr">status</span>: <span class="hljs-number">501</span>, <span class="hljs-comment">// unsupported</span>
    }
  );
};
</code></pre>
<p>This will match unhandled methods in our implementation, such as <code>PATCH</code> requests.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-25-at-08.56.25@2x.png" alt="Handling unsupported methods in a server endpoint." width="600" height="400" loading="lazy">
<em>Handling unsupported methods in a server endpoint.</em></p>
<h2 id="heading-streams-oh-streams">Streams, Oh Streams</h2>
<p>I’ve chosen a playful title for this section as it involves a relatively lesser-known feature of Astro: server streaming.</p>
<h3 id="heading-what-is-server-streaming">What is server streaming?</h3>
<p>Generally speaking, SSR refers to generating HTML on the server and sending that to a browser in response to a request.</p>
<p>In theory, we may break this off into distinct steps:</p>
<ul>
<li>Browser requests a page</li>
<li>The server renders the page (and every associated data)</li>
<li>The server returns the <strong>fully formed page</strong> to the browser</li>
<li>The browser renders the page</li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/send_full_page.png" alt="Server sending a fully formed page to the client." width="600" height="400" loading="lazy">
<em>Server sending a fully formed page to the client.</em></p>
<p>What’s important here is to note that the server generates the page’s full HTML, and only then does it send the HTML to the browser.</p>
<p>Now, consider a different approach.</p>
<p>In most cases, certain parts of the HTML page are static and could be sent from the server immediately, that is without relying on fetching all the relevant data.</p>
<p>What if the server could transmit the <code>HTML</code> to the browser as it creates the page server side?</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/server_send_chunks.png" alt="The server sends partial chunks to the browser." width="600" height="400" loading="lazy">
<em>The server sends partial chunks to the browser.</em></p>
<p>This is the crux of streaming: stream HTML to a browser as the server generates the HTML.</p>
<h3 id="heading-why-should-we-bother">Why should we bother?</h3>
<p>In theory, browsers can render partial HTML and support receiving and rendering HTML data in chunks. Users can view and interact with a page as it streams rather than waiting for the full page to be sent as one big chunk.</p>
<p>Different applications will need various workarounds. But streaming improves server overhead. The server doesn’t need as much memory to buffer entire pages. It’ll incrementally send page data to the browser releasing memory to handle more requests and consequently save overhead costs. </p>
<p>This is a great argument to convince your boss that streaming is good for the company’s wallets (except if your company plays the silly game of <em>burning as much cash as possible</em>).</p>
<h3 id="heading-streaming-is-easy-yet-difficult">Streaming is easy yet difficult</h3>
<p>I’ve sung the praises of streaming. It is conceptually easy to reason about. But in practice you may experience some difficult use cases.</p>
<p>A great example is considering the <code>&lt;title&gt;</code> of a page that goes in our HTML’s <code>&lt;head&gt;</code>. Typically, the <code>&lt;head&gt;</code> is one of the first elements we stream to the browser. But some elements within the <code>&lt;head&gt;</code> could very well be dynamic, for example we may have a <code>&lt;title&gt;</code> in the form <code>&lt;title&gt;{product name} fetched from the server&lt;title&gt;</code>.</p>
<p>What’s likely to happen is we stream a stale <code>&lt;title&gt;</code> before we eventually get the product name from the database (assuming the database is the external source of data here).</p>
<p>This out-of-order streaming represents some of the most common issues we may face in practice. In this example, we may provide a generic <code>&lt;title&gt;</code> placeholder and continue streaming.</p>
<p>Once the data becomes available server-side, we may stream a tiny <code>&lt;script&gt;</code> that updates the page title to the desired value.</p>
<p>Okay, that’s enough backstory. Next, let’s dig into streaming in Astro.</p>
<h3 id="heading-server-streaming-in-astro">Server streaming in Astro</h3>
<p>Now that you’re convinced (not confused) about the importance of server streaming, let’s explore how streaming in Astro works.</p>
<p>Perhaps the most important thing to know is that Astro supports streaming by default. Yes, you heard that right. Browsers also natively support HTML streaming.</p>
<p>Essentially, within the Astro template, Astro will stream out HTML that occurs before hitting an async boundary.</p>
<p>For example, consider the basic page with a <code>&lt;LoadPets/&gt;</code> component responsible for fetching and rendering some pet data from a database.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> LoadPets <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/LoadPets.astro'</span>
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> 
   <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span> Petsssss! <span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span> 
 <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is a pet site<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span> Consider how pets are awesome ... <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">LoadPets</span> /&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>In this contrived example, Astro will steam out the <code>&lt;head&gt;</code>, <code>&lt;h1&gt;</code> and <code>&lt;p&gt;</code> sections to the browser before stopping to fetch the data in <code>&lt;LoadPets /&gt;</code> and then stream its result to the browser when ready.</p>
<p>Let’s explore a visual example.</p>
<p>Update the <code>ssr</code> project to have a new <code>streaming.astro</code> page with the following content:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> Block <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Block.astro"</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Streaming<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #1"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{1000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #2"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{2000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #3"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{3000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #4"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{4000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #5"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{5000}</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>The <code>&lt;Block/&gt;</code> component receives a <code>text</code> and a <code>delay</code> prop. <code>delay</code> represents how long to wait before rendering its template, that is simulating some network request call.</p>
<p>Here’s the <code>&lt;Block/&gt;</code> component:</p>
<pre><code class="lang-js">{<span class="hljs-comment">/** 📂 src/components/Block.astro **/</span>}
---
<span class="hljs-keyword">import</span> { sleep } <span class="hljs-keyword">from</span> <span class="hljs-string">"../sleep"</span>;

interface Props {
  <span class="hljs-attr">text</span>: string;
  delay: number;
}

<span class="hljs-keyword">const</span> { text, delay } = Astro.props;

<span class="hljs-keyword">await</span> sleep(delay);
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
  {text}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">div</span> {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">2rem</span> <span class="hljs-number">6rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
    <span class="hljs-attribute">background-color</span>: blanchedalmond;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span>
</code></pre>
<p>Where <code>sleep</code> is a utility as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/sleep.ts </span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> sleep = <span class="hljs-function">(<span class="hljs-params">delay: number</span>) =&gt;</span>
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(r, delay));
</code></pre>
<p>Now, go to the Chrome browser and visit the <code>/streaming</code> route to view the wonders of streaming.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-26-at-11.47.56.png" alt="Initial block streamed while awaiting Block #2" width="600" height="400" loading="lazy">
<em>Initial block streamed while awaiting Block #2</em></p>
<p>Each block of content comes in one at a time.</p>
<p>It’s important to note that we don’t have to abstract the async bits into components. Streaming equally works with standard promises within the Astro template:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/streaming_blocks</span>
---
<span class="hljs-keyword">import</span> Block <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Block.astro"</span>;
<span class="hljs-keyword">import</span> { sleep } <span class="hljs-keyword">from</span> <span class="hljs-string">"../sleep"</span>;

<span class="hljs-keyword">const</span> block5Promise = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"Block #5"</span>;
};
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Streaming<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #1"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{1000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #2"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{2000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #3"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{3000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Block</span> <span class="hljs-attr">text</span>=<span class="hljs-string">"Block #4"</span> <span class="hljs-attr">delay</span>=<span class="hljs-string">{4000}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{block5Promise}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>An important fact to note here is that Astro initiates the async fetches in parallel when sibling async components are in the component tree.</p>
<p>So in our example, <code>Block #1</code> through <code>Block #5</code> start fetching data in parallel and don’t block one another.</p>
<p>When <code>Block #4</code> is rendered, <code>block5Promise</code> is already fetched as it takes one second compared to <code>Block #4</code>’s four seconds. So the result of <code>block5</code> is streamed alongside <code>Block #4</code>.</p>
<p>This can be difficult to grasp via text descriptions, so here's a visual:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-25-at-13.44.47@2x.png" alt="Describing the parallelized rendering of each block." width="600" height="400" loading="lazy">
<em>Describing the parallelized rendering of each block.</em></p>
<p>Give this a look in your Chrome browser.</p>
<h3 id="heading-how-to-take-advantage-of-streaming">How to take advantage of streaming</h3>
<p>Since Astro supports streaming by default, understanding and applying it is the first step to taking advantage of streaming.</p>
<p>Consider the following example:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { sleep } <span class="hljs-keyword">from</span> <span class="hljs-string">"../sleep"</span>;

<span class="hljs-keyword">const</span> getSomeData = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"some data "</span>;
};

<span class="hljs-keyword">const</span> getSomeOtherData = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">200</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"another data"</span>;
};

<span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getSomeData();
<span class="hljs-keyword">const</span> otherData = <span class="hljs-keyword">await</span> getSomeOtherData();
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Product<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A name<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A fact<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{otherData}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>In the example above, we presumably need to fetch two resources, <code>data</code> and <code>otherData</code>. But our solution blocks streaming. We wait for <code>await getSomeData()</code> and <code>await getSomeOtherData()</code> before sending the full page to the browser.</p>
<p>If we wanted to take advantage of server streaming, we could either render the promises directly within the markup:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> { sleep } <span class="hljs-keyword">from</span> <span class="hljs-string">"../sleep"</span>;

<span class="hljs-keyword">const</span> getSomeData = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"some data "</span>;
};

<span class="hljs-keyword">const</span> getSomeOtherData = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> sleep(<span class="hljs-number">200</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-string">"another data"</span>;
};
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Product<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A name<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{getSomeData}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A fact<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{getSomeOtherData}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Or extract the data fetching to child components:</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">import</span> Data <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/Data.astro'</span>
<span class="hljs-keyword">import</span> OtherData <span class="hljs-keyword">from</span> <span class="hljs-string">'../components/OtherData.astro'</span>
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Product<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A name<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Handle fetch of data in &lt;Data /&gt; --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Data</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>A fact<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Handle other data fetch in &lt;OtherData /&gt; --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">OtherData</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Excellent!</p>
<h2 id="heading-wrapping-up-this-chapter-5">Wrapping Up This Chapter</h2>
<p>Server-side rendering is powerful and opens up many opportunities in our application. But with much power comes responsibility. </p>
<p>So, before considering making every page in your application server-rendered, consider the pros and cons (as discussed in Chapter 3). Then, make the right decision for your application — that’s where true responsibility lies. And do not forget to leverage hybrid rendering where possible.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-139.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter seven.</em></p>
<h1 id="heading-chapter-7-be-audible-how-to-build-a-fullstack-astro-project">Chapter 7: Be Audible! (How to Build a Fullstack Astro Project)</h1>
<blockquote>
<p>… People will believe what they see. Let them see. ― Henry David Thoreau</p>
</blockquote>
<p>In this chapter, I’ll ask you to see beyond static apps and build a full stack application with Astro. To view the complete application, see the <a target="_blank" href="https://github.com/understanding-astro/react.dev-astrohttps://github.com/understanding-astro/fullstack-astro">GitHub repo</a>. </p>
<h2 id="heading-what-youll-learn-6">What You’ll Learn</h2>
<ul>
<li>The ability to add authentication to an Astro application.</li>
<li>An understanding of setting up a backend for an Astro application.</li>
<li>A working knowledge of handling form submissions without dedicated API routes.</li>
<li>Hands-on experience uploading and retrieving data in an Astro application.</li>
<li>An understanding of the kind of apps you can build with Astro.</li>
</ul>
<h2 id="heading-project-setup">Project Setup</h2>
<p>We’ve seen how to build static sites with Astro. So, to make this section laser-focused on scripting and Astro features, I’ve set up a static site for us to work on here.</p>
<p>The site has been stripped of any relevant functionality. We will build those step-by-step together.</p>
<p>Start by cloning the project:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/understanding-astro/fullstack-astro
</code></pre>
<p>Change directories:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> fullstack-astro
</code></pre>
<p>You should be on the <code>clean-slate</code> branch by default. Otherwise, check out to <code>clean-slate</code>.</p>
<p>Next, install dependencies and start the application:</p>
<pre><code class="lang-bash">npm install &amp;&amp; npm run start
</code></pre>
<p>The application should successfully run on one of the local server ports.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-11.59.17@2x.png" alt="The BeAudible app initialised. " width="600" height="400" loading="lazy">
<em>The BeAudible app initialised.</em></p>
<h2 id="heading-project-overview-1">Project Overview</h2>
<p>Our application is for a hypothetical startup, BeAudible. Its mission is to discover the voices of the world.</p>
<p>In technical terms, BeAudible lets authorised users create audio recordings, upload them to their servers, and have a timeline where people can listen to everyone’s recordings.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/beaudible-overview.png" alt="An overview of the BeAudible application." width="600" height="400" loading="lazy">
<em>An overview of the BeAudible application.</em></p>
<p>The project we just cloned will receive and upload a user’s recording and eventually display every recording on a shared timeline.</p>
<p>Let’s explore the pages in the project.</p>
<h3 id="heading-the-homepage">The homepage</h3>
<p>Firstly, consider the homepage, that is the base route <code>/</code>.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-11.59.17@2x-1.png" alt="The sections of the BeAudible application." width="600" height="400" loading="lazy">
<em>The sections of the BeAudible application.</em></p>
<ol>
<li>The navigation bar holds a feedback form for users to send their thoughts.</li>
<li>The navigation bar includes a record link to navigate to a dedicated page for recording a user’s audio.</li>
<li>The navigation bar contains a sign-out button. By implication, the homepage should be protected, that is only authenticated users should land here.</li>
<li>Finally, in the centre of the page lies the timeline that should list all users’ recordings.</li>
</ol>
<h3 id="heading-the-record-page">The record page</h3>
<p>If you click “Record” from the navigation bar, you will be navigated to the <code>/record</code> route where a user can record their audio.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-12.24.30.png" alt="The record page." width="600" height="400" loading="lazy">
<em>The record page.</em></p>
<p>A React component hydrated in the Astro application powers the recording user interface element.</p>
<h3 id="heading-the-signup-page">The signup page</h3>
<p>Now, go to the <code>/signup</code> route.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-12.22.45.png" alt="The sign up page." width="600" height="400" loading="lazy">
<em>The sign-up page.</em></p>
<p>This is the page to sign up users to BeAudible.</p>
<h3 id="heading-the-sign-in-page">The sign-in page</h3>
<p>Finally, visit the <code>/signin</code> route.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-12.21.59.png" alt="The signin page." width="600" height="400" loading="lazy">
<em>The sign-in page.</em></p>
<p>This is the page for previously authenticated users to log in to the application.</p>
<p>Go ahead and kill the running application from the terminal. Then, we’ll continue with some setup.</p>
<h3 id="heading-helper-components-and-utilities">Helper components and utilities</h3>
<p>To ensure our focus remains on Astro, I created UI components and stored them in the <code>src/components</code> folder.</p>
<p>We will import and use these components to develop our solution as we proceed.</p>
<p>Similarly, constants have been stored in <code>src/constants</code> and utility scripts in <code>src/scripts</code>. We aim to concentrate on the critical objective of this chapter, which is to build a full stack application with Astro.</p>
<h2 id="heading-technology-choices">Technology Choices</h2>
<ol>
<li><strong>Firebase</strong> as a backend service: we can choose any backend service with Astro, but we’ll use Firebase for simplicity. The principles we’ll discuss work with any other preferred service. We will leverage Firebase’s authentication and cloud storage services.</li>
<li><strong>Tailwind</strong> for styling: Tailwind is famous for styling applications. Instead of writing the styles manually, the project uses Tailwind.</li>
<li><strong>Astro</strong> as the primary web framework: Of course, the web framework of choice for our application is Astro. No questions asked! But we will also leverage React components for islands of interactivity.</li>
</ol>
<h2 id="heading-backend-setup">Backend Setup</h2>
<p>Let’s point our attention to setting up our backend server. Remember, we will use Firebase as our backend service.</p>
<p>Go to the <a target="_blank" href="https://www.freecodecamp.org/news/p/3c1efa5a-f575-4365-9958-d220b339bc38/[https://firebase.google.com/]">Firebase homepage</a> and visit the Firebase console.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.35.06@2x.png" alt="The Firebase homepage. " width="600" height="400" loading="lazy">
<em>The Firebase homepage.</em></p>
<p>The process is much smoother if you have (and are signed in to) a Google account (for example, Gmail).</p>
<p>Next, create a new Firebase project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.36.54@2x.png" alt="Creating a new Firebase project." width="600" height="400" loading="lazy">
<em>Creating a new Firebase project.</em></p>
<p>Name the project <code>BeAudible</code> and choose whether to use Google Analytics in the project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.41.10@2x.png" alt="Choosing Google analytics and creating the project." width="600" height="400" loading="lazy">
<em>Choosing Google analytics and creating the project.</em></p>
<p>After successfully creating the project, add a web application to the Firebase project.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.51.18@2x.png" alt="Adding a web application to the Firebase project. " width="600" height="400" loading="lazy">
<em>Adding a web application to the Firebase project.</em></p>
<p>Now, continue the web app set-up process by choosing a name (preferably the same as before), setting up Firebase hosting, and registering the web application.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.53.46@2x.png" alt="Continuing the application set-up." width="600" height="400" loading="lazy">
<em>Continuing the application set-up.</em></p>
<p>The next step is critical.</p>
<p><strong>Copy your web app’s Firebase configuration</strong>. We’ll use that to initialise the Firebase application client side.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.59.41@2x.png" alt="Copying the Firebase configuration for the client SDK." width="600" height="400" loading="lazy">
<em>Copying the Firebase configuration for the client SDK.</em></p>
<p>The next steps are optional. Follow the guided prompt from Firebase and continue to the Firebase console.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-13.02.36@2x.png" alt="Following the guided prompt from Firebase." width="600" height="400" loading="lazy">
<em>Following the guided prompt from Firebase.</em></p>
<p>Upon completion, we’ll be redirected to the Firebase application dashboard.</p>
<p>Go to the project settings, find the service account section, and generate a new private key that we’ll leverage in our server application.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-27-at-11.26.30.png" alt="Project overview > Project settings " width="600" height="400" loading="lazy">
<em>Project overview &gt; Project settings</em></p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-27-at-11.28.49.png" alt="Generating a new private key. " width="600" height="400" loading="lazy">
<em>Generating a new private key.</em></p>
<p>This will download a JSON file to your machine. Keep it secure as it provides access to Firebase’s service. We will leverage this to access Firebase’s server resources from our application server.</p>
<h2 id="heading-how-to-handle-authentication">How to Handle Authentication</h2>
<p>Generally speaking, authentication is serious business and can take different forms.</p>
<p>Firebase provides an authentication service, so we will leverage its client libraries to authenticate the user client-side.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/simple-auth-flow.png" alt="Simplified authentication process." width="600" height="400" loading="lazy">
<em>Simplified authentication process.</em></p>
<p>The client authentication will communicate with Firebase’s servers, but later on, we will look at verifying a user’s authentication token (JWT) on our server.</p>
<p>First, set up the Firebase application to receive client authentication requests.</p>
<p>Return to the Firebase console and set up authentication.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.13.50@2x.png" alt="Select authentication from the list of provided services." width="600" height="400" loading="lazy">
<em>Select authentication from the list of provided services.</em></p>
<p>Firebase provides different sign-in methods. Let’s keep this simple. Enable the Email and password method from the Firebase console.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.15.36@2x.png" alt="Selecting the email / password sign-in method." width="600" height="400" loading="lazy">
<em>Selecting the email / password sign-in method.</em></p>
<p>Make sure to enable the option and hit save.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.16.33@2x.png" alt="Enabling and saving the Email / Password sign-in method." width="600" height="400" loading="lazy">
<em>Enabling and saving the Email / Password sign-in method.</em></p>
<h3 id="heading-how-to-initialise-firebase-on-the-client">How to initialise Firebase on the client</h3>
<p><code>src/scripts/firebase/init.ts</code> contains the initialisation script for our client application.</p>
<p>The code responsible for initialising the application is shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
<span class="hljs-comment">// 📂 src/scripts/firebase/init.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> app = initializeApp(firebaseConfig);
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> auth = getAuth(app);
</code></pre>
<p>The script exports the initialised application via <code>app</code> and the authentication client module via <code>auth</code> where <code>initializeApp</code> and <code>getAuth</code> are methods imported from the Firebase SDK.</p>
<p>We must now replace the <code>firebaseConfig</code> variable with the object copied while initialising the Firebase application.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-12.59.41@2x-1.png" alt="The firebase client configuration. " width="600" height="400" loading="lazy">
<em>The firebase client configuration.</em></p>
<p>Once this is done, we should have the Firebase client rightly initialised.</p>
<h3 id="heading-how-to-use-the-firebase-emulators">How to use the Firebase emulators</h3>
<p>Talking to the production firebase services while testing and developing locally is rather silly.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/talk-to-prod-firebase.png" alt="Sending requests to the production Firebase servers while developing locally. " width="600" height="400" loading="lazy">
<em>Sending requests to the production Firebase servers while developing locally.</em></p>
<p>Instead, we can use the Firebase Emulator Suite while developing locally. The emulator suite will intercept our Firebase service requests and provide a testing ground locally without hitting the production services.</p>
<p>I’ve set up the project to use the Firebase emulators. So let’s get it running.</p>
<p>Make sure you have the Firebase CLI tools installed. If you don’t, install the CLI via the following command:</p>
<pre><code class="lang-bash">npm install -g firebase-tools
</code></pre>
<p>Assuming you have the application running in one tab of your terminal, open another tab and run the firebase <code>emulators</code> script to start the firebase emulators:</p>
<pre><code class="lang-bash">npm run emulators
</code></pre>
<p>This will start the authentication and storage emulators with a user interface running on <code>localhost:4001</code>. We can view the development data in the emulator user interface, for example application user signups and uploaded recordings.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-15.06.19.png" alt="Starting the Firebase emulators. " width="600" height="400" loading="lazy">
<em>Starting the Firebase emulators.</em></p>
<h3 id="heading-how-to-handle-user-signups">How to handle user signups</h3>
<p>So, how are we going to handle user signups?</p>
<p>Please consider the overall flow diagram below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/sign-up-flow.png" alt="The signup flow." width="600" height="400" loading="lazy">
<em>The signup flow.</em></p>
<ul>
<li>The flow kicks off with the user submitting the signup form.</li>
<li>Then check if the submitted email and password are valid.</li>
<li>If the form values are invalid, display an error.</li>
<li>Create a new user via the <code>createUserWithEmailAndPassword</code> method of the Firebase auth module.</li>
<li>If the new user creation fails, display an error.</li>
<li>Otherwise, our new user is now in a signed-in state.</li>
<li>Grab the user auth token (this is called ID token in Firebase lingo and represents a JSON Web Token (JWT)).</li>
<li>Redirect the user to the homepage with the token as a URL parameter, that is <code>/?token=${USER_AUTH_TOKEN}</code>.</li>
</ul>
<p>Before delving into the code for how to do this, I’d like to point out that the project has module aliasing set up to prevent pesky relative imports, for example:</p>
<pre><code class="lang-js"><span class="hljs-comment">// This ... </span>
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../firebase/init"</span>

<span class="hljs-comment">// Becomes this ...</span>
<span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/init"</span>;
</code></pre>
<p>This is achieved by updating the <code>tsconfig.json</code> file to include the alias:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 tsconfig.json</span>
{
   <span class="hljs-comment">// ...</span>
    <span class="hljs-string">"baseUrl"</span>: <span class="hljs-string">"."</span>,
    <span class="hljs-string">"paths"</span>: {
      <span class="hljs-string">"@components/*"</span>: [<span class="hljs-string">"src/components/*"</span>],
      <span class="hljs-string">"@layouts/*"</span>: [<span class="hljs-string">"src/layouts/*"</span>],
      <span class="hljs-string">"@scripts/*"</span>: [<span class="hljs-string">"src/scripts/*"</span>],
      <span class="hljs-string">"@stores/*"</span>: [<span class="hljs-string">"src/stores/*"</span>],
      <span class="hljs-string">"@constants/*"</span>: [<span class="hljs-string">"src/constants/*"</span>]
    }
  }
}
</code></pre>
<p>We will reference existing modules in the project via the relevant module alias.</p>
<p>Now, here is the annotated code for handling the user sign-up:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/signup.astro --&gt;</span> 
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-comment">// import the Validator from the tiny "validator.tool" library </span>
  <span class="hljs-keyword">import</span> Validator <span class="hljs-keyword">from</span> <span class="hljs-string">"validator.tool"</span>;
  <span class="hljs-keyword">import</span> { createUserWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
  <span class="hljs-comment">// Import the auth module from `src/scripts` </span>
  <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/init"</span>;
  <span class="hljs-comment">// Import basic form validation rules </span>
  <span class="hljs-keyword">import</span> { authClientValidationRules } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/authClientValidationRules"</span>;

 <span class="hljs-comment">// Type alias for the form values </span>
  type FormValues = {
    email?: string;
    password?: string;
  };

  <span class="hljs-comment">// Grab the submit button element </span>
  <span class="hljs-keyword">const</span> submitButton = <span class="hljs-built_in">document</span>.getElementById(
    <span class="hljs-string">"submit-signup-form"</span>
  ) <span class="hljs-keyword">as</span> HTMLButtonElement | <span class="hljs-literal">null</span>;

  <span class="hljs-comment">// Grab the form element </span>
  <span class="hljs-keyword">const</span> form = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"signup-form"</span>) <span class="hljs-keyword">as</span> HTMLFormElement | <span class="hljs-literal">null</span>;

   <span class="hljs-comment">// Initialise the validator </span>
  <span class="hljs-keyword">const</span> validator = <span class="hljs-keyword">new</span> Validator({
    form,
    <span class="hljs-comment">// Pass in basic rules already existing in the project</span>
    <span class="hljs-attr">rules</span>: authClientValidationRules,
  });


  <span class="hljs-keyword">if</span> (validator.form) {
    <span class="hljs-comment">// Attach a submit event handler on the form</span>
    validator.form.onsubmit = <span class="hljs-keyword">async</span> (evt) =&gt; {
      evt.preventDefault();

      <span class="hljs-keyword">const</span> errors = validator.errorMessages;
      <span class="hljs-keyword">const</span> values = validator.getValues() <span class="hljs-keyword">as</span> FormValues;

      <span class="hljs-comment">//Check for errors </span>
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Object</span>.keys(errors).length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> errorMessages = <span class="hljs-built_in">Object</span>.values(errors).join(<span class="hljs-string">"...and..."</span>);
        <span class="hljs-keyword">return</span> alert(errorMessages);
      }

      <span class="hljs-keyword">const</span> { email, password } = values <span class="hljs-keyword">as</span> Required&lt;FormValues&gt;;

      <span class="hljs-keyword">if</span> (!submitButton) {
        <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Missing form button"</span>);
      }

      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Show submitting state </span>
        submitButton.innerText = <span class="hljs-string">"Submitting"</span>;
        submitButton.disabled = <span class="hljs-literal">true</span>;

        <span class="hljs-comment">// Create the new user </span>
        <span class="hljs-keyword">const</span> { user } = <span class="hljs-keyword">await</span> createUserWithEmailAndPassword(
          auth,
          email,
          password
        );

         <span class="hljs-comment">// redirect the user to the homepage with their token</span>
        <span class="hljs-keyword">const</span> token = <span class="hljs-keyword">await</span> user.getIdToken();
        <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`/?token=<span class="hljs-subst">${token}</span>`</span>;
      } <span class="hljs-keyword">catch</span> (error) {
        submitButton.innerText = <span class="hljs-string">"Signup"</span>;
        submitButton.disabled = <span class="hljs-literal">false</span>;

        alert(error);
      }
    };
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>In the solution above, we’re handling form validation via <a target="_blank" href="https://github.com/jaywcjlove/validator.js">validator.js</a> but could have used any other library. Another minimal framework agnostic library that makes a good choice is <a target="_blank" href="https://github.com/pablo-abc/felte">Felte</a>.</p>
<h3 id="heading-how-to-handle-user-sign-in">How to handle user sign-in</h3>
<p>With user sign-up handled, the process for user sign-in is the same except for one change. Instead of calling the <code>createUserWithEmailAndPassword</code> method, we’ll use the <code>signInWithEmailAndPassword</code> Firebase auth method.</p>
<p>Notice how the flow is identical in the code below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/signin.astro --&gt;</span>
<span class="hljs-comment">&lt;!-- ... --&gt;</span> 

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { signInWithEmailAndPassword } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase/auth"</span>;
  <span class="hljs-keyword">import</span> Validator <span class="hljs-keyword">from</span> <span class="hljs-string">"validator.tool"</span>;
  <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/init"</span>;
  <span class="hljs-keyword">import</span> { authClientValidationRules } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/authClientValidationRules"</span>;

  type FormValues = {
    email?: string;
    password?: string;
  };

  <span class="hljs-keyword">const</span> form = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"signin-form"</span>) <span class="hljs-keyword">as</span> HTMLFormElement | <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">const</span> submitButton = <span class="hljs-built_in">document</span>.querySelector(
    <span class="hljs-string">"#signin-form button[type='submit']"</span>
  ) <span class="hljs-keyword">as</span> HTMLButtonElement | <span class="hljs-literal">null</span>;

  <span class="hljs-keyword">const</span> validator = <span class="hljs-keyword">new</span> Validator({
    form,
    <span class="hljs-attr">rules</span>: authClientValidationRules,
  });

  <span class="hljs-keyword">if</span> (validator.form) {
    validator.form.onsubmit = <span class="hljs-keyword">async</span> (evt) =&gt; {
      evt.preventDefault();

      <span class="hljs-keyword">const</span> errors = validator.errorMessages;
      <span class="hljs-keyword">const</span> values = validator.getValues() <span class="hljs-keyword">as</span> FormValues;

      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Object</span>.keys(errors).length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">const</span> errorMessages = <span class="hljs-built_in">Object</span>.values(errors).join(<span class="hljs-string">"...and..."</span>);
        <span class="hljs-keyword">return</span> alert(errorMessages);
      }

      <span class="hljs-keyword">const</span> { email, password } = values <span class="hljs-keyword">as</span> Required&lt;FormValues&gt;;

      <span class="hljs-keyword">if</span> (!submitButton) {
        <span class="hljs-keyword">return</span> alert(<span class="hljs-string">"Missing form button"</span>);
      }

      <span class="hljs-keyword">try</span> {
        submitButton.innerText = <span class="hljs-string">"Submitting"</span>;
        submitButton.disabled = <span class="hljs-literal">true</span>;

        <span class="hljs-keyword">const</span> { user } = <span class="hljs-keyword">await</span> signInWithEmailAndPassword(
          auth,
          email,
          password
        );

        <span class="hljs-keyword">const</span> token = <span class="hljs-keyword">await</span> user.getIdToken();
        <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`/?token=<span class="hljs-subst">${token}</span>`</span>;
      } <span class="hljs-keyword">catch</span> (error) {
        submitButton.innerText = <span class="hljs-string">"Signin"</span>;
        submitButton.disabled = <span class="hljs-literal">false</span>;

        alert(error);
      }
    };
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>With these in place, we’ve got authentication handled!</p>
<p>But a question that may remain in your heart is, why exactly are we sending the user token in the homepage redirect URL?</p>
<h2 id="heading-how-to-implement-protected-pages">How to Implement Protected Pages</h2>
<p>Every page in our application is statically generated except for <code>index.astro</code>, that is the homepage.</p>
<p>The homepage is server-side rendered because we want to ensure it’s protected, and that only authenticated users ever land here.</p>
<p>We will discuss how we’ll achieve this, but first we need to write some code that runs on the server here.</p>
<h3 id="heading-how-to-initialise-firebase-on-the-server">How to initialise Firebase on the server</h3>
<p>During the project initialisation, we downloaded a private key for server access. This is a JSON file in the form:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">type</span>: <span class="hljs-string">"..."</span>,
  <span class="hljs-attr">project_id</span>: <span class="hljs-string">"..."</span>
   <span class="hljs-comment">// more properties </span>
}
</code></pre>
<p>We need these values to initialise our server application. So, create a <code>.env</code> file to store these secrets. Then, we’ll break up the JSON keys into individual environment variables as shown below:</p>
<pre><code class="lang-js">
FIREBASE_PRIVATE_KEY_ID=<span class="hljs-string">"..."</span>
FIREBASE_PRIVATE_KEY=<span class="hljs-string">"..."</span>
FIREBASE_PROJECT_ID=<span class="hljs-string">"..."</span>
FIREBASE_CLIENT_EMAIL=<span class="hljs-string">"..."</span>
FIREBASE_CLIENT_ID=<span class="hljs-string">"..."</span>
FIREBASE_AUTH_URI=<span class="hljs-string">"..."</span>
FIREBASE_TOKEN_URI=<span class="hljs-string">"..."</span>
FIREBASE_AUTH_PROVIDER_CERT_URL=<span class="hljs-string">"..."</span>
FIREBASE_CLIENT_CERT_URL=<span class="hljs-string">"..."</span>
</code></pre>
<p>Save the <code>env</code> file. Without this, we won’t be able to access the application resources from our server.</p>
<p>✨ Fun fact: As discussed in Chapter 5, we’re providing TypeScript support for these environment values in <code>.env.d.ts</code>.</p>
<h3 id="heading-how-to-protect-the-home-page-route">How to protect the home page route</h3>
<p>Once a user has successfully signed in, Firebase generates a unique ID token that serves as their unique identifier and provides access to various resources, such as Firebase Cloud Storage.</p>
<p>I have loosely referred to this as auth tokens. We will use this ID token to recognise the user on our server.</p>
<p>✨ Fun fact: Firebase ID tokens are short-lived and last for an hour.</p>
<p>Consider the flow below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/protected-route-flow.png" alt="The protected route flow. " width="600" height="400" loading="lazy">
<em>The protected route flow.</em></p>
<ul>
<li>The flow kicks off with the user landing on the homepage.</li>
</ul>
<p>Note that the following steps are performed on the server, that is within the frontmatter section of our server-side rendered page.</p>
<ul>
<li>Then, retrieve the user ID token from the URL (first-time user) or the request cookies (returning user).</li>
<li>Verify the validity of the token. We will use the Firebase server SDK (Firebase admin) to check this.</li>
<li>If the token is invalid or doesn’t exist, the user is unauthorised. Redirect them to the <code>/signin</code> page.</li>
<li>If the token is valid, set the <code>token</code> as a cookie.</li>
</ul>
<p>✨Fun fact: by setting the token via cookies, we can remove the token from the URL and refresh without losing the user signed-in state. Every request will send back the cookie to the server, where we can recheck its validity.</p>
<p>Now, here’s the implementation:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>
---
<span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> { serverApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/initServer"</span>;
<span class="hljs-keyword">import</span> { getAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase-admin/auth"</span>;
<span class="hljs-keyword">import</span> { TOKEN } <span class="hljs-keyword">from</span> <span class="hljs-string">"@constants/cookies"</span>;

<span class="hljs-comment">// Get client token from the URL param</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(Astro.request.url);
<span class="hljs-keyword">const</span> urlTokenParam = url.searchParams.get(<span class="hljs-string">"token"</span>);

<span class="hljs-comment">// Get token from cookies </span>
<span class="hljs-keyword">const</span> cookieToken = Astro.cookies.get(TOKEN);
<span class="hljs-keyword">const</span> token = urlTokenParam || cookieToken.value;

<span class="hljs-keyword">if</span> (!token) {
  <span class="hljs-comment">// Unauthorised user. Redirect to sign in</span>
  <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/signin"</span>);
}

<span class="hljs-keyword">const</span> auth = getAuth(serverApp);

<span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// verify the auth token</span>
  <span class="hljs-keyword">await</span> auth.verifyIdToken(token);

  <span class="hljs-comment">// set token cookie </span>
  <span class="hljs-comment">// Note that the "TOKEN" constant refers to the string "X-Token."</span>
  Astro.cookies.set(TOKEN, token, {
    <span class="hljs-attr">path</span>: <span class="hljs-string">"/"</span>,
    <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>,
  });
} <span class="hljs-keyword">catch</span> (error) {
  <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Could not decode token"</span>, {
    <span class="hljs-attr">fromCookie</span>: !!cookieToken.value,
    <span class="hljs-attr">fromUrl</span>: !!urlTokenParam,
  });

  <span class="hljs-comment">// Error occurred, e.g., invalid token. Redirect to sign in</span>
  <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/signin"</span>);
}
---
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-15.41.52.png" alt="The token cookie set in the browser response." width="600" height="400" loading="lazy">
<em>The token cookie set in the browser response.</em></p>
<h3 id="heading-how-to-update-the-redirect-url">How to update the redirect URL</h3>
<p>When a user successfully signs in, the user looks something like <code>localhost:3000/?token=${some-long-string}</code>.</p>
<p>After performing our token validation on the server and returning the protected <code>HTML</code> page, we may update the URL to remove the <code>token</code> parameter.</p>
<pre><code class="lang-js"><span class="hljs-comment">// Before </span>
<span class="hljs-attr">localhost</span>:<span class="hljs-number">3000</span>/?token=${some-long-string}

<span class="hljs-comment">// After </span>
<span class="hljs-attr">localhost</span>:<span class="hljs-number">3000</span>
</code></pre>
<p>This is not necessary, but a nice UX touch.</p>
<p>Since we want to do this on the client, our go-to solution is to add a client <code>&lt;script&gt;</code> to the page.</p>
<p>Consider the solution below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/index.astro --&gt;</span> 
<span class="hljs-comment">&lt;!-- ... --&gt;</span> 

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-comment">// Enhancement: remove the token from the URL after the page's parsed.</span>
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-built_in">window</span>.location.href);
  <span class="hljs-keyword">const</span> urlTokenParam = url.searchParams.get(<span class="hljs-string">"token"</span>);

  <span class="hljs-keyword">if</span> (urlTokenParam) {
    <span class="hljs-comment">// delete the token param from the URL</span>
    url.searchParams.delete(<span class="hljs-string">"token"</span>);

   <span class="hljs-comment">// update history without a refresh with the new URL</span>
    <span class="hljs-built_in">window</span>.history.pushState({}, <span class="hljs-string">""</span>, url.href);
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>The solution is arguably easy to reason about, with the crux after getting the search parameter being <code>window.history.pushState(...).</code></p>
<h3 id="heading-how-to-log-out-a-user-from-the-protected-page">How to log out a user from the protected page</h3>
<p>The top left section of the application’s navigation bar includes a sign-out button. When a user clicks this, we will sign them out of the application.</p>
<p>To sign out a user, we will use the Firebase client SDK to log a user out of the device.</p>
<p>But remember that the protected index page checks the <code>token</code> request cookie value.</p>
<p>When we sign out a user using the Firebase client SDK, the issued client <code>token</code> remains valid for up to an hour (depending on when it was issued).</p>
<p>So, consider the flow for our solution below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/sign-out-flow.png" alt="The user sign out flow." width="600" height="400" loading="lazy">
<em>The user sign out flow (Click sign-out button, make a request to API endpoint, log out the user, re-ditrect user to sign-in page)</em></p>
<p>Let’s start our implementation by updating the client application to handle the click event on the sign-out button and initiate our flow as shown below:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- 📂 src/pages/layouts/BaseLayout.astro --&gt;</span> 
<span class="hljs-comment">&lt;!-- ... --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> { auth } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/init"</span>;

   <span class="hljs-comment">// Grab the sign-out button </span>
  <span class="hljs-keyword">const</span> signoutButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"sign-out-button"</span>) <span class="hljs-keyword">as</span>
    | HTMLButtonElement
    | <span class="hljs-literal">undefined</span>;

  <span class="hljs-keyword">if</span> (signoutButton) {
    <span class="hljs-comment">// Add a click event listener on the button</span>
    signoutButton.addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Disable the button while we log the user out</span>
        signoutButton.disabled = <span class="hljs-literal">true</span>;
        <span class="hljs-comment">// Change button text to read "Signing out ..."</span>
        signoutButton.innerText = <span class="hljs-string">"Signing out ..."</span>;
        <span class="hljs-comment">// Invalidate server http cookie</span>
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/auth/signout"</span>, {
          <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
        });

        <span class="hljs-keyword">if</span> (!response.ok) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"server signout failed"</span>);
        }
        <span class="hljs-comment">/**
         * sign the user out via the signOut method
        * on the Firebase auth module 
        */</span>    
        <span class="hljs-keyword">await</span> auth.signOut ();
        <span class="hljs-comment">// Redirect to the signin page </span>
        <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">"/signin"</span>;
      } <span class="hljs-keyword">catch</span> (error) {
        signoutButton.disabled = <span class="hljs-literal">false</span>;
        alert(error);
      }
    });
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>We’re making a request to <code>/api/auth/signout</code>, but the API route does not exist.</p>
<p>Let’s change that with the following code:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/api/auth/signout.ts</span>
<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">import</span> { TOKEN } <span class="hljs-keyword">from</span> <span class="hljs-string">"@constants/cookies"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> post: APIRoute = <span class="hljs-function">(<span class="hljs-params">ctx</span>) =&gt;</span> {
  ctx.cookies.delete(TOKEN, {
    <span class="hljs-attr">path</span>: <span class="hljs-string">"/"</span>,
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">message</span>: <span class="hljs-string">"successfully signed out"</span> }),
  };
};
</code></pre>
<p>After successful sign-out, attempt to visit the protected page <code>localhost:3000</code>, and you’ll be automatically redirected to <code>/sign</code>.</p>
<p>We’re now cooking with gas! 🔥</p>
<h2 id="heading-cloud-storage-setup">Cloud Storage Setup</h2>
<p>We’ve got a big part of our application functioning — largely the authentication and keeping the index page protected. But we’re protecting an empty page at the moment. So users cannot record or view other users’ recordings.</p>
<p>Let’s fix this by setting up cloud storage to save user recordings on the server.</p>
<p>Go to the Firebase console and click “See all build features” to find the cloud storage service.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.33.40@2x.png" alt="Viewing all build features on the Firebase console." width="600" height="400" loading="lazy">
<em>Viewing all build features on the Firebase console.</em></p>
<p>Next, select the Storage service.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.33.58@2x.png" alt="Selecting the storage service. " width="600" height="400" loading="lazy">
<em>Selecting the storage service.</em></p>
<p>Then begin the setup.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.34.29@2x.png" alt="Clicking get started on the Storage service page." width="600" height="400" loading="lazy">
<em>Clicking get started on the Storage service page.</em></p>
<p>Keep the storage rules as-is:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.34.40@2x.png" alt="The default storage rules. " width="600" height="400" loading="lazy">
<em>The default storage rules.</em></p>
<p>Then select a server location.</p>
<p>BeAudible is a hypothetical US startup, so I’ll choose a US location here.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-26-at-15.35.33@2x.png" alt="Selecting a Storage location." width="600" height="400" loading="lazy">
<em>Selecting a Storage location.</em></p>
<p>Once the setup is complete, visit the Storage page and copy the bucket name in the form <code>gs://{name-of-project}.appspot.com.</code></p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-27-at-16.43.07.png" alt="The Storage bucket name." width="600" height="400" loading="lazy">
<em>The Storage bucket name.</em></p>
<p>Excellent!</p>
<p>When we upload and get the user recordings, we’ll need this to connect to the storage servers.</p>
<h2 id="heading-how-to-upload-audio-recordings">How to Upload Audio Recordings</h2>
<p>The recorder user interface is powered by a React Recorder component hydrated via the <code>client:load</code> directive.</p>
<pre><code class="lang-js">&lt;Recorder client:load&gt;
   ...
&lt;/Recorder&gt;
</code></pre>
<p>Open the <code>Recorder</code> component and consider the <code>onAudioDownload</code> callback.</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/AudioRecorder.tsx</span>
<span class="hljs-comment">// ... </span>
&lt;VoiceRecorder
   onAudioDownload={<span class="hljs-function">(<span class="hljs-params">blob: Blob</span>) =&gt;</span> {
   <span class="hljs-comment">// 👀 upload recording</span>
   }}
/&gt;
</code></pre>
<p>After a user completes the recording, this callback will be invoked. Our first task is to go ahead and upload the audio blob to the server.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/upload-flow.png" alt="Sending audio blob to a server endpoint." width="600" height="400" loading="lazy">
<em>Sending audio blob to a server endpoint.</em></p>
<h3 id="heading-how-to-handle-uploads-via-an-api-route">How to handle uploads via an API route</h3>
<p>Let’s go ahead and create the API endpoint that’ll receive the audio blob from the client.</p>
<p>Consider the flow for our solution below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/save-audio-recording-flow.png" alt="The save recording endpoint flow diagram." width="600" height="400" loading="lazy">
<em>The save recording endpoint flow diagram (Endpoint receives post rquest. Is token valid? If yes, convert audio blob to buffer, save file to storage with unique name, and return success response. If not, return error response.</em></p>
<p>Now, here’s the annotated code:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/api/recording.ts</span>
<span class="hljs-comment">// ... </span>
<span class="hljs-keyword">import</span> type { APIRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-comment">// nanoid will be used to generate unique IDs</span>
<span class="hljs-keyword">import</span> { nanoid } <span class="hljs-keyword">from</span> <span class="hljs-string">"nanoid"</span>;
<span class="hljs-keyword">import</span> { TOKEN } <span class="hljs-keyword">from</span> <span class="hljs-string">"@constants/cookies"</span>;
<span class="hljs-keyword">import</span> { getAuth } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase-admin/auth"</span>;
<span class="hljs-keyword">import</span> { BUCKET_NAME } <span class="hljs-keyword">from</span> <span class="hljs-string">"@constants/firebase"</span>;
<span class="hljs-keyword">import</span> { getStorage } <span class="hljs-keyword">from</span> <span class="hljs-string">"firebase-admin/storage"</span>;
<span class="hljs-keyword">import</span> { serverApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"@scripts/firebase/initServer"</span>;

<span class="hljs-comment">// get firebase server auth module </span>
<span class="hljs-keyword">const</span> auth = getAuth(serverApp);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> post: APIRoute = <span class="hljs-keyword">async</span> (ctx) =&gt; {
  <span class="hljs-comment">// Create an error response </span>
  <span class="hljs-keyword">const</span> authUserError = <span class="hljs-keyword">new</span> Response(<span class="hljs-string">"Unauthenticated user"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">401</span> });

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Get token cookie </span>
    <span class="hljs-keyword">const</span> authToken = ctx.cookies.get(TOKEN).value;

    <span class="hljs-comment">// not present, return error response </span>
    <span class="hljs-keyword">if</span> (!authToken) {
      <span class="hljs-keyword">return</span> authUserError;
    }

    <span class="hljs-comment">// verify the user token</span>
    <span class="hljs-keyword">await</span> auth.verifyIdToken(authToken);    
  } <span class="hljs-keyword">catch</span> (error) {
   <span class="hljs-comment">/**
     * Return error response, e.g., 
      * if the token verification fails
     */</span>
    <span class="hljs-keyword">return</span> authUserError;
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Get the audio blob from the client request</span>
    <span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">await</span> ctx.request.blob();

    <span class="hljs-comment">// Get access to the firebase storage </span>
    <span class="hljs-keyword">const</span> storage = getStorage(serverApp);
    <span class="hljs-keyword">const</span> bucket = storage.bucket(BUCKET_NAME);

    <span class="hljs-comment">// convert Blob to native Node Buffer for server storage</span>
    <span class="hljs-keyword">const</span> buffer = Buffer.from(<span class="hljs-keyword">await</span> blob.arrayBuffer());
    <span class="hljs-keyword">const</span> file = bucket.file(<span class="hljs-string">`recording-<span class="hljs-subst">${nanoid()}</span>.wav`</span>);

    <span class="hljs-comment">// save to firebase storage </span>
    <span class="hljs-keyword">await</span> file.save(buffer);

    <span class="hljs-comment">// return a successful response</span>
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Recording uploaded"</span>,
      }),
    };
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-string">"Something went horribly wrong"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">500</span> });
  }
};
<span class="hljs-comment">// ...</span>
</code></pre>
<h3 id="heading-how-to-upload-recordings-from-the-client">How to upload recordings from the client</h3>
<p>Now that we’ve got the API endpoint ready to receive client requests, let’s go ahead and upload the user recordings from the client.</p>
<p>Instead of clogging our user interface components with the upload logic, I find it more maintainable to move such business logic away from the UI components and, in our case, have this collocated with the application state managed via <code>nanastores</code>.</p>
<p>Remember <code>nanostores</code>?</p>
<p>We’ll use <a target="_blank" href="https://github.com/nanostores/nanostores">nano stores</a> for state management. The <code>~1kb</code> library is simple and efficient for our use case.</p>
<p>Create a new <code>audioRecording.ts</code> file to handle our recording state and also be responsible for exposing a <code>uploadRecording</code> method.</p>
<p>Consider the implementation below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/stores/audioRecording.ts</span>
<span class="hljs-keyword">import</span> { atom } <span class="hljs-keyword">from</span> <span class="hljs-string">"nanostores"</span>;

<span class="hljs-comment">/**
 * Deterministic state representation
 */</span>
type Store =
  | {
      <span class="hljs-attr">blob</span>: <span class="hljs-literal">null</span>;
      status: <span class="hljs-string">"idle"</span>;
    }
  | {
      <span class="hljs-attr">blob</span>: Blob;
      status: <span class="hljs-string">"uploading"</span> | <span class="hljs-string">"completed"</span> | <span class="hljs-string">"failed"</span>;
    };

<span class="hljs-comment">/**
 * Optional naming convention: $[name_of_store]
 * instead of [name_of_store]Store
 *, i.e., $audioRecording instead of audioRecordingStore
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> $audioRecording = atom&lt;Store&gt;({
  <span class="hljs-comment">// Initialise the atom with the default state </span>
  <span class="hljs-attr">blob</span>: <span class="hljs-literal">null</span>,
  <span class="hljs-attr">status</span>: <span class="hljs-string">"idle"</span>,
});

<span class="hljs-comment">/**
 * upload audio recording action
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> uploadRecording = <span class="hljs-keyword">async</span> (blob: Blob) =&gt; {
  <span class="hljs-comment">// Update $audioRecording state to "uploading."</span>
  $audioRecording.set({
    <span class="hljs-attr">status</span>: <span class="hljs-string">"uploading"</span>,
    blob,
  });

  <span class="hljs-keyword">try</span> {
   <span class="hljs-comment">// POST request to our recording endpoint </span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/recording"</span>, {
      <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
      <span class="hljs-attr">body</span>: blob, <span class="hljs-comment">// pass blob as the request body </span>
    });

    <span class="hljs-keyword">if</span> (response.ok) {
     <span class="hljs-comment">// Successful? Update state to "completed."</span>
      $audioRecording.set({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"completed"</span>,
        blob,
      });
    } <span class="hljs-keyword">else</span> {
     <span class="hljs-comment">// Request failed. Update state to "failed."</span>
      $audioRecording.set({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"failed"</span>,
        blob,
      });
    }
  } <span class="hljs-keyword">catch</span> (error) {
    $audioRecording.set({
      <span class="hljs-attr">status</span>: <span class="hljs-string">"failed"</span>,
      blob,
    });
  } <span class="hljs-keyword">finally</span> {
    <span class="hljs-comment">// after 't' revert state to idle again</span>
    <span class="hljs-keyword">const</span> timeout = <span class="hljs-number">3000</span>;
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      $audioRecording.set({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"idle"</span>,
        <span class="hljs-attr">blob</span>: <span class="hljs-literal">null</span>,
      });
    }, timeout);
  }
};
</code></pre>
<p>Our UI state is well-represented, and the upload action is defined. But this will only take effect when used in the UI component.</p>
<h3 id="heading-how-to-react-to-ui-changes-in-framework-components">How to react to UI changes in framework components</h3>
<p>We will now update the <code>AudioRecorder</code> UI component to react to the state in the <code>$audioRecording</code> store as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/components/AudioRecorder.tsx</span>

<span class="hljs-comment">/**
* The useStore hook will help with the React 
* component rerenders. In simple terms, it'll hook into the 
* store and react upon any change.
*/</span>
<span class="hljs-keyword">import</span> { useStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"@nanostores/react"</span>;
<span class="hljs-keyword">import</span> { VoiceRecorder } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-voice-recorder-player"</span>;
<span class="hljs-comment">// Import the store and the upload recording action</span>
<span class="hljs-keyword">import</span> { $audioRecording, uploadRecording } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stores/audioRecording"</span>;

type Props = {
  cta?: string;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Recorder = <span class="hljs-function">(<span class="hljs-params">props: Props</span>) =&gt;</span> {
  <span class="hljs-comment">// Get the current application state from the store </span>
  <span class="hljs-keyword">const</span> state = useStore($audioRecording);

  <span class="hljs-comment">// React deterministically based on the status of the store</span>
  <span class="hljs-keyword">switch</span> (state.status) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"idle"</span>:
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">VoiceRecorder</span>
               // 👀 <span class="hljs-attr">Invoke</span> <span class="hljs-attr">uploadRecording</span> <span class="hljs-attr">after</span> <span class="hljs-attr">a</span> <span class="hljs-attr">user</span> <span class="hljs-attr">completes</span> <span class="hljs-attr">the</span> <span class="hljs-attr">recording</span> 
            <span class="hljs-attr">onAudioDownload</span>=<span class="hljs-string">{(blob:</span> <span class="hljs-attr">Blob</span>) =&gt;</span> uploadRecording(blob)}
          /&gt;

          {props.cta}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
<span class="hljs-comment">/** 
 Show relevant UI during the uploading state. 
**/</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"uploading"</span>:
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center w-56 h-56 border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-800 dark:border-gray-700"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"px-3 py-1 text-xs font-medium leading-none text-center text-blue-800 bg-blue-200 rounded-full animate-pulse dark:bg-blue-900 dark:text-blue-200"</span>&gt;</span>
            Uploading ...
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
<span class="hljs-comment">/** 
 Show relevant UI during the failed state. 
**/</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"failed"</span>:
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-red-400 rounded-md py-6 px-3 text-slate-100 motion-safe:animate-bounce"</span>&gt;</span>
          An error occurred uploading your recording
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
<span class="hljs-comment">/** 
 Show relevant UI during the completed state. 
**/</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">"completed"</span>:
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-green-400 rounded-md py-6 px-3 text-slate-100 motion-safe:animate-bounce"</span>&gt;</span>
          Successfully published your recording!
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
      );
<span class="hljs-comment">/** 
 Typescript exhaustive checking
 @see https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
**/</span>

    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">const</span> _exhaustiveCheck: never = state;
      <span class="hljs-keyword">return</span> _exhaustiveCheck;
  }
};
</code></pre>
<p>Now, a user should be able to record in the browser, and we will go ahead and save the recording on our backend.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-19.15.22@2x.png" alt="Viewing saved recordings in the Firebase emulator." width="600" height="400" loading="lazy">
<em>Viewing saved recordings in the Firebase emulator.</em></p>
<h2 id="heading-how-to-fetch-data-from-the-server">How to Fetch Data from the Server</h2>
<p>We’re correctly saving user recordings, but at the moment they can’t be viewed on the homepage.</p>
<p>Let’s resolve that.</p>
<p>Our solution is to fetch the recordings on the server and send the rendered HTML page to the client.</p>
<p>Here’s the code solution:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/pages/index.astro</span>

---
<span class="hljs-keyword">import</span> { BUCKET_NAME } <span class="hljs-keyword">from</span> <span class="hljs-string">"@constants/firebase"</span>;
<span class="hljs-keyword">import</span> LinkCTA <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/LinkCTA.astro"</span>;
<span class="hljs-keyword">import</span> AudioPlayer <span class="hljs-keyword">from</span> <span class="hljs-string">"@components/AudioPlayer.astro"</span>;
<span class="hljs-comment">// ...</span>

<span class="hljs-comment">// Represent the recordings with the "Audible" type alias</span>
type Audible = { <span class="hljs-attr">url</span>: string; timeCreated: string };

<span class="hljs-comment">// audibles will hold the list of "Audibles."</span>
<span class="hljs-keyword">let</span> audibles: Audible[] = [];
<span class="hljs-keyword">const</span> storage = getStorage(serverApp);


<span class="hljs-keyword">try</span> {
   <span class="hljs-comment">/**
     *  After verifying the user auth token 
       * and setting the token cookie ...
    */</span> 
    <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// get all recordings in the storage bucket</span>
    <span class="hljs-keyword">const</span> bucket = storage.bucket(BUCKET_NAME);
    <span class="hljs-keyword">const</span> [files] = <span class="hljs-keyword">await</span> bucket.getFiles();

    audibles = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(
      files.map(<span class="hljs-keyword">async</span> (file) =&gt; {
        <span class="hljs-keyword">const</span> [metadata] = <span class="hljs-keyword">await</span> file.getMetadata();

        <span class="hljs-comment">// return the url and timeCreated metadata</span>
        <span class="hljs-keyword">return</span> {
          <span class="hljs-attr">url</span>: file.publicUrl(),
          <span class="hljs-attr">timeCreated</span>: metadata.timeCreated,
        };
      })
    );
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching audibles"</span>);
    <span class="hljs-keyword">return</span> Astro.redirect(<span class="hljs-string">"/signin"</span>);
  }
}

<span class="hljs-comment">//...</span>
---
</code></pre>
<p>Now update the component template section to render the “audibles”. We’ll leverage the <code>AudioPlayer</code> component, passing it the audible <code>url</code> and the <code>timeCreated</code> metadata.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex flex-col items-center"</span>&gt;</span>
    {
      audibles.length === 0 ? (
        <span class="hljs-tag">&lt;&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Empty</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">LinkCTA</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/record"</span>&gt;</span>Record<span class="hljs-tag">&lt;/<span class="hljs-name">LinkCTA</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span>
      ) : (
        audibles
          .sort((a, b) =&gt;
            new Date(a.timeCreated) <span class="hljs-tag">&lt; <span class="hljs-attr">new</span> <span class="hljs-attr">Date</span>(<span class="hljs-attr">b.timeCreated</span>) ? <span class="hljs-attr">1</span> <span class="hljs-attr">:</span> <span class="hljs-attr">-1</span>
          )
          <span class="hljs-attr">.map</span>((<span class="hljs-attr">audible</span>) =&gt;</span> (
            <span class="hljs-tag">&lt;<span class="hljs-name">AudioPlayer</span> <span class="hljs-attr">url</span>=<span class="hljs-string">{audible.url}</span> <span class="hljs-attr">timeCreated</span>=<span class="hljs-string">{audible.timeCreated}</span> /&gt;</span>
          ))
      )
    }
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>In the code above, we display an <code>Empty</code> user interface empty if there are no audibles. Otherwise, we render a sorted list of audibles.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-19.06.31@2x.png" alt="Rendering the sorted list of audio recordings." width="600" height="400" loading="lazy">
<em>Rendering the sorted list of audio recordings.</em></p>
<h2 id="heading-how-to-submit-html-forms">How to Submit HTML forms</h2>
<p>The final puzzle in our application is handling the submission of the feedback form.</p>
<p>I’ve included this feature to show an example of handling a form within the same server-side rendered page, that is without creating an API endpoint to handle the form request.</p>
<p>Take a look at the form element and notice how its method attribute is set to <code>POST</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/layouts/BaseLayout.astro</span>
<span class="hljs-comment">// ... </span>
&lt;form <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"mx-auto flex"</span> method=<span class="hljs-string">"POST"</span>&gt;
...
&lt;/form&gt;
</code></pre>
<p>By default, the browser will send a POST request to the server when this form is submitted, which we can capture and react upon.</p>
<p>In the frontmatter section of the <code>index.astro</code> page, we can add a condition to handle the feedback form requests as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ... </span>
<span class="hljs-keyword">if</span> (Astro.request.method === <span class="hljs-string">"POST"</span>) {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// Get the form data </span>
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> Astro.request.formData();
    <span class="hljs-comment">/**
    * Get the feedback value. 
    * Corresponds to the form input element value of the name, "feedback".
    */</span>
    <span class="hljs-keyword">const</span> feedback = data.get(<span class="hljs-string">"feedback"</span>);

    <span class="hljs-comment">// Do something with the data</span>
    <span class="hljs-built_in">console</span>.log({ feedback });

    <span class="hljs-comment">// Do something with the data</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) {
      <span class="hljs-built_in">console</span>.error(error.message);
    }
  }
}
<span class="hljs-comment">// ...</span>
</code></pre>
<p>I’m keeping this simple by just logging the feedback on the server. But we could save this value to a database in the real world. The crux here is receiving the form values, as shown above.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-05-29-at-19.14.07@2x.png" alt="The logged feedback data. " width="600" height="400" loading="lazy">
<em>The logged feedback data.</em></p>
<h2 id="heading-wrapping-up-this-chapter-6">Wrapping Up This Chapter</h2>
<p>Astro is great for building content-focused websites such as blogs, landing pages, and so on. But, we can do much more with it.</p>
<p>Suppose you can build the application as a multi-page application (MPA), that is not a single-page application, and can leverage islands of interactivity (component islands). In that case, you can build it with Astro.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-140.png" alt="Image" width="600" height="400" loading="lazy">
<em>Chapter eight.</em></p>
<h1 id="heading-chapter-8-build-your-own-astro-integrations-1">Chapter 8: Build Your Own  Astro Integrations</h1>
<p>At the end of this chapter, you’ll join the order of mages who wield great power to bend Astro to their will with new functionality and behaviour.</p>
<h2 id="heading-what-youll-learn-7">What You’ll Learn</h2>
<ul>
<li>The relationship between Astro and the Vite module bundler</li>
<li>The different types of integrations available in Astro</li>
<li>Build your first Astro integration</li>
<li>Understand the Astro hooks lifecycle</li>
<li>Deepen your knowledge of building custom Astro feature integrations</li>
</ul>
<h2 id="heading-astro-and-vite">Astro and Vite</h2>
<p>Before we dive into the beautiful world of Astro integrations, we need to know who’s powering the Astro build ship - and that’s <a target="_blank" href="https://vitejs.dev/">Vite</a>, the build tool all about speed, efficiency and flexibility. </p>
<p>Think of Vite as our trusty co-pilot, helping us bundle our web pages and creating a lightning-fast development environment.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/astro-vite-relationship.png" alt="The Astro Vite relationship." width="600" height="400" loading="lazy">
<em>The Astro Vite relationship.</em></p>
<p>To build the custom integrations we’re dreaming of, we may need to go beyond Astro and venture deep into Vite territory, for example customising the build step with Vite plugins.</p>
<p>Now, I know this might not be very clear, especially when we start talking about Vite in the upcoming sections of this chapter. But fear not - you now know why Vite is essential to the build process, and I’ll explain with examples in the coming sections of this chapter.</p>
<h2 id="heading-what-are-astro-integrations">What are Astro Integrations?</h2>
<p>By definition, Astro integrations extend Astro with new functionality and behaviour within your project.</p>
<p>We’ll find ourselves building three types of Astro integrations, namely:</p>
<ol>
<li><strong>Renderers</strong>: these integrations enable a framework component’s rendering (typically server-side rendering and client-side hydration). Examples of this include the official React, Preact, and Vue Astro integrations.</li>
<li><strong>Libraries</strong>: these integrations enable external library support within Astro. Examples of this include the official Tailwind and Partytown integrations.</li>
<li><strong>Features</strong>: these are integrations that extend the behaviour of Astro in a specific way, usually to support a user-defined feature set. Examples include the official <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/sitemap/">sitemap</a> integration that generates a sitemap when you build your Astro project.</li>
</ol>
<p>For most people, the majority of integrations you build will be to support a particular feature, that is feature integrations. This will be the sole focus of this chapter. Once you have sufficient knowledge of building feature integrations, you can transfer the knowledge to library or renderer integrations.</p>
<p>Let’s get started with a contrived Astro integration.</p>
<h2 id="heading-hello-world-sorry-hello-integration">Hello World. Sorry – Hello, Integration</h2>
<p>Let’s get you acquainted with a basic hello world Astro integration. Even though we will be wielding swords and slaying dragons soon, before that you must get introduced to the tools of the trade.</p>
<h3 id="heading-project-objective">Project objective</h3>
<p>The goal for our first Astro integration is arguably straightforward: we will write a custom Astro integration that automatically logs a hello world message to the browser console on every application page.</p>
<p>Have you got it?</p>
<p>I heard a yes!</p>
<h3 id="heading-your-first-custom-integration">Your first custom integration</h3>
<p>We will approach this solution by injecting a script on every application page.</p>
<p>How?</p>
<p>Where?</p>
<p>When?</p>
<p>Hold your horses, mate!</p>
<p>Start by beginning a new Astro project with the familiar command:</p>
<pre><code class="lang-js">npm create astro@latest hello-astro-integration
</code></pre>
<p>Now that you’re a pro at this, name the project whatever you like, for example <code>hello-astro-integration</code>, and use a minimal (empty) template.</p>
<p>Open the application directory and head over to the <code>astro.config.mjs</code> file.</p>
<p>The <code>astro.config.mjs</code> file includes configuration options for our Astro project. This is where we define integrations for our project, that is this is where the magic happens.</p>
<p>At the moment, our <code>astro.config.mjs</code> file should be in the default empty state, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({});
</code></pre>
<p>Let’s change that by adding an empty <code>integrations</code> list to the configuration:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [], <span class="hljs-comment">// 👀 look here </span>
});
</code></pre>
<p>In a nutshell, an Astro integration is represented by an object with <code>name</code> and <code>hooks</code> properties, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-comment">// 👀 look here </span>
  <span class="hljs-attr">integrations</span>: [
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-hello"</span>,
      <span class="hljs-attr">hooks</span>: {},
    },
  ],
});
</code></pre>
<p>In the code block above, we’ve outlined the object in the <code>integrations</code> array.</p>
<p>The name of the integration is <code>astro-hello</code>. We’ll discuss hooks in the coming section, but it represents extendable “hook” points within the Astro build lifecycle process.</p>
<p>For example, let’s leverage the first hook in the lifecycle process called <code>astro:config:setup</code>.</p>
<p>This hook is the starting point for the entire build lifecycle. It is triggered on initialisation before Astro has resolved the project configuration. It’s the perfect place to inject scripts onto a new page or extend the project configuration before it’s resolved.</p>
<p>Let’s take advantage of that by passing it into the hooks object and pointing it to a function invoked when the hook is triggered.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-hello"</span>,
      <span class="hljs-attr">hooks</span>: {
        <span class="hljs-comment">// 👀 hook: callbackFn</span>
        <span class="hljs-string">"astro:config:setup"</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {},
      },
    },
  ],
});
</code></pre>
<p>Note the <code>options</code> parameter in the hook callback. It is an object with the following type definition:</p>
<pre><code class="lang-js">{  
  <span class="hljs-attr">config</span>: AstroConfig;
  command: <span class="hljs-string">'dev'</span> | <span class="hljs-string">'build'</span>;
  isRestart: boolean;
  updateConfig: <span class="hljs-function">(<span class="hljs-params">newConfig: Record&lt;string, any&gt;</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
  addRenderer: <span class="hljs-function">(<span class="hljs-params">renderer: AstroRenderer</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
  addWatchFile: <span class="hljs-function">(<span class="hljs-params">path: URL | string</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
  injectScript: <span class="hljs-function">(<span class="hljs-params">stage: InjectedScriptStage, content: string</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
  injectRoute: <span class="hljs-function">(<span class="hljs-params">{ pattern: string, entryPoint: string }</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
}
</code></pre>
<p>Luckily it contains the <code>injectScript</code> method we’re interested in:</p>
<pre><code class="lang-js">  injectScript: <span class="hljs-function">(<span class="hljs-params">stage: InjectedScriptStage, content: string</span>) =&gt;</span> <span class="hljs-keyword">void</span>;
</code></pre>
<p><code>stage</code> denotes how the script <code>content</code> should be injected into the page, and there are four possible values : <code>head-inline</code>, <code>before-hydration</code>, <code>page</code>, and <code>page-ssr</code>.</p>
<p>The <code>page</code> option will bundle and inject the script with other <code>&lt;script&gt;</code> tags defined in any Astro components on the page. The final output will eventually load this with a <code>&lt;script type="module&gt;</code>.</p>
<p>When I started tinkering with the integrations API, I tried silly things to get <code>injectScript</code> to work. I can confidently tell you these won’t work:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 👀 Error: Failed to parse source for import analysis</span>
<span class="hljs-comment">// because the content contains invalid JS syntax.</span>
injectScript(<span class="hljs-string">"page"</span>, <span class="hljs-string">"console.log('Hello World')"</span>)

<span class="hljs-keyword">const</span> log = <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"me"</span>);
<span class="hljs-comment">// 👀 Uncaught ReferenceError: log is not defined</span>
options.injectScript(<span class="hljs-string">"page"</span>, <span class="hljs-string">"log()"</span>);
</code></pre>
<p>This saves you the futility I experienced until I looked in the Astro source code.</p>
<p>The <code>content</code> string parameter in <code>injectScript</code> refers to an import path. This is as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [
    {
      <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-hello"</span>,
      <span class="hljs-attr">hooks</span>: {
        <span class="hljs-string">"astro:config:setup"</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
          <span class="hljs-comment">//  👀 "page" option with an import path</span>
          options.injectScript(<span class="hljs-string">"page"</span>, <span class="hljs-string">`import '/src/scripts/
  globalLog.js'`</span>);
        },
      },
    },
  ],
});
</code></pre>
<p>Since we’re passing an import path to the script, let’s ensure the script exists.</p>
<p>Create a new script with the following content in <code>src/scripts/globalLog.js</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/scripts/globalLog.js</span>
<span class="hljs-keyword">const</span> logger = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> msg = <span class="hljs-string">"Hello Integrations"</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`%c <span class="hljs-subst">${msg}</span>`</span>, <span class="hljs-string">"background: black;  color: yellow"</span>);
};

logger();
</code></pre>
<p>The <code>logger</code> method calls the <code>console.log</code> method with a <code>Hello integrations</code> string while adding some colour to the message.</p>
<p>And voilà!</p>
<p>We have our first integration running as expected.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-06-at-05.29.53.png" alt="Working integration log printed in the browser console" width="600" height="400" loading="lazy">
<em>Working integration log printed in the browser console</em></p>
<p>We may create more pages, and the console message will be logged on every page in the application.</p>
<h3 id="heading-how-to-print-a-message-to-the-server-console">How to print a message to the server console</h3>
<p>Since we have hook points into the Astro build process, it is also possible to output logs to the server console.</p>
<p>This may be useful for usability or ascertaining that our custom integration works as expected.</p>
<p>At the moment, here’s the mess that my server logs look like:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-06-at-05.33.46.png" alt="The (messy) Astro server logs" width="600" height="400" loading="lazy">
<em>The (messy) Astro server logs</em></p>
<p>Yours should look familiar. This is from the incremental process of building our first integration.</p>
<p>Let’s go ahead and print something to the logs once we’ve successfully injected our script onto the page.</p>
<pre><code class="lang-js"><span class="hljs-comment">// ... </span>

<span class="hljs-attr">hooks</span>: {
    <span class="hljs-string">"astro:config:setup"</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
      options.injectScript(<span class="hljs-string">"page"</span>, <span class="hljs-string">`import '/src/scripts/ 
    globalLog.js'`</span>);

     <span class="hljs-comment">// 👀 add a new log </span>
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Injected hello integration script"</span>);
    },
},
</code></pre>
<p>Restart the server for a clean slate, and we should have the log printed as shown below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-06-at-05.38.59.png" alt="The server log from our hello world integration" width="600" height="400" loading="lazy">
<em>The server log from our hello world integration</em></p>
<p>Since we’re fancy developers who care about usability, let’s go ahead and make the log feel native to other Astro logs by adding some text formatting and colour via <code>kleur</code>.</p>
<p>Install the <code>kelur</code> package:</p>
<pre><code class="lang-js">npm install kleur
</code></pre>
<p>Once the installation is complete, we should now have a new log in the dev server that reads:</p>
<pre><code class="lang-js"><span class="hljs-number">05</span>:<span class="hljs-number">41</span>:<span class="hljs-number">02</span> AM [astro] update /package-lock.json
</code></pre>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-06-at-05.41.12.png" alt="Example native astro server log" width="600" height="400" loading="lazy">
<em>Example native astro server log</em></p>
<p><code>05:41:02</code> represents my current time.</p>
<p>Please do not ask me why I’m writing this chapter so early in the morning.</p>
<p>Let’s go ahead and make our log look similar. Instead of just using <code>console.log</code>, let’s introduce a <code>logServerMessage</code> that does our beautiful bidding as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs </span>

<span class="hljs-keyword">import</span> kleur <span class="hljs-keyword">from</span> <span class="hljs-string">"kleur"</span>;
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;

<span class="hljs-comment">// 👀 The Intl.DateTimeFormat object enables language-sensitive </span>
<span class="hljs-comment">// date and time formatting.</span>
<span class="hljs-keyword">const</span> dateTimeFormat = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat([], {
  <span class="hljs-attr">hour</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">minute</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">second</span>: <span class="hljs-string">"2-digit"</span>,
});

<span class="hljs-keyword">const</span> logServerMessage = <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  <span class="hljs-comment">// 👀 Get a new date string using the dateTimeFormat object</span>
  <span class="hljs-keyword">const</span> date = dateTimeFormat.format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());

  <span class="hljs-comment">// log to console with kleur colours and formatting</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${kleur.gray(date)}</span> <span class="hljs-subst">${kleur
    .bold()
    .cyan(<span class="hljs-string">"[astro-hello-integration]"</span>)}</span> <span class="hljs-subst">${message}</span>
  `</span>);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-comment">// ... same content as before</span>
});
</code></pre>
<p>Now we should have a beautiful log message that feels native to Astro, like the other server console logs.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-06-at-05.47.14.png" alt="The custom integration &quot;native feeling&quot; server log" width="600" height="400" loading="lazy">
<em>The custom integration "native feeling" server log</em></p>
<h3 id="heading-custom-integrations-as-factory-functions">Custom integrations as factory functions</h3>
<p>Our current implementation is beginning to clog the Astro configuration file.</p>
<p>In practice, instead of inlining our custom Astro integration, it’s likely to live in a separate file as a factory function, that is a function that creates and returns the Astro integration object.</p>
<p>Let’s do that – it'll be something of a refactor.</p>
<p>Move the entire integration content into a new <code>src/integrations/astro-hello.ts</code> file.</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/integrations/astro-hello.ts</span>
<span class="hljs-keyword">import</span> kleur <span class="hljs-keyword">from</span> <span class="hljs-string">"kleur"</span>;

<span class="hljs-keyword">const</span> dateTimeFormat = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat([], {
  <span class="hljs-attr">hour</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">minute</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">second</span>: <span class="hljs-string">"2-digit"</span>,
});

<span class="hljs-keyword">const</span> logServerMessage = <span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> date = dateTimeFormat.format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${kleur.gray(date)}</span> <span class="hljs-subst">${kleur
    .bold()
    .cyan(<span class="hljs-string">"[astro-hello-integration]"</span>)}</span> <span class="hljs-subst">${message}</span>
    `</span>);
};

<span class="hljs-comment">// 👀 Introduce a default export function that returns the Astro </span>
<span class="hljs-comment">// integration object.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">helloIntegration</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-hello"</span>,
    <span class="hljs-attr">hooks</span>: {
      <span class="hljs-string">"astro:config:setup"</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
        options.injectScript(<span class="hljs-string">"page"</span>, <span class="hljs-string">`import '/src/scripts/
    globalLog.js'`</span>);

        logServerMessage(<span class="hljs-string">"Injected script"</span>);
      },
    },
  };
}
</code></pre>
<p>Now, add in TypeScript types:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/integrations/astro-hello.ts</span>

<span class="hljs-keyword">import</span> type { AstroIntegration } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-keyword">const</span> logServerMessage = <span class="hljs-function">(<span class="hljs-params">message: string</span>) =&gt;</span> {
  <span class="hljs-comment">// ...</span>
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">helloIntegration</span>(<span class="hljs-params"></span>): <span class="hljs-title">AstroIntegration</span> </span>{
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Oh yeah!</p>
<p>Our implementation is coming around nicely.</p>
<p>Now, let’s clean up <code>astro.config.mjs</code> by importing our integration as shown below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 astro.config.mjs</span>
<span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;
<span class="hljs-keyword">import</span> astroHello <span class="hljs-keyword">from</span> <span class="hljs-string">"./src/integrations/astro-hello"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-comment">// 👀 invoke the imported astroHello function in the list</span>
  <span class="hljs-attr">integrations</span>: [astroHello()],
});
</code></pre>
<p>And there we have it! A sparkly clean, custom Astro integration.</p>
<p>You may view the complete source code on <a target="_blank" href="https://github.com/understanding-astro/hello-astro-integration">GitHub</a>.</p>
<h2 id="heading-the-astro-hooks-lifecycle">The Astro Hooks Lifecycle</h2>
<p>By definition, lifecycle refers to the series of changes in the life of an organism. For example, a butterfly starts as an egg, larva, pupa, and then becomes a full-blown adult.</p>
<p>Until human cloning becomes available, there’s a decent chance you also started as an infant, then grew into a toddler, eventually puberty, and then found your way into adulthood. At least, I hope so!</p>
<p>In software, the term lifecycle represents a process’s different stages.</p>
<p>With Astro hooks, we explicitly refer to the stages Astro goes through while building your application pages. This is the process from resolving the Astro configuration setup to spinning up a local server to bundling your pages statically or server-side rendered in production.</p>
<p>The entire process is what I call the Astro hooks lifecycle.</p>
<p>To get productive in developing custom integrations, we’ll need to know where in the lifecycle we need to effect a change or react to.</p>
<p>Hooks are functions which are called at various stages of the build. To interact with the build process, we'll leverage the following ten hooks:</p>
<ul>
<li><code>astro:config:setup</code></li>
<li><code>astro:config:done</code></li>
<li><code>astro:server:setup</code></li>
<li><code>astro:server:start</code></li>
<li><code>astro:server:done</code></li>
<li><code>astro:build:start</code></li>
<li><code>astro:build:setup</code></li>
<li><code>astro:build:generated</code></li>
<li><code>astro:build:ssr</code></li>
<li><code>astro:build:done</code></li>
</ul>
<p>Ten seems like a lot to remember. Good thing it isn’t a dozen hooks (twelve). And you don’t have to memorise these. Instead, understand how they work. You can always refer to the official reference when needed.</p>
<h3 id="heading-the-when-and-why-of-hooks">The when and why of hooks</h3>
<p>One of the first questions I asked myself when I started tinkering with Astro integrations was when exactly are these triggered, and is there some order of execution to them?</p>
<p>Well, the answer to these lies below, but first, consider the following diagram that depicts the order in which the hooks are executed:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/hooks-lifecycle.png" alt="Execution order of Astro hooks " width="600" height="400" loading="lazy">
<em>Execution order of Astro hooks</em></p>
<p>Kicking off the process are two hooks:</p>
<ol>
<li><code>astro:config:setup</code></li>
<li><code>astro:config:done</code></li>
</ol>
<p>These hooks are always executed regardless of the Astro build process.</p>
<p>Here’s a breakdown of when these are executed and how we could leverage these in our custom integrations:  </p>
<table>
    <thead>
        <tr>
            <th>
                Hook
            </th>
            <th>
                Executed when … 
            </th>
            <th>
                 Why use this … 
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br><br><code>astro:config:<br>setup</code><br>
            </td>
            <td>
                <br><br>Astro is initialised. <br><br>This happens <br>before the Astro project configuration (or Vite config) <br>are resolved. 
            </td>
            <td>
                <br><br>Consider being the first one at the pub before it opens. You can cause a ruckus before anyone else even shows up! <br><br>Similarly, this is where you swoop in to extend the project configuration e.g., updating the Astro config, applying Vite plugins, adding component renderers and injecting scripts before Astro knows what hit it. 
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:config:done</code>
            </td>
            <td>
                <br><br>The Astro config has been resolved. At this point, every <code>astro:config:setup</code> hook has been invoked for every integration in the project. <br><br><br>
            </td>
            <td>
                <br><br>Like a perfect pint of beer, we patiently wait to grab the glass only after it’s been poured. <br><br>Similarly, after the Astro config has finally got its act together and all the other integrations have done their thing, this is where we retrieve the final config for use in our integration. <br>
            </td>
        </tr>
    </tbody>
</table>

<p>Once <code>astro:config:done</code> is fired, there are two branches to consider: development and production mode.</p>
<p>When developing your apps locally, without initiating a production build typically via <code>npm run build</code> or <code>astro build</code>, the left side of the chart depicts the order of hooks execution in developer mode. Then the following hooks are invoked:</p>
<ol>
<li><code>astro:server:setup</code></li>
<li><code>astro:server:start</code></li>
<li><code>astro:server:done</code></li>
</ol>
<p>These hooks are executed when building your app for local development.</p>
<p>Here’s a breakdown of when these are executed and how we could leverage these in our custom integrations:  </p>
<table>
    <thead>
        <tr>
            <th>
                Hook
            </th>
            <th>
                Executed when … 
            </th>
            <th>
                 Why use this … 
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br><br><code>astro:server:<br>setup</code><br>
            </td>
            <td>
                <br><br>The Vite server has just been created in development mode.<br><br>This is before the <code>listen()</code>server event is fired i.e., before starting the server.
            </td>
            <td>
                <br><br>This is where we may update the Vite server options and middleware.<br><br>The Vite dev server object is passed as an argument to our hook.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:server:start</code>
            </td>
            <td>
                <br><br>The Vite <code>listen()</code>method has just been fired i.e., the server is running. <br><br><br>
            </td>
            <td>
                <br><br>Like tech-savvy superheroes, we can jump in here to save the day at the last minute - well, if that involves intercepting network requests. <br><br>This is where we may jump in to intercept network requests at the specified dev server address (passed as an argument to our hook)
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:server:done</code>
            </td>
            <td>
                <br><br>The dev server has just been closed.
            </td>
            <td>
                <br><br>Like cleaners coming in after the party to sweep up the mess, this is where we run cleanups. <br><br>If you wish to clean up any side effects triggered during <code>astro:server:setup</code> or <code>astro:server:start</code>, here’s where you do so!
            </td>
        </tr>
    </tbody>
</table>

<p>When we run a production build, two hooks will always be triggered. These are</p>
<ol>
<li><code>astro:build:start</code></li>
<li><code>astro:build:setup</code></li>
</ol>
<p>And here’s a breakdown of when these are executed and how we could leverage these in our custom integrations:</p>
<table>
    <thead>
        <tr>
            <th>
                Hook
            </th>
            <th>
                Executed when … 
            </th>
            <th>
                 Why use this … 
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br><br><code>astro:build:<br>start</code><br>
            </td>
            <td>
                <br><br>The Astro config is completely resolved but before the production build begins. 
            </td>
            <td>
                <br><br>The production build is about to start but perhaps you want to set up some global objects or clients needed during the build? <br>Here’s where we do so.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:build:setup</code>
            </td>
            <td>
                <br><br>The build is just about to get started. At this point, the build config is fully constructed. <br><br><br>
            </td>
            <td>
                <br><br>To steal the perfect phrase from the official Astro documentation: this is our final chance to modify the build. <br><br>It's like getting ready for a night out - we’ve put on our best outfit and look sharp, but we just need to add that one last accessory to complete the look. This is our chance to do just that - to overwrite some defaults and make sure everything is looking top-notch. <br><br>I must mention that if you're not sure whether to use this hook or <code>astro:build:start</code>, go for <code>astro:build:start</code> instead.
            </td>
        </tr>
    </tbody>
</table>

<p>Now, depending on whether the page being built is statically generated or to be server-side rendered, either <code>astro:build:generated</code> or <code>astro:build:ssr</code> will be invoked, and finally, <code>astro:build:done</code>.</p>
<p>Yes, you guessed it. Here’s the final breakdown of when these are executed and how we could leverage these in our custom integrations:</p>
<table>
    <thead>
        <tr>
            <th>
                Hook
            </th>
            <th>
                Executed when … 
            </th>
            <th>
                 Why use this … 
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <br><br><code>astro:build:<br>generated</code><br>
            </td>
            <td>
                <br><br>The static production build has completely generated routes and assets. 
            </td>
            <td>
                <br><br>Access generated routes and assets before build artefacts are cleaned up. As per the official docs, this is an uncommon case and we might be better off using <code>astro:build:done</code> in many cases., except we really need to access these files before cleanup.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:build:ssr</code>
            </td>
            <td>
                <br><br>A production SSR build is completed.<br><br><br>
            </td>
            <td>
                <br><br>To get access to the SSR manifest. This is helpful when creating custom SSR builds.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>astro:build:done</code>
            </td>
            <td>
                <br><br>The production build is complete!
            </td>
            <td>
                <br><br>This is where we may access the generated routes and assets e.g., to be copied somewhere. For transforming generated assets, consider using a Vite plugin and configuring <code>astro:config:setup</code>.
            </td>
        </tr>
    </tbody>
</table>

<h3 id="heading-examining-the-hooks-evaluation-order">Examining the hooks evaluation order</h3>
<p>Even though we’ve taken time to explore when the Astro hooks are invoked, there’s no better teacher than practice.</p>
<p>Let’s go ahead and write out a simple integration that spits out a log to the server console when invoked. Then, you can tinker with building several pages for production and inspect the logs.</p>
<p>Our eventual goal is to have a custom integration that looks something like this:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">name</span>: <span class="hljs-string">"some-identifier"</span>,
  <span class="hljs-attr">hooks</span>: {
   <span class="hljs-string">"hook-name"</span>: <span class="hljs-function">() =&gt;</span> {
     <span class="hljs-comment">// log hook name so we know it's been invoked</span>
   }
  }
}
</code></pre>
<p>Makes sense?</p>
<p>Let’s go ahead and build this out.</p>
<p>If building along, extend the hello world application or create a new Astro application with the following custom integration:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 src/integrations/lifecycle-logs.ts</span>

<span class="hljs-keyword">import</span> kleur <span class="hljs-keyword">from</span> <span class="hljs-string">"kleur"</span>;
<span class="hljs-keyword">import</span> type { AstroIntegration } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-comment">//Create a new dateTimeFormat object</span>
<span class="hljs-keyword">const</span> dateTimeFormat = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat([], {
  <span class="hljs-attr">hour</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">minute</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">second</span>: <span class="hljs-string">"2-digit"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> lifecycleLogs = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> hooks = [
    <span class="hljs-string">`astro:config:setup`</span>,
    <span class="hljs-string">`astro:config:done`</span>,
    <span class="hljs-string">`astro:server:setup`</span>,
    <span class="hljs-string">`astro:server:start`</span>,
    <span class="hljs-string">`astro:server:done`</span>,
    <span class="hljs-string">`astro:build:start`</span>,
    <span class="hljs-string">`astro:build:setup`</span>,
    <span class="hljs-string">`astro:build:generated`</span>,
    <span class="hljs-string">`astro:build:ssr`</span>,
    <span class="hljs-string">`astro:build:done`</span>,
  ] <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;

  <span class="hljs-comment">// base integration structure. "hooks" will be updated</span>
  <span class="hljs-keyword">let</span> integration: AstroIntegration = {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-lifecycle-logs"</span>,
    <span class="hljs-attr">hooks</span>: {},
  };

  <span class="hljs-comment">// loop over the hooks list and add the name and log</span>
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> hook <span class="hljs-keyword">of</span> hooks) {
    integration.hooks[hook] = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// 👀 Get a new date string</span>
      <span class="hljs-keyword">const</span> date = dateTimeFormat.format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());

      <span class="hljs-comment">// log with kleur colours and formatting</span>
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${kleur.gray(date)}</span> <span class="hljs-subst">${kleur
        .bold()
        .yellow(<span class="hljs-string">"[lifecycle-log]"</span>)}</span> <span class="hljs-subst">${kleur.green(hook)}</span>
        `</span>);
    };
  }

  <span class="hljs-keyword">return</span> integration;
};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> lifecycleLogs;
</code></pre>
<p>Import <code>lifecycleLogs</code> and add it to your project’s integration list, then (re)start your application to see the logs in the console as shown below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-08-at-17.13.02.png" alt="The dev lifecycle hooks " width="600" height="400" loading="lazy">
<em>The dev lifecycle hooks</em></p>
<p>As an exercise, I suggest you add a new SSR page and run a production build to see the order of hooks execution logged.</p>
<p>Here’s an example with two pages:</p>
<ul>
<li>a static <code>index.astro</code> page</li>
<li>a server-side rendered <code>ssr.astro</code> page</li>
</ul>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-08-at-18.16.15.png" alt="The entire hook lifecycle logged " width="600" height="400" loading="lazy">
<em>The entire hook lifecycle logged</em></p>
<h2 id="heading-how-to-build-a-default-pre-render-integration">How to Build a Default Pre-render Integration</h2>
<p>When we enable SSR in our project, we can also opt-in to pre-rendering, that is to statically render some files at build time.</p>
<p>The way to do this is to add an <code>export const prerender = true</code> to the desired static page(s).</p>
<p>There was a time Astro didn’t support hybrid rendering, so this is an excellent feature.</p>
<p>But in practice, we may have multiple static pages and just a few server-side rendered ones. Adding <code>export const prerender = true</code> to all the static pages gets painfully annoying.</p>
<p>The other day I started building an Astro application that was predominantly statically rendered. Then I realised I needed one server-side rendered route. </p>
<p>At this point, I change my <code>astro.config.mjs</code> file to include <code>output: server</code>. Consequently, I had to go to all the existing static pages to add <code>export const prerender = true</code>. This wasn’t pleasant.</p>
<p>You may view the complete source code on <a target="_blank" href="https://github.com/understanding-astro/astro-integration-prerender-by-default">GitHub</a>.</p>
<h3 id="heading-project-objective-1">Project objective</h3>
<p>The goal of our custom integration is to flip the default hybrid rendering behaviour of Astro.</p>
<p>By default, with an <code>output: server</code> in our configuration, all pages are assumed to be server-rendered, and we must explicitly add <code>export const prerender = true</code> to our static pages.</p>
<p>We want to achieve a different behaviour for cases when we have more static pages, that is:</p>
<ul>
<li>By default, with <code>output: server</code> in our configuration, render all pages statically at build time – prerender by default.</li>
<li>Add <code>export const prerender = false</code> to render a page server-side explicitly.</li>
</ul>
<p>See what we’ve done there?</p>
<p>Now, please give it a think. How do we achieve this?</p>
<p>At the time of writing, there’s a public roadmap for Astro to <a target="_blank" href="https://github.com/withastro/roadmap/issues/539">support default pre-rendering</a> internally. Until then, let’s bend Astro to our will.</p>
<h3 id="heading-integration-api">Integration API</h3>
<p>We will design our integration as a factory function named <code>prerenderByDefault</code>.</p>
<p>Our users will go ahead and invoke this function within their <code>integrations</code> list, as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [prerenderByDefault()],
});
</code></pre>
<p>By default, we will log messages to the server console but expose a <code>silent</code> parameter to prevent server console logs.</p>
<p>Astro integrations usually support configurations by passing arguments to the factory function. Below’s our proposed API:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [prerenderByDefault({
     <span class="hljs-attr">silent</span>: <span class="hljs-literal">true</span> <span class="hljs-comment">// or false (boolean)</span>
  })],
});
</code></pre>
<p>Finally, we will add some basic validation within our integration. If the user doesn’t have an <code>output: server</code> or <code>adapter</code> option in their configuration, we will skip pre-rendering by default. This is because we only want our integration to take effect during hybrid rendering, which is only activated with <code>output: server</code> in the user’s project configuration.</p>
<h3 id="heading-technical-solution-overview">Technical solution overview</h3>
<p>At its core, our integration will take advantage of two lifecycle hooks: <code>astro:config:setup</code> and <code>astro:config:done</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">prerenderByDefault</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-prerender-by-default"</span>,
    <span class="hljs-attr">hooks</span>: {
      <span class="hljs-string">"astro:config:setup"</span>() {

      },
      <span class="hljs-string">"astro:config:done"</span>(options) {

      },
    },
  };
}
</code></pre>
<p>In <code>astro:config:done</code>, we will retrieve the project’s resolved configuration and perform our validation.</p>
<pre><code class="lang-js"><span class="hljs-string">"astro:config:done"</span>(options) {

   <span class="hljs-comment">// 1. Get resolved config from options.config </span>
   <span class="hljs-comment">// 2. Validate that the config object has the right </span>
    <span class="hljs-comment">//   output and adapter values </span>

}
</code></pre>
<p>In <code>astro:config:setup</code>, we will swoop in and extend the user’s Astro project configuration by applying a custom Vite plugin.</p>
<pre><code class="lang-js"><span class="hljs-string">"astro:config:setup"</span>(options) {
    <span class="hljs-comment">// Apply a custom Vite plugin here</span>
}
</code></pre>
<p>When Astro builds our project, it does so using Vite. Integrations are to Astro what plugins are to Vite. To extend Vite, we use plugins.</p>
<p>We can tap into the Vite build lifecycle to access the user’s Astro code (particularly their <code>pages</code>) during the build process.</p>
<p>Now, here comes the fun part.</p>
<p>First, we will parse the Astro code into Abstract Syntax Trees (ASTs).</p>
<p>Essentially, an AST serves as a means of representing the code’s structure in a programming language. Just as a sentence can be broken down into nouns, verbs, and adjectives, an AST dissects code into its essential components – variables, functions, and operations – and reflects their relationships in a tree-like structure.</p>
<p>A valid Astro component may take different forms. But the <code>frontmatter</code> must always be the first child node of the root node.</p>
<p>For example, the following is correct:</p>
<pre><code class="lang-js">--- 
 <span class="hljs-comment">// frontmatter</span>
---
<span class="hljs-comment">// markup goes here </span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span> Hello world <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>The following is invalid:</p>
<pre><code class="lang-js">&lt;h1&gt; Hello world &lt;/h1&gt;

--- 
 <span class="hljs-comment">// frontmatter </span>
---
</code></pre>
<p>With this heuristic, we will grab the first child node in the root of our parsed AST and make some decisions:</p>
<ul>
<li>If the file already has a <code>prerender</code> export, do nothing, that is leave the file as is.</li>
<li>Otherwise, update the code to include <code>export const prerender = true</code> – so we will update the code within our integration. It’s important to note that this only transforms the page’s code to be built. It does not update the local file.</li>
<li>Finally, if a page has no frontmatter, we will create one and include the <code>export const prerender = true</code> code snippet.</li>
</ul>
<h3 id="heading-how-to-initialise-projects-via-cli-flags">How to Initialise projects via CLI flags</h3>
<p>The <code>create astro</code> command is robust. But sometimes you don’t have the patience to select every option via prompts.</p>
<p>In such cases, use the CLI flags as shown below.</p>
<p>Initialise a new project with the following command:</p>
<pre><code class="lang-bash">npm create astro@latest -- --template=minimal    
--typescript=strictest --git --install   
astro-integration-prerender-by-default
</code></pre>
<p>This will set up a new Astro project in the <code>prerenderbyDefault</code>directory with CLI flags passed instead of via prompts, that is <code>--template=minimal</code> will use the minimal template, <code>--template=strictest</code> will use the <code>strictest</code> typescript config, <code>--git</code> will initialise a Git repo and <code>--install</code> will install the dependencies.</p>
<p>Here’s a quick table of the available CLI flags:</p>
<table>
    <thead>
        <tr>
            <th>
                Name
            </th>
            <th>
                Description
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                <code>--template &lt;name&gt;</code>
            </td>
            <td>
                Specify the template. Where <code>name</code> could be <br>any of the directories in <br><a href="https://github.com/withastro/astro/tree/main/examples/">https://github.com/withastro/astro/tree/main/examples/</a>.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--install</code> / <code>--no-install</code>
            </td>
            <td>
                <br><br>Install dependencies (or not).
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--git</code> / <code>--no-git</code>
            </td>
            <td>
                <br><br>Initialize git repo (or not).
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--yes</code> (<code>-y</code>)
            </td>
            <td>
                <br><br>Skip all prompts and accept the defaults.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--no</code> (<code>-n</code>) 
            </td>
            <td>
                <br><br>Skip all prompts and decline the defaults.
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--dry-run</code>
            </td>
            <td>
                <br><br>Walk through the project creation steps <br>without any actual execution. Useful for a “dry run” 
            </td>
        </tr>
        <tr>
            <td>
                <br><br><code>--skip-houston</code>
            </td>
            <td>
                <br><br>Skip the Houston animation. If in a hurry, this saves some time and starts the prompt directly. 
            </td>
        </tr>
        <tr>
            <td>
                <br><br> <code>--typescript &lt;option&gt;</code>
            </td>
            <td>
                <br><br>Where <code>option</code> is <code>strict</code> , <code>strictest</code> or<code>relaxed</code>
            </td>
        </tr>
    </tbody>
</table>

<p>Now, change the directory and run the new Astro application:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ./astro-integration-prerender-by-default &amp;&amp; npm run start
</code></pre>
<p>By default, this should start the application on an available port, for example <code>localhost:3000</code>.</p>
<h3 id="heading-how-to-set-up-the-integration">How to set up the integration</h3>
<p>Create a new <code>index</code> file in <code>integrations/prerenderByDefault</code> and create the integration factory function as shown below:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> default <span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">prerenderByDefault</span></span>() {
  <span class="hljs-built_in">return</span> {
    name: <span class="hljs-string">"astro-prerender-by-default"</span>,
    hooks: {
      <span class="hljs-string">"astro:config:setup"</span>() {},
      <span class="hljs-string">"astro:config:done"</span>() {},
    },
  };
}
</code></pre>
<p>Let’s add support for configuring the integration by accepting a configuration object.</p>
<p>Go ahead and create a <code>types.ts</code> file in <code>integrations/prerenderByDefault</code> as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> type Config =
  | {
      silent?: boolean;
    }
  | <span class="hljs-literal">undefined</span>;
</code></pre>
<p>Now, let’s add a <code>config</code> parameter to the <code>prerenderByDefault</code> factory function and type its return value as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { AstroIntegration } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;
<span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">prerenderByDefault</span>(<span class="hljs-params">config: Config</span>): <span class="hljs-title">AstroIntegration</span> </span>{
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now go ahead and add the integration in the project’s config file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro/config"</span>;
<span class="hljs-keyword">import</span> prerenderByDefault <span class="hljs-keyword">from</span> <span class="hljs-string">"./integrations/prerenderByDefault"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">integrations</span>: [prerenderByDefault()],
});
</code></pre>
<h3 id="heading-how-to-validate-a-resolved-astro-configuration">How to validate a resolved Astro configuration</h3>
<p>Let’s go ahead to handle our integration validation. First, we will create an <code>isValidAstroConfig</code> method to receive an Astro configuration and a validation result.</p>
<p>Here’s the implementation below:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 prerenderByDefault/isValidAstroConfig.ts</span>

<span class="hljs-keyword">import</span> type { AstroConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"astro"</span>;

<span class="hljs-comment">/**
 * @param config: the fully resolved astro project config
 * @returns validation result
 */</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isValidAstroConfig = <span class="hljs-function">(<span class="hljs-params">config: AstroConfig</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (config.output !== <span class="hljs-string">"server"</span>) {
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">type</span>: <span class="hljs-string">"invalid_output_config"</span>, <span class="hljs-attr">value</span>: <span class="hljs-literal">false</span> } <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;
  }

  <span class="hljs-keyword">if</span> (!config.adapter) {
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">type</span>: <span class="hljs-string">"invalid_adapter_config"</span>, <span class="hljs-attr">value</span>: <span class="hljs-literal">false</span> } <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;
  }

  <span class="hljs-comment">/**
   * configuration is valid
   */</span>
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">type</span>: <span class="hljs-string">"success"</span>, <span class="hljs-attr">value</span>: <span class="hljs-literal">true</span> } <span class="hljs-keyword">as</span> <span class="hljs-keyword">const</span>;
};
</code></pre>
<p>I’ve decided to return an object instead of simple boolean values to utilise typescript’s <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking">exhaustiveness checking</a>.</p>
<p>Now, let’s leverage <code>isValidAstroConfig</code> in the <code>astro:config:done</code> hook by doing the following:</p>
<ul>
<li>Retrieve the final Astro project configuration</li>
<li>Validate the configuration</li>
<li>Log messages to the server console based on the validation result</li>
</ul>
<p>Here’s how:</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">prerenderByDefault</span>(<span class="hljs-params">config: Config</span>): <span class="hljs-title">AstroIntegration</span> </span>{
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-prerender-by-default"</span>,
    <span class="hljs-attr">hooks</span>: {
      <span class="hljs-string">"astro:config:setup"</span>() {},
      <span class="hljs-comment">// 👀 look below </span>
      <span class="hljs-string">"astro:config:done"</span>(options) {
        <span class="hljs-comment">// get the 'silent' integration config property, default to false.</span>
        <span class="hljs-keyword">const</span> silent = config?.silent ?? <span class="hljs-literal">false</span>;

        <span class="hljs-comment">// validate the resolved project configuration</span>
        <span class="hljs-keyword">const</span> validationResult = isValidAstroConfig(options.config);

        <span class="hljs-comment">/**
         * Leverage Typescript exhaustive check to handle all
         * validation types and log messages where appropriate
         */</span>
        <span class="hljs-keyword">switch</span> (validationResult.type) {
          <span class="hljs-keyword">case</span> <span class="hljs-string">"invalid_adapter_config"</span>:
            log({
              silent,
              <span class="hljs-attr">message</span>: <span class="hljs-string">`Adapter not set for hybrid rendering. Skipping`</span>,
            });
            <span class="hljs-keyword">return</span>;

          <span class="hljs-keyword">case</span> <span class="hljs-string">"invalid_output_config"</span>:
            log({
              silent,
              <span class="hljs-attr">message</span>: <span class="hljs-string">`Config output not set to "server". Skipping`</span>,
            });
            <span class="hljs-keyword">return</span>;

          <span class="hljs-keyword">case</span> <span class="hljs-string">"success"</span>:
            <span class="hljs-keyword">return</span>;

          <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">const</span> _exhaustiveCheck: never = validationResult;
            <span class="hljs-keyword">return</span> _exhaustiveCheck;
        }
      },
    },
  };
}
</code></pre>
<p>We’re calling a <code>log</code> function to write messages to the server console depending on the validation result, but this function does not exist.</p>
<p>We’ve written similar log functions, so here’s the code for this one:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 prerenderByDefault/log.ts</span>

<span class="hljs-keyword">import</span> kleur <span class="hljs-keyword">from</span> <span class="hljs-string">"kleur"</span>;

type LogOptions = {
  <span class="hljs-attr">silent</span>: boolean;
  message: string;
};

<span class="hljs-keyword">const</span> dateTimeFormat = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Intl</span>.DateTimeFormat([], {
  <span class="hljs-attr">hour</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">minute</span>: <span class="hljs-string">"2-digit"</span>,
  <span class="hljs-attr">second</span>: <span class="hljs-string">"2-digit"</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> log = <span class="hljs-function">(<span class="hljs-params">options: LogOptions</span>) =&gt;</span> {
  <span class="hljs-comment">// do not log if the "silent" argument is passed</span>
  <span class="hljs-keyword">if</span> (options.silent) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// get new date</span>
  <span class="hljs-keyword">const</span> date = dateTimeFormat.format(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>());

  <span class="hljs-comment">// log to the console with colours and text formatting</span>
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${kleur.gray(date)}</span> <span class="hljs-subst">${kleur
    .bold()
    .magenta(<span class="hljs-string">"[astro-prerender-by-default]"</span>)}</span> <span class="hljs-subst">${options.message}</span>
  `</span>);
};
</code></pre>
<p>Now make sure to import the <code>log</code> function in <code>prerenderByDefault/index.ts</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { log } <span class="hljs-keyword">from</span> <span class="hljs-string">"./log"</span>;
...
</code></pre>
<p>Now if we go ahead and build the project with <code>npm run build</code>, we should have our integration validation log displayed as shown below:</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-12-at-07.01.41.png" alt="Validation server log " width="600" height="400" loading="lazy">
<em>Validation server log</em></p>
<p>This is expected because the project does not have a <code>server</code> output configured. In this case, hybrid rendering cannot be utilised.</p>
<h3 id="heading-how-to-apply-vite-plugins-in-custom-integrations">How to apply Vite plugins in custom integrations</h3>
<p>Astro uses Vite under the hood. As such, it’s possible to pass additional configurations to Vite in the <code>astro.config.mjs</code> file, for example:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">vite</span>: {
    <span class="hljs-comment">//This adds a custom plugin directly to the Astro config</span>
    <span class="hljs-attr">plugins</span>: [myPlugin()]
  }
}
</code></pre>
<p>Consequently, we can take advantage of this in our integration.</p>
<p>Remember from the lifecycle hooks section that <code>astro:config:setup</code> is where we may swoop in to extend the project configuration. Let’s do so now:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { injectVitePlugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"./injectVitePlugin"</span>;
<span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"astro-prerender-by-default"</span>,
    <span class="hljs-attr">hooks</span>: {
      <span class="hljs-comment">// 👀 look here</span>
      <span class="hljs-string">"astro:config:setup"</span>(options) {
        options.updateConfig({
          <span class="hljs-attr">vite</span>: {
            <span class="hljs-attr">plugins</span>: [injectVitePlugin()],
          },
        });
      },
}
<span class="hljs-comment">// ...</span>
</code></pre>
<p>In the plugins array, we’re invoking <code>injectVitePlugin()</code>, which should return a valid Vite plugin.</p>
<p>Oh, but what’s a valid Vite plugin, you might ask?</p>
<p>Similar to Astro integrations, a Vite plugin is represented by an object with a name property and specific hooks, which are methods on the object, for example:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-${name}, 
  configResolved() {
   // Called after the Vite config is resolved
  }
}</span>
</code></pre>
<p>Let’s go ahead and write out a basic version of <code>injectVitePlugin</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { Plugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> injectVitePlugin = (): <span class="hljs-function"><span class="hljs-params">Plugin</span> =&gt;</span> {
  <span class="hljs-comment">//Our prerender plugin to be fleshed out</span>
  <span class="hljs-keyword">const</span> prerenderByDefaultPlugin = { <span class="hljs-attr">name</span>: <span class="hljs-string">""</span> };

  <span class="hljs-keyword">return</span> {
    <span class="hljs-comment">// name follows the pattern vite-plugin-${framework}-${feature}</span>
    <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-inject-default-prerender"</span>,
    <span class="hljs-attr">configResolved</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
      <span class="hljs-comment">//Grab the Vite plugins in the resolved config </span>
     <span class="hljs-comment">// and add our plugin as the first in the list </span>
      (options.plugins <span class="hljs-keyword">as</span> Plugin[]).unshift(prerenderByDefaultPlugin);
    },
  };
};
</code></pre>
<p>We will flesh out this basic structure, but first consider that in the astro hooks lifecycle, <code>astro:config:setup</code> runs before <code>astro:config:done</code>.</p>
<p>We're updating the Vite plugins in <code>astro:config:setup</code>. But we're validating the project config in <code>astro:config:done</code>.</p>
<p>We’ll likely run into a race condition here, that is updating the Vite plugin list in <code>astro:config:setup</code> before <code>astro:config:done</code> has wholly validated the project’s config.</p>
<p>How can we resolve this?</p>
<p>Let’s leverage a promise.</p>
<p>We will initialise a promise that’s only resolved after validation is complete, and we will await the promise resolution in <code>injectVitePlugin</code>. Luckily, <code>astro:config:setup</code> can take in async functions. Particularly in the Vite plugin function(s).</p>
<p>Let’s walk through the changes required to achieve this.</p>
<p>First, let’s introduce a <code>ValidationResult</code> type in our <code>types.ts</code> file:</p>
<pre><code class="lang-js"><span class="hljs-comment">// 📂 prerenderByDefault/types.ts</span>

<span class="hljs-keyword">import</span> type { isValidAstroConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"./isValidAstroConfig"</span>;

<span class="hljs-keyword">export</span> type ValidationResult = ReturnType&lt;<span class="hljs-keyword">typeof</span> isValidAstroConfig&gt;;

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Now, create a new promise in the main <code>index</code> file:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> type { Config, ValidationResult } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;

<span class="hljs-keyword">let</span> resolveValidationResult: <span class="hljs-function">(<span class="hljs-params">value: ValidationResult</span>) =&gt;</span> <span class="hljs-keyword">void</span>;

<span class="hljs-keyword">let</span> validationResultPromise = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>&lt;ValidationResult&gt;(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
  resolveValidationResult = resolve;
});

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Right after validation is done in <code>astro:config:done</code>, let’s go ahead and resolve the promise with the result of the validation:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ... </span>
<span class="hljs-string">"astro:config:done"</span>(options) {
   <span class="hljs-keyword">const</span> silent = config?.silent ?? <span class="hljs-literal">false</span>;
   <span class="hljs-keyword">const</span> validationResult = isValidAstroConfig(options.config);

   <span class="hljs-comment">// resolve the validation promise</span>
   resolveValidationResult(validationResult);

   <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Then pass both the integration configuration and validation result promise to <code>injectVitePlugin</code>:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
<span class="hljs-attr">plugins</span>: [injectVitePlugin(config, validationResultPromise)],
</code></pre>
<p>We must now update <code>injectVitePlugin</code> to await the validation result promise as shown below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { Plugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> type { Config, ValidationResult } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> injectVitePlugin = <span class="hljs-keyword">async</span> (
  config: Config,
  <span class="hljs-attr">validationResultPromise</span>: <span class="hljs-built_in">Promise</span>&lt;ValidationResult&gt;
): <span class="hljs-built_in">Promise</span>&lt;Plugin | <span class="hljs-literal">null</span>&gt; =&gt; {

  <span class="hljs-comment">// await the validation result promise before continuing</span>
  <span class="hljs-keyword">const</span> validationResult = <span class="hljs-keyword">await</span> validationResultPromise;

  <span class="hljs-comment">// exit if the validation result value is false</span>
  <span class="hljs-keyword">if</span> (!validationResult.value) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }

  <span class="hljs-comment">// TBD ..</span>
  <span class="hljs-keyword">const</span> prerenderByDefaultPlugin = { <span class="hljs-attr">name</span>: <span class="hljs-string">""</span> };

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-inject-default-prerender"</span>,
    <span class="hljs-attr">configResolved</span>: <span class="hljs-function">(<span class="hljs-params">options</span>) =&gt;</span> {
      (options.plugins <span class="hljs-keyword">as</span> Plugin[]).unshift(prerenderByDefaultPlugin);
    },
  };
};
</code></pre>
<p>Phew! We’ve eradicated the pesky race condition. So our solution is shaping up nicely, eh?</p>
<h3 id="heading-how-to-write-vite-plugins-for-astro">How to write Vite plugins for Astro</h3>
<p>We know what a Vite plugin looks like now. But the core functionality of our integration hasn’t been written yet. This is currently represented by the <code>prerenderByDefaultPlugin</code> variable, that is:</p>
<pre><code class="lang-js"><span class="hljs-comment">// TBD...</span>
  <span class="hljs-keyword">const</span> prerenderByDefaultPlugin = { <span class="hljs-attr">name</span>: <span class="hljs-string">""</span> };
</code></pre>
<p>Let’s change this to be returned from a separate <code>getVitePlugin</code> function:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> { getVitePlugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"./getVitePlugin"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> injectVitePlugin = <span class="hljs-keyword">async</span> (
  config: Config,
  <span class="hljs-attr">validationResultPromise</span>: <span class="hljs-built_in">Promise</span>&lt;ValidationResult&gt;
): <span class="hljs-built_in">Promise</span>&lt;Plugin | <span class="hljs-literal">null</span>&gt; =&gt; {
  <span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">const</span> prerenderByDefaultPlugin = getVitePlugin(config);

  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>Where <code>getVitePlugin</code> is the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getVitePlugin = <span class="hljs-function">(<span class="hljs-params">config: Config</span>) =&gt;</span> ({
  <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-prerender-by-default"</span>,
});
</code></pre>
<h3 id="heading-how-to-parse-and-transforming-asts">How to parse and transforming ASTs</h3>
<p>We want to transform a user’s Astro code and make updates before it is eventually built.</p>
<p>Luckily Vite has a <code>transform</code>  hook we can leverage just for this. Let’s play around with this a bit in our <code>getVitePlugin</code> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { Plugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;
<span class="hljs-keyword">import</span> { log } <span class="hljs-keyword">from</span> <span class="hljs-string">"./log"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getVitePlugin = (config: Config): <span class="hljs-function"><span class="hljs-params">Plugin</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> silent = config?.silent ?? <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-prerender-by-default"</span>,
    <span class="hljs-keyword">async</span> transform(code, id) {
      <span class="hljs-comment">// 👀 log the value of the id</span>
      log({
        silent,
        <span class="hljs-attr">message</span>: id,
      });
    },
  };
};
</code></pre>
<p>The <code>transform</code> hook is ideal for transforming individual modules in the build process, and we receive the <code>code</code> in the file as a <code>string</code> and an <code>id</code> representing the <code>string</code> path to the file name.</p>
<p>To test how this works, update the Astro project config to include a <code>server</code> output.</p>
<pre><code class="lang-js"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">output</span>: <span class="hljs-string">"server"</span>,
  <span class="hljs-attr">integrations</span>: [prerenderByDefault()],
});
</code></pre>
<p>Then add an adapter to handle server-side rendering with:</p>
<pre><code class="lang-bash">npx astro add netlify
</code></pre>
<p>We may now explore the log from <code>getVitePlugin</code> by running <code>npm run build</code> from the terminal.</p>
<p>Notice how many more files are transformed than just the user’s <code>.astro</code> pages.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-12-at-09.18.14.png" alt="Exploring the list of transformed files." width="600" height="400" loading="lazy">
<em>Exploring the list of transformed files.</em></p>
<p>Most of the files here are related to Astro internals. So we must only concern ourselves with the user’s <code>.astro</code> pages. We want to transform those files while leaving everything else as is.</p>
<p>Let’s add a simple conditional:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ... </span>
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-prerender-by-default"</span>,
    <span class="hljs-keyword">async</span> transform(code, id) {
      <span class="hljs-comment">// 👀 filter out other file types</span>
      <span class="hljs-keyword">if</span> (!id.endsWith(<span class="hljs-string">".astro"</span>)) {
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-comment">// log the value of the id</span>
      log({
        silent,
        <span class="hljs-attr">message</span>: id,
      });
    },
  };
</code></pre>
<p>Now, rerun the build, and we should have just the user’s <code>.astro</code> page files.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-12-at-09.22.30.png" alt="Logging the project page files." width="600" height="400" loading="lazy">
<em>Logging the project page files.</em></p>
<p>This is excellent.</p>
<p>Just after the conditional, we can get on with parsing the code. To do this, we will leverage the <code>parse</code> utility exported from Astro’s compiler as shown below:</p>
<pre><code class="lang-js">    <span class="hljs-comment">// ... </span>
    <span class="hljs-keyword">async</span> transform(code, id) {
      <span class="hljs-keyword">if</span> (!id.endsWith(<span class="hljs-string">".astro"</span>)) {
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-comment">// 👀 </span>
      <span class="hljs-keyword">const</span> { ast } = <span class="hljs-keyword">await</span> parse(code);

      <span class="hljs-comment">// 👀 logs for debugging </span>
      log({
        silent,
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Parsed AST"</span>,
      });

      <span class="hljs-built_in">console</span>.log(ast);
    }
</code></pre>
<p>This project only has a single page in <code>src/index.astro</code>. So, essentially, only that page will be transformed.</p>
<p>Here’s the content of the page:</p>
<pre><code class="lang-js">---
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Astro<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Here’s the corresponding AST logged to the console:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">type</span>: <span class="hljs-string">'root'</span>,
  <span class="hljs-attr">children</span>: [
    { <span class="hljs-attr">type</span>: <span class="hljs-string">'frontmatter'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'\n'</span>, <span class="hljs-attr">position</span>: [<span class="hljs-built_in">Object</span>] },
    {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'element'</span>,
      <span class="hljs-attr">name</span>: <span class="hljs-string">'html'</span>,
      <span class="hljs-attr">attributes</span>: [<span class="hljs-built_in">Array</span>],
      <span class="hljs-attr">children</span>: [<span class="hljs-built_in">Array</span>]
    },
    { <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'\n'</span>, <span class="hljs-attr">position</span>: [<span class="hljs-built_in">Object</span>] }
  ]
}
</code></pre>
<p>Every parsed AST will have a <code>root</code> element. An empty file will have the shape:</p>
<pre><code class="lang-js">{ <span class="hljs-attr">type</span>: <span class="hljs-string">'root'</span> }
</code></pre>
<p>Knowing this, we can build out our parsing logic. But we need a way to walk the entire AST. </p>
<p>We could write a sophisticated function to loop over every element in the tree. But we can leverage the <code>walk</code> utility from the Astro compiler, which will traverse every node in the tree, and we could perform any actions on a specified node via a callback.</p>
<p>Let’s take that for a spin by adding the following:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { ast } = <span class="hljs-keyword">await</span> parse(code);

<span class="hljs-comment">// 👀</span>
walk(ast, <span class="hljs-function">(<span class="hljs-params">node</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"=========== \n"</span>, node);
});
</code></pre>
<p>Inspect the logs, and we should have the different nodes logged to the console, for example:</p>
<pre><code class="lang-js">=========== 
 {
  <span class="hljs-attr">type</span>: <span class="hljs-string">'root'</span>,
  <span class="hljs-attr">children</span>: [
    { <span class="hljs-attr">type</span>: <span class="hljs-string">'frontmatter'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'\n'</span>, <span class="hljs-attr">position</span>: [<span class="hljs-built_in">Object</span>] },
    {
      <span class="hljs-attr">type</span>: <span class="hljs-string">'element'</span>,
      <span class="hljs-attr">name</span>: <span class="hljs-string">'html'</span>,
      <span class="hljs-attr">attributes</span>: [<span class="hljs-built_in">Array</span>],
      <span class="hljs-attr">children</span>: [<span class="hljs-built_in">Array</span>]
    },
    { <span class="hljs-attr">type</span>: <span class="hljs-string">'text'</span>, <span class="hljs-attr">value</span>: <span class="hljs-string">'\n'</span>, <span class="hljs-attr">position</span>: [<span class="hljs-built_in">Object</span>] }
  ]
}
=========== 
 {
  <span class="hljs-attr">type</span>: <span class="hljs-string">'frontmatter'</span>,
  <span class="hljs-attr">value</span>: <span class="hljs-string">'\n'</span>,
  <span class="hljs-attr">position</span>: {
    <span class="hljs-attr">start</span>: { <span class="hljs-attr">line</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">column</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">offset</span>: <span class="hljs-number">0</span> },
    <span class="hljs-attr">end</span>: { <span class="hljs-attr">line</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">column</span>: <span class="hljs-number">4</span>, <span class="hljs-attr">offset</span>: <span class="hljs-number">7</span> }
  }
}
=========== 
<span class="hljs-comment">// ... see logs</span>
</code></pre>
<p>It’s game time. Let’s go ahead and write out the complete code, which involves:</p>
<ul>
<li>Walking the AST</li>
<li>Checking if the file has a frontmatter</li>
<li>Checking if the file already has a <code>prerender</code> export in its frontmatter. For this, we will use <a target="_blank" href="https://github.com/guybedford/es-module-lexer#readme">es-module-lexer</a> , which outputs the list of exports of import specifiers</li>
<li>Adding <code>export const prerender = true</code> to the code where required</li>
<li>After transforming the AST, that is adding <code>export const prerender = true</code> where needed, we will return the AST to code via the <code>serialize</code> utility from the Astro compiler.</li>
</ul>
<p>Here we go:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> type { Plugin } <span class="hljs-keyword">from</span> <span class="hljs-string">"vite"</span>;
<span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">"./types"</span>;
<span class="hljs-keyword">import</span> { parse } <span class="hljs-keyword">from</span> <span class="hljs-string">"@astrojs/compiler"</span>;
<span class="hljs-keyword">import</span> { walk, is, serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">"@astrojs/compiler/utils"</span>;
<span class="hljs-keyword">import</span> { parse <span class="hljs-keyword">as</span> parseESModuleLexer } <span class="hljs-keyword">from</span> <span class="hljs-string">"es-module-lexer"</span>;

<span class="hljs-keyword">import</span> { log } <span class="hljs-keyword">from</span> <span class="hljs-string">"./log"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getVitePlugin = (config: Config): <span class="hljs-function"><span class="hljs-params">Plugin</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> silent = config?.silent ?? <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"vite-plugin-astro-prerender-by-default"</span>,
    <span class="hljs-keyword">async</span> transform(code, id) {
      <span class="hljs-keyword">if</span> (!id.endsWith(<span class="hljs-string">".astro"</span>)) {
        <span class="hljs-keyword">return</span>;
      }

      <span class="hljs-keyword">const</span> { ast } = <span class="hljs-keyword">await</span> parse(code);

      walk(ast, <span class="hljs-function">(<span class="hljs-params">node</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (is.root(node)) {
          <span class="hljs-keyword">const</span> firstChildNode = node.children?.[<span class="hljs-number">0</span>];

          <span class="hljs-comment">//Check that a frontmatter exists as the first child node</span>
          <span class="hljs-keyword">if</span> (firstChildNode?.type === <span class="hljs-string">"frontmatter"</span>) {
            <span class="hljs-comment">//Using es-module-lexer, get the list of exports</span>
            <span class="hljs-keyword">const</span> [, <span class="hljs-built_in">exports</span>] = parseESModuleLexer(firstChildNode.value);

            <span class="hljs-comment">//Check if any export is named "prerender". "n" stands for "name."</span>
            <span class="hljs-keyword">if</span> (<span class="hljs-built_in">exports</span>.some(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e.n === <span class="hljs-string">"prerender"</span>)) {
              log({
                silent,
                <span class="hljs-attr">message</span>: <span class="hljs-string">"'prerender' export found. Skipping"</span>,
              });

              <span class="hljs-comment">// exit - let whatever prerender value is exported take effect</span>
              <span class="hljs-keyword">return</span>;
            }

            <span class="hljs-comment">// add prerender export for the static build, i.e., "export const prerender = true."</span>
            <span class="hljs-comment">// note that we concatenate this to whatever the current string value of the node is</span>
            firstChildNode.value = <span class="hljs-string">`\nexport const prerender = true; \n <span class="hljs-subst">${firstChildNode.value}</span>`</span>;

            log({
              silent,
              <span class="hljs-attr">message</span>: <span class="hljs-string">"Added 'prerender' export to frontmatter"</span>,
            });
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// No frontmatter in this astro component. Add frontmatter node and default export</span>
            log({
              silent,
              <span class="hljs-attr">message</span>: <span class="hljs-string">"No frontmatter, going ahead to add one"</span>,
            });

            <span class="hljs-comment">// "unshift" to add this to the start of the list, i.e., the first child</span>
            node.children.unshift({
              <span class="hljs-attr">type</span>: <span class="hljs-string">"frontmatter"</span>,
              <span class="hljs-attr">value</span>: <span class="hljs-string">"\nexport const prerender = true\n"</span>,
            });
          }
        }
      });

      <span class="hljs-comment">//serialise the AST and return the result</span>
      <span class="hljs-keyword">const</span> result = serialize(ast);

      <span class="hljs-comment">// added for the reader's debugging</span>
      <span class="hljs-built_in">console</span>.log(result);
      <span class="hljs-keyword">return</span> result;
    },
  };
};
</code></pre>
<p>The code block above is annotated. Please take a close look at it. If something is unclear, add some <code>console.log</code>s. Together with the annotation, I’m sure you’ll understand the explanations even better.</p>
<h3 id="heading-manual-testing">Manual testing</h3>
<p>We have our solution complete. Now, let’s test it. First, build the project with <code>npm run build</code>, and even though we have a <code>server</code> output in the Astro config, we now have the <code>index.astro</code> page statically built by default.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-12-at-16.10.58@2x.png" alt="Pre-rendering the index.astro static route." width="600" height="400" loading="lazy">
<em>Pre-rendering the index.astro static route.</em></p>
<p>To render a server-side page, we need to manually add <code>export const prerender = false</code>.</p>
<p>Create a new page with identical content as <code>index.astro</code> and have the <code>prerender</code> export.</p>
<pre><code class="lang-js">---
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> prerender = <span class="hljs-literal">false</span>;
---

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/svg+xml"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/favicon.svg"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"generator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{Astro.generator}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>SSR<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>SSR<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
</code></pre>
<p>Now rerun the build and notice how only the <code>index.astro</code> page is pre-rendered.</p>
<p><img src="https://blog.ohansemmanuel.com/content/images/2023/06/CleanShot-2023-04-12-at-16.17.15@2x.png" alt="Skipping prerender when export is found." width="600" height="400" loading="lazy">
<em>Skipping prerender when export is found.</em></p>
<h2 id="heading-how-to-build-renderers-and-library-integrations">How to Build Renderers and Library Integrations</h2>
<p>As stated earlier in the chapter, the focus here is feature integrations. For building renderers and library integrations, I strongly recommend taking a look at the source code for popular integrations such as:</p>
<ul>
<li>The <a target="_blank" href="https://github.com/withastro/astro/tree/main/packages/integrations/react">React</a> , <a target="_blank" href="https://github.com/withastro/astro/tree/main/packages/integrations/preact">Preact</a>or <a target="_blank" href="https://github.com/withastro/astro/tree/main/packages/integrations/vue">Vue</a> renderer integrations.</li>
<li>The <a target="_blank" href="https://github.com/withastro/astro/tree/main/packages/integrations/tailwind">Tailwind</a> or <a target="_blank" href="https://github.com/withastro/astro/tree/main/packages/integrations/partytown">partytown</a> library integrations.</li>
</ul>
<p>Most of these integrations are barely 100 lines of code at the core. Dig into them!</p>
<h2 id="heading-wrapping-up-this-chapter-7">Wrapping Up This Chapter</h2>
<p>Building custom integrations is definitely something you can do. Heck! Writing compilers isn’t a prerequisite.</p>
<p>Building upon the explanations and examples discussed here, we’ve seen how mere mortals like us can reach down into the internals of Astro and bend it to our will. Now, put this knowledge to practice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-141.png" alt="Image" width="600" height="400" loading="lazy">
<em>The end.</em></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Look who made it to the end! 🚀</p>
<p>Yes, you!</p>
<p>I’ve poured my heart into these chapters, and I’m sure you’ve learned a thing or two.</p>
<p>So, where do you go next?</p>
<p>Firstly, I strongly recommend visiting the official Astro <a target="_blank" href="https://astro.build/">documentation</a>. It’s a great resource that’ll benefit you long-term as you develop Astro applications.</p>
<p>Secondly, ponder the features that make Astro stand out:</p>
<ul>
<li><strong>Leverage Component Islands</strong>: A new web architecture for building faster websites.</li>
<li><strong>Zero JS, by default</strong>: Keep applications fast with no JS runtime overhead.</li>
<li><strong>Edge-ready</strong>: Deploy anywhere, even global edge runtimes like Deno or Cloudflare.</li>
<li><strong>Incredibly customizable</strong>: Use Tailwind, MDX, and 100+ other <a target="_blank" href="https://astro.build/integrations/">integrations</a>.</li>
<li><strong>Bring your own framework</strong>: Supports React, Preact, Vue, Svelte, Solid, Lit and more.</li>
</ul>
<h2 id="heading-helpful-links-and-resources">Helpful links and resources</h2>
<ul>
<li>⚠️ <a target="_blank" href="https://www.ohansemmanuel.com/newsletter">Stay in touch with my work</a> and be first to know about updates to this book (and my other writings). <a target="_blank" href="https://www.ohansemmanuel.com/newsletter">Do so here</a>.</li>
<li><a target="_blank" href="https://astro.build/integrations/">Astro integrations</a>: explore these to add more functionality to your Astro applications.</li>
<li><a target="_blank" href="https://astro.build/themes">Astro themes</a>: explore themes you can start your new project with.  </li>
</ul>
<p>Until next time,</p>
<p><a target="_blank" href="https://www.ohansemmanuel.com/">Ohans E.</a>🥂</p>
<h2 id="heading-want-to-get-the-ebook-1">Want to get the ebook?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/book-cover-transparent-1.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://github.com/understanding-astro/understanding-astro-book">Download ebooks on Github</a></em></p>
<ul>
<li>500+ pages of value</li>
<li>4+ practical project chapters</li>
<li>100+ carefully crafted illustrations and images</li>
<li>Learn techniques to build faster applications </li>
<li><strong>Integrate React, Svelte, Vue, Tailwind</strong> and more into an Astro project </li>
<li>Learn to build your own <strong>component islands implementation</strong> from scratch</li>
<li>Learn to <strong>build fullstack applications with Astro</strong> (without sacrificing performance) </li>
<li>Go <strong>beyond the basics</strong> and parse Astro code into ASTs and build custom project features </li>
</ul>
<p><em><a target="_blank" href="https://ohans.me/ua-github">Download the free ebook on Github.</a></em> </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Data Visualization is Important for UX Design ]]>
                </title>
                <description>
                    <![CDATA[ These days, we find ourselves consuming vast amounts of information, at an unprecedented rate. From online transactions, to social media interactions, to scientific research, the complexity of data grows exponentially. This reveals a challenge — how ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-data-visualization-is-important-for-ux-design/</link>
                <guid isPermaLink="false">66d03a45ab216b411575949d</guid>
                
                    <category>
                        <![CDATA[ data visualization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ux design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Faith Olohijere ]]>
                </dc:creator>
                <pubDate>Thu, 01 Jun 2023 15:39:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/luke-chesser-JKUTrJ4vK00-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, we find ourselves consuming vast amounts of information, at an unprecedented rate. From online transactions, to social media interactions, to scientific research, the complexity of data grows exponentially. This reveals a challenge — how to transform this raw data to actionable insight. </p>
<p>This is where data visualization comes in. Data visualization is a very important concept we should all consider when designing for our users.</p>
<p>In this article, you'll learn what data visualization is, how it relates to UX Design, and challenges faced when designing for data visualization.</p>
<h2 id="heading-what-is-data-visualization">What is Data Visualization?</h2>
<p>Data Visualization can be defined as being able to view data in designs. This could be done using charts, maps, tables, and so on. Data visualization simply enables users to see and understand data, without stressing their head or eyes. </p>
<p>By leveraging the power of visual perception, data visualization empowers individuals and organizations to gain deeper insights, and make informed decisions.</p>
<h2 id="heading-how-does-data-visualization-relate-to-ux-design">How Does Data Visualization Relate to UX Design?</h2>
<p>You might be wondering if and how data visualization relates to UX design. After all, isn't data all about numbers?</p>
<p>They do relate! You see, data visualization isn't just about numbers. It's about representing these numbers in a way that makes sense to the user, and also helps them make decisions. </p>
<p>As UX designers, our primary goal is to solve a problem for our users. When we design for data visualization, we're reducing the strain of going through bulky amounts of data for our users, and generally making their lives easier.</p>
<h2 id="heading-why-is-data-visualization-important">Why is Data Visualization important?</h2>
<p>Data Visualization is important for so many reasons, some of which are:</p>
<ul>
<li>Enhancement of understanding: Just putting out data might confuse a particular user or customer. Using visuals to interpret the data makes it easier and more fun for the users.</li>
<li>Communication of insight: Data visualization helps communicate insights on a digital platform. Users will be able to understand the data, data sources and how it's being analyzed.</li>
<li>Better user experience: Overall, data visualization helps in making sure your user has a better experience while interacting with your platform. You're generally trying to make their lives easier and reduce information overload.</li>
<li>Informed decision-making: Helping the user understand the data better, will keep them informed, and help them make better decisions which can boost their businesses.</li>
</ul>
<h2 id="heading-challenges-of-designing-for-data-visualization">Challenges of Designing for Data Visualization</h2>
<p>Data visualization is so easy to get wrong. Because of the delicate nature of data and how broad it can appear, designers might face some challenges when trying to help users visualize data. Some of these challenges include:</p>
<ul>
<li>Bulky/complex data: The bulky nature of the data might make it difficult to represent the data using visuals. Also, simplifying the data without losing information can be very tricky.</li>
<li>Cluttered interface: Somewhat related to the previous point, the overwhelming nature of the data can make the design look cluttered. Because you're designing with a lot of data, you might end up having a messy interface, while trying to put it all on a screen. You have to find a way to make sure your user interfaces don't look cluttered with an overload of information.</li>
<li>Usage of color: Despite the fact that we want the interface to look visually appealing, we have to consider the fact that colors can distract users from the overall intention, which is to communicate data to the users. Therefore, we should figure out a way to strike the balance, especially when differentiating color categories and highlighting important information.</li>
<li>Background of the target audience: You should also consider the background and understanding of your target audience. Some users might not have technical knowledge so, it would be best not to use jargon while designing for them.</li>
</ul>
<h2 id="heading-how-to-design-efficiently-for-data-visualization">How to Design Efficiently for Data Visualization</h2>
<p>To effectively design for data visualization and correctly interpret data for your users, you should consider these tips:</p>
<h3 id="heading-define-the-purpose">Define the purpose</h3>
<p>First of all, you'll want to define your purpose for the design. This is a common step in most designs. You need to understand the purpose of the visualization – is it to convey trends, monitor business growth, and so on. </p>
<p>Also, consider the domain, requirements, and possible constraints. This will help you determine the best visualization type to use.  </p>
<p>For example, say you're designing a data visualization for a social media tracking platform. The purpose here is to track the growth of the social media account, note what posts are most engaged with, and basically providing actionable insights to help the user make decisions that would further boost the account.</p>
<h3 id="heading-know-your-audience">Know your audience</h3>
<p>Another tip for designing effectively is to know your audience. You have to understand what your audience needs before figuring out how to help them. </p>
<p>You can conduct some research on the kind of people that would use the data visualization tool for the domain you're working on.</p>
<p>Using the same example as the first tip, your audience would generally be social media managers, brand ambassadors, or others who work primarily in the social media space. Do note that the audience may vary in terms of age, expertise, and personal preferences. </p>
<h3 id="heading-choose-the-visualization-type">Choose the visualization type</h3>
<p>Here, you choose the best visualization type for conveying your information. You can decide to use bar charts, tables, graphs, and so on, to represent your data.</p>
<p>Continuing with our example, you'd want to select visualizations that effectively represent progress over time, such as bar charts, progress charts, and so on. You might also want to analyze key data such as active followers. Would you use a bar chart or a line chart to do so? This is what choosing the visualization type entails.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/15319232948826_guardianmasterclass-dataviz-1292.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Examples of some data visualization charts. Image from [OpenClassrooms](https://www.google.com/url?sa=i&amp;url=https%3A%2F%2Fopenclassrooms.com%2Fen%2Fcourses%2F4555961-apply-metrics-to-design-decisions%2F5403056-decide-on-an-approach-to-present-information&amp;psig=AOvVaw11EJaN3uJ8zzLGJ7VRciQd&amp;ust=1685717146266000&amp;source=images&amp;cd=vfe&amp;ved=0CBEQjRxqFwoTCKjA</em>--nov8CFQAAAAAdAAAAABAR)_</p>
<h3 id="heading-have-an-intuitive-and-simple-layout">Have an intuitive and simple layout</h3>
<p>Here, you'd have to think of the kind of layout you want to use for your design. You would want to go for a simple layout that would help your users easily understand the visuals.</p>
<p>For the social media data visualization, you might want to: </p>
<ul>
<li>Incorporate a clear hierarchy of elements using size, colors, and placement.</li>
<li>Use familiar patterns like icons or symbols, to represent different social media metrics.</li>
<li>Embrace a minimalist approach by displaying essential information only. </li>
<li>Design a clear navigation system so users would always understand their current location on the application. </li>
</ul>
<p>Having an intuitive layout can help reduce information overload on your users. When elements are arranged logically, and the visualization is simple and free of clutter, your users are sure to have a great experience.</p>
<h3 id="heading-use-accessible-colors">Use accessible colors</h3>
<p>Next, choose colors that are easy on the eyes. Aside from the background and primary colors, you might use color coding to differentiate between different metrics/elements. </p>
<p>For example, you may decide to get the gender demography of the audience for the social media account. To represent this data, you could use a circle graph (doughnut), and choose different colors to represent the genders.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Circle-Chart.png" alt="Image" width="600" height="400" loading="lazy">
<em>An example of a circle graph with each element differentiated by colors.</em></p>
<p>Please note that accessibility isn't only for people with disabilities. Make sure to use colors that don't distract the user from important elements and information. You can use <a target="_blank" href="https://www.freecodecamp.org/news/the-60-30-10-rule-in-design/">the 60-30-10 rule</a> to choose a color palette that improves understanding and aids interpretation.</p>
<h3 id="heading-a-data-visualization-dashboard">A Data Visualization Dashboard</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Desktop---6--3-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of a dashboard for a Data Visualization Tool</em></p>
<p>The image above shows a dashboard for a social media data visualization tool. The dashboard contains performance metrics, analytics of likes, followers and comments on the account. The dashboard also contains the demographics of the audience, engagement rates and posts activity. </p>
<p>All these are important data analytics information which are useful to the owner of the account.</p>
<p>You can see how charts and bar graphs are used to interpret the data. Imagine having one big list which has the demographics of each user, each like and comment posted, everyone who engaged with each posts, and so on. It would be a bit overwhelming and hard to sift through to find insights.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The average person consumes about 40-75 gigabytes of data per day. Now, that's quite a lot. Imagine having to take in this huge amount of data through cluttered and disorganized interfaces. This would make it difficult to understand and interpret the data.</p>
<p>As designers, it's our job to ease the experiences of users while interacting with our products. We have to focus and align our design choices with the purpose and audience, as well as continuously evaluate our visualizations to ensure they effectively convey insights and appropriately inform the viewers. Remember, this gets better with practice.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Component-Based Architecture in Medusa – How to Build Robust User Interfaces ]]>
                </title>
                <description>
                    <![CDATA[ Medusa is a modern JavaScript framework that makes it easy to build robust user interfaces.  It is built around a component-based architecture, which is a design pattern that breaks down a UI into smaller, reusable components. This makes it easier to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/exploring-component-based-architecture-in-medusa-building-robust-user-interfaces/</link>
                <guid isPermaLink="false">66ba610abab56b945824004b</guid>
                
                    <category>
                        <![CDATA[ components ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ valentine Gatwiri ]]>
                </dc:creator>
                <pubDate>Thu, 18 May 2023 18:22:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-18-21-05-01.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Medusa is a modern JavaScript framework that makes it easy to build robust user interfaces. </p>
<p>It is built around a component-based architecture, which is a design pattern that breaks down a UI into smaller, reusable components. This makes it easier to maintain and update the UI, as well as to create new features.</p>
<p>Medusa is like a LEGO set for building user interfaces. Just as LEGO bricks can be assembled and combined to create various structures, Medusa allows you to build robust UIs by assembling and combining reusable components.</p>
<p>In this article, we'll explore the component-based architecture in Medusa. We will start by discussing the benefits of using this architecture, and then we will provide some code examples to show how you can use it to build user interfaces.</p>
<h2 id="heading-benefits-of-component-based-architecture">Benefits of Component-Based Architecture</h2>
<p>There are many benefits to using the component-based architecture in Medusa. Some of the most important benefits include:</p>
<ul>
<li><strong>Reusability:</strong> Components can be reused in multiple places throughout the UI, which can save time and effort.</li>
<li><strong>Maintainability:</strong> Components are isolated from each other, which makes it easier to maintain and update the UI.</li>
<li><strong>Scalability:</strong> Components can be easily added to or removed from the UI, which makes it easy to scale the UI as needed.</li>
<li><strong>Testability:</strong> Components can be easily tested in isolation, which makes it easier to ensure that they are working properly.</li>
</ul>
<h2 id="heading-understanding-the-medusa-framework">Understanding the Medusa Framework</h2>
<p>Medusa is a comprehensive framework and toolset for building e-commerce applications. It provides the necessary components and infrastructure to develop, deploy, and manage online stores.</p>
<h3 id="heading-the-medusa-backend">The Medusa Backend</h3>
<p>The backend of Medusa focuses on server-side operations and manages the core functionalities of the e-commerce application. It includes components such as:</p>
<ul>
<li><strong>Server</strong>: Medusa provides a server that handles the logic and data management of the application. It facilitates communication between the frontend and various services, such as databases and payment gateways.</li>
<li><strong>APIs</strong>: The backend exposes a set of APIs (Application Programming Interfaces) that allow the frontend and other applications to interact with the server. These APIs define the endpoints and data structures for performing actions like fetching products, processing orders, and managing user accounts.</li>
<li><strong>Business Logic</strong>: The backend of Medusa implements the business rules and workflows specific to the e-commerce domain. It handles tasks such as inventory management, order processing, payment handling, and applying discounts or promotions.</li>
<li><strong>Database Integrations</strong>: The backend interacts with a database to store and retrieve data related to products, orders, customers, and other entities. Medusa supports various databases, including PostgreSQL and MySQL, and provides an abstraction layer to simplify database operations.</li>
</ul>
<p>The Medusa backend contains the following directories and files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-44-27.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa backend</em></p>
<h3 id="heading-the-medusa-frontend">The Medusa Frontend</h3>
<p>The frontend of Medusa is responsible for the user-facing part of the e-commerce application. It focuses on providing an interactive and engaging interface for customers to browse products, add items to their cart, and complete the purchase. The frontend includes:</p>
<ul>
<li><strong>The Storefront</strong>: The storefront component represents the application where users interact with the e-commerce store. It includes product listings, search functionality, product details, shopping cart, and checkout flow. The frontend is responsible for rendering these components and handling user interactions.</li>
<li><strong>The User Interface (UI)</strong>: The frontend defines the visual layout, design, and user experience of the e-commerce application. It utilizes HTML, CSS, and JavaScript to create responsive and user-friendly interfaces. Medusa provides UI components and templates that can be customized and extended to match the branding and requirements of the online store.</li>
<li><strong>Integration with Backend APIs</strong>: The frontend interacts with the backend APIs provided by the Medusa server. It communicates with the server to fetch product data, submit orders, update user information, and perform other operations. The frontend consumes the data returned by the backend APIs and uses it to render dynamic content on the user interface.</li>
</ul>
<p>The Medusa frontend contains the following directories and files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-45-21.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa storefront</em></p>
<p>Medusa is written in TypeScript and uses React as its frontend framework. Medusa is divided into three main parts: the <code>server</code>, the <code>storefront</code>, and the <code>admin</code> panel. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-33-22.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Medusa is a complete e-commerce solution that includes various tools and components:</p>
<h3 id="heading-server">Server</h3>
<p>Medusa provides a server that handles the core functionalities of an e-commerce application, such as managing product catalogs, handling orders and payments, and managing user accounts.</p>
<p>Here is how to access the backend:</p>
<pre><code>cd my-medusa-store/backend
yarn start
</code></pre><p>To get started with Medusa, you can check out <a target="_blank" href="https://gatwirival.hashnode.dev/what-is-medusajs-and-why-use-it">this article</a>.</p>
<p>When we run <code>yarn start</code> in the backend, by default, <a target="_blank">localhost:9000</a> is used.</p>
<p>Navigate to <a target="_blank">localhost:9000/store/products</a> in your browser to view a JSON collection of items. Because the seeder only inserts one product, it will only contain one item in the JSON object.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-10-50-50-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-storefront">Storefront</h3>
<p>Medusa offers a storefront component, which is the user-facing part of the application. It includes the website or interface where customers browse products, add items to their cart, and proceed with the purchasing process.</p>
<pre><code>cd my-medusa-store/storefront
yarn develop # <span class="hljs-keyword">for</span> Gatsby storefront
yarn dev # <span class="hljs-keyword">for</span> Next.js storefront
</code></pre><p>Since I'm using Next.js in my store front, I'll be using <code>yarn dev</code>.When we run <code>yarn dev</code> in the storefront, by default, <a target="_blank">localhost:8000</a> is used.</p>
<p>Here's what the storefront should look like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-11-58-02.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-admin-panel">Admin Panel</h3>
<p>Medusa provides an admin panel that enables the store owners or administrators to manage the e-commerce operations. It allows them to add and update products, process orders, handle inventory, configure shipping options, and perform other administrative tasks.</p>
<h2 id="heading-how-to-set-up-the-medusa-admin-dashboard">How to Set Up the Medusa Admin Dashboard</h2>
<p>To set up the Medusa admin dashboard, follow these steps:</p>
<p>To install the package, you'll have to navigate to the directory of your Medusa backend and run the following command to install the admin dashboard:</p>
<pre><code>yarn add @medusajs/admin
</code></pre><p>If you use npm, you can use the command below:</p>
<pre><code>npm install @medusajs/admin
</code></pre><h3 id="heading-how-to-enable-the-admin-plugin-in-the-medusa-configuration-file">How to Enable the Admin Plugin in the Medusa Configuration File</h3>
<p>To enable the admin plugin, open the <code>medusa-config.js</code> file in your project and locate the <code>plugins</code> array, then add the following lines:</p>
<pre><code><span class="hljs-keyword">const</span> plugins = [
  <span class="hljs-comment">// ...</span>
  {
    <span class="hljs-attr">resolve</span>: <span class="hljs-string">"@medusajs/admin"</span>,
    <span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('@medusajs/admin').PluginOptions}</span> </span>*/</span>
    <span class="hljs-attr">options</span>: {
      <span class="hljs-comment">// ...</span>
    },
  },
]
</code></pre><p>The admin plugin accepts various options for customization:</p>
<ul>
<li><code>serve</code> (default: <code>true</code>): A boolean indicating whether to serve the admin dashboard when the Medusa backend starts. Set it to <code>false</code> if you prefer to serve the admin dashboard separately using the <code>yarn dev</code> command.</li>
<li><code>path</code> (default: <code>"app"</code>): A string indicating the path on which the admin server should run. It should not be prefixed or suffixed with a slash ("/"), and it cannot be one of the reserved paths: <code>"admin"</code> and <code>"store"</code>.</li>
<li><code>outDir</code>: Optional path specifying where to output the admin build files.</li>
<li><code>autoRebuild</code> (default: <code>false</code>): A boolean indicating whether the admin UI should be automatically rebuilt if there are any changes or if a missing build is detected when the backend starts. If not set, you must manually build the admin dashboard.</li>
</ul>
<p>You can enable <code>autoRebuild</code>  by setting it to <code>true</code> in the plugin options to build the admin UI.</p>
<p>Run the admin dashboard using the <code>yarn dev</code> command. </p>
<p>This command starts both the Medusa Backend and the admin dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-13-23-52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>By default, the admin dashboard will be accessible at <a target="_blank">localhost:9000/app</a>. If you have set a custom <code>path</code> option, the admin will be available at <code>localhost:9000/&lt;PATH&gt;</code>, with <code>&lt;PATH&gt;</code> being the value of the <code>path</code> option. You can learn more <a target="_blank" href="https://docs.medusajs.com/admin/quickstart">here</a>.</p>
<p>Make sure to adjust the configurations and paths according to your specific setup.</p>
<p>By following these steps, you should be able to successfully set up and access the Medusa admin dashboard.</p>
<h2 id="heading-understanding-medusas-component-based-architecture">Understanding Medusa's Component-Based Architecture</h2>
<p>Medusa uses a component-based architecture to make it easy to extend and customize. Each component is self-contained and can be reused in other parts of the app. This makes it easy to add new features or change the look and feel of the app without affecting other parts of the codebase.</p>
<p>Here is an example of a component from the Medusa starter app located in the <code>storefront/src/modules/components/empty-cart-message</code> folder:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> UnderlineLink <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/components/underline-link"</span>

<span class="hljs-keyword">const</span> EmptyCartMessage = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-amber-100 px-8 py-24 flex flex-col justify-center items-center text-center"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl-semi"</span>&gt;</span>Your shopping bag is empty<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-base-regular mt-4 mb-6 max-w-[32rem]"</span>&gt;</span>
        You don<span class="hljs-symbol">&amp;apos;</span>t have anything in your bag. Let<span class="hljs-symbol">&amp;apos;</span>s change that, use
        the link below to start browsing our products.
      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">UnderlineLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/store"</span>&gt;</span>Explore products<span class="hljs-tag">&lt;/<span class="hljs-name">UnderlineLink</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> EmptyCartMessage
</code></pre>
<p>This component renders a message when the shopping cart is empty. The message includes a link to the store so that users can start browsing products.</p>
<p>Here is a breakdown of the code:</p>
<p>The <code>import UnderlineLink from "@modules/common/components/underline-link"</code> statement imports the <code>UnderlineLink</code> component from the <code>@modules/common/components</code> module. This component will be used to render the link to the store.</p>
<p>The <code>const EmptyCartMessage = () =&gt; {</code> statement defines the <code>EmptyCartMessage</code> component. This component takes no props and returns a <code>div</code> element with the following attributes: <code>className="bg-amber-100 px-8 py-24 flex flex-col justify-center items-center text-center"</code>.</p>
<p>The <code>return</code> statement returns the <code>div</code> element with the following content:</p>
<ul>
<li>An <code>h1</code> element with the text "Your shopping bag is empty".</li>
<li>A <code>p</code> element with the text "You don't have anything in your bag. Let's change that, use the link below to start browsing our products.".</li>
<li>A <code>div</code> element with the <code>UnderlineLink</code> component that links to the store.</li>
</ul>
<h2 id="heading-medusa-vs-shopify">Medusa vs Shopify</h2>
<p>Medusa is an open-source alternative to Shopify. Here are some comparisons between the two products:</p>
<h3 id="heading-customization-and-flexibility">Customization and Flexibility</h3>
<p>Medusa offers a high level of customization and flexibility. It allows developers to create highly tailored e-commerce applications that align with specific business requirements. With Medusa, developers have full control over the application's codebase and can customize various aspects, including the frontend and backend functionalities.</p>
<p>Shopify provides a less granular level of customization compared to Medusa. It operates as a hosted platform, meaning that the core infrastructure and backend are managed by Shopify. While Shopify offers a theme customization system and an app ecosystem to extend functionality, it may have limitations when it comes to implementing highly custom features.</p>
<h3 id="heading-hosting-and-infrastructure">Hosting and Infrastructure</h3>
<p>Medusa is a self-hosted framework, which means that developers need to set up their own hosting environment and manage the infrastructure themselves. This provides more control and scalability, but also requires additional technical expertise and resources.</p>
<p>Shopify is a fully hosted platform, meaning that hosting and infrastructure management are handled by Shopify. This eliminates the need for developers to set up and maintain hosting environments, making it more accessible to non-technical users.</p>
<h3 id="heading-pricing">Pricing</h3>
<p>As an open-source framework, Medusa is free to use, and there are no licensing fees. However, since it requires self-hosting, there may be associated costs for hosting services and infrastructure management.</p>
<p>Shopify operates on a subscription-based pricing model. It offers different pricing plans with varying features and transaction fees. The subscription fees cover hosting, security, and support services.</p>
<h3 id="heading-ecosystem-and-app-integrations">Ecosystem and App Integrations</h3>
<p>Medusa has a growing ecosystem of extensions and integrations, allowing developers to leverage third-party tools and services to enhance their e-commerce applications. While the ecosystem may not be as extensive as Shopify's, Medusa's open-source nature enables developers to create custom integrations as needed.</p>
<p>Shopify has a vast ecosystem of apps and integrations available through the Shopify App Store. These apps can add functionality, extend features, and integrate with various services such as marketing, analytics, and shipping providers. The extensive app ecosystem of Shopify is one of its major strengths.</p>
<h3 id="heading-target-audience">Target Audience</h3>
<p>Medusa is suited for businesses that require advanced customization and flexibility in their e-commerce applications. It is a suitable choice for developers and businesses with technical expertise looking for full control over their e-commerce infrastructure.</p>
<p>Shopify is designed to cater to a wide range of users, including small to medium-sized businesses and individuals, who want a user-friendly and hassle-free e-commerce solution. It is suitable for users with limited technical knowledge or resources who prioritize ease of use and convenience.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The component-based architecture is a powerful design pattern that can be used to build robust user interfaces in Medusa. By using components, you can create reusable, maintainable, scalable, and testable UIs.</p>
<p>Medusa is a comprehensive e-commerce framework that includes a server, a storefront component, and an admin panel. It provides developers with the necessary tools to build customizable and scalable e-commerce applications, tailored to specific business needs.</p>
<p>I hope this article has been helpful. Please let me know if you have any questions. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ TailwindCSS vs NextUI – How to Choose a UI Framework ]]>
                </title>
                <description>
                    <![CDATA[ If you're a developer, choosing the proper UI framework can be tough. This is partly because there are so many options to choose from, each with its strengths and weaknesses.  In this guide, I will discuss the differences between two popular framewor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/tailwindcss-vs-nextui-how-to-choose-a-ui-framework/</link>
                <guid isPermaLink="false">66c4c65399f22436b71945dc</guid>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sophia Iroegbu ]]>
                </dc:creator>
                <pubDate>Tue, 28 Feb 2023 01:20:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/pexels-xxss-is-back-777001--2-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're a developer, choosing the proper UI framework can be tough. This is partly because there are so many options to choose from, each with its strengths and weaknesses. </p>
<p>In this guide, I will discuss the differences between two popular frameworks: Tailwind CSS and NextUI. </p>
<p>Tailwind CSS is a framework that has gained a large following among developers due to its simplicity, flexibility, and ease of use. NextUI, on the other hand, is popular because of its scalability, performance, and flexibility. </p>
<p>This article will compare the critical features of the two frameworks, their pros and cons, and their suitability for different project types. </p>
<p>Whether you are a developer looking to select the best framework for your project or you're just curious about these tools, this article is for you. </p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ul>
<li>What is Tailwind CSS and what are its pros and cons?</li>
<li>What is NextUI and what are its pros and cons?</li>
<li>How to decide between Tailwind and NextUI</li>
</ul>
<p>Let’s dive in! 🚀</p>
<h1 id="heading-what-is-tailwind-css">What is Tailwind CSS?</h1>
<p><a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a> is a utility-first CSS framework – that is, it's a framework that focuses on functionality rather than its looks or UI. <a target="_blank" href="https://adamwathan.me/">Adam Wathan</a>, a Canadian Software developer and entrepreneur, built Tailwind CSS. </p>
<p>Tailwind allows you to completely customize your utility classes without leaving the HTML. Tailwind CSS offers a fast and easy way to style your websites. </p>
<p>Tailwind CSS is highly customizable. This means as a user, you can modify the fonts, colors and other design elements to match your web design preferences. </p>
<p>This framework also has faster load time because of <a target="_blank" href="https://purgecss.com/">PurgeCSS</a> (which removes unused or unwanted CSS styles from your final build resulting in smaller file sizes). </p>
<h3 id="heading-pros-of-using-tailwind-css">Pros of using Tailwind CSS:</h3>
<p>Just as everything has advantages and disadvantages, here are the advantages of using tailwind CSS:</p>
<ul>
<li>Tailwind is easy to learn and use for first-time developers.</li>
<li>Tailwind uses a few lines of code to create flexible designs.</li>
<li>Tailwind can easily convert a design to reusable components since it is a utility-first framework.</li>
<li>Tailwind is a guide and avoids using unfamiliar names for CSS classes and ids.</li>
<li>Tailwind can effortlessly be customized to suit the developer’s needs.</li>
</ul>
<h3 id="heading-cons-of-using-tailwind-css">Cons of using Tailwind CSS:</h3>
<p>And now here are the disadvantages of using Tailwind CSS:</p>
<ul>
<li>Tailwind contains large CSS files because it provides low-level CSS classes, so when styling, the files may be too large and complex to maintain over time. This also affects the performance of the website or application.</li>
<li>Tailwind may have many low-level classes, but it is hard to override the styles of specific elements when changing designs or styling specific elements.</li>
<li>Tailwind is not always the best framework for some projects. It is undoubtedly a powerful tool, but specific design requirements may not be achievable using the available classes when working on a project. Tailwind is best for projects that involves a lot of styling UI and customizing design elements such as Prototyping, Dashboards, E-commerce websites and lots more.   </li>
</ul>
<h1 id="heading-what-is-nextui">What is NextUI?</h1>
<p><a target="_blank" href="https://nextui.org/">NextUI</a> is a component-based CSS framework, which means it provides a set of pre-designed components you can use at any time. Examples include buttons, forms, navigation bars, navigation menus, and more. </p>
<p>NextUI is built and maintained by the <a target="_blank" href="https://vercel.com/">Vercel</a> team. The beauty of NextUI is that it gives you a customizable template, so you don’t have to write much CSS code to create a responsive website. </p>
<p>The downside of using this framework is that developers won’t have 100% control of the design as the components are limited by NextUI. </p>
<p>In addition to its core UI components, NextUI offers developer tools such as automatic code splitting, server-side rendering, and built-in support for libraries like <a target="_blank" href="https://graphql.org/">GraphQL</a> and <a target="_blank" href="https://www.apollographql.com/">Apollo</a>.  </p>
<h3 id="heading-pros-of-using-nextui">Pros of using NextUI:</h3>
<p>Just as everything has advantages and disadvantages, here are the advantages of using NextUI:</p>
<ul>
<li>NextUI is highly scalable when handling large and complex applications if the application grows. It doesn’t slow down when handling such large files.</li>
<li>NextUI is very flexible and built to suit the user’s needs or tastes.</li>
<li>NextUI has a large, reliable <a target="_blank" href="https://discord.gg/9b6yyZKmH4">community</a> so getting help when using the tool is stress-free.</li>
<li>NextUI has an extensive range of pre-built components and features, which saves the developer's time when building.</li>
<li>NextUI is built on React, making it user-friendly to build a JavaScript application.</li>
</ul>
<h3 id="heading-cons-of-using-nextui">Cons of using NextUI:</h3>
<p>And now here are the disadvantages of using NextUI:</p>
<ul>
<li>NextUI has a large footprint compared to other frameworks despite being built on ReactJS. This can also affect its performance if the developer does not fully understand how to navigate NextUI.</li>
<li>NextUI may not be the best option when building an application that does not involve or use ReactJS.</li>
<li>NextUI may be relatively easy to use but has a learning curve that may be difficult to understand for those who don’t use React.</li>
<li>NextUI requires more attention in setting up and configuration because it offers a range of developer tools for customization.</li>
</ul>
<h1 id="heading-how-to-decide-between-nextui-and-tailwind">How to Decide Between NextUI and Tailwind</h1>
<table>
  <tbody><tr>
    <th>NextUI</th>
    <th>Tailwind CSS</th>
  </tr>
  <tr>
   <td>NextUI was built for rendering React applications.</td>
   <td>Tailwind aims at styling any website or web application easily. </td>
  </tr>
  <tr>
    <td>NextUI CSS is customizable and provides a wide range of developer tools and features to customize the pre-built components to the developer’s needs. </td>
    <td>Tailwind CSS is customizable but the only additional developer tool it provides is PurgeCSS. It doesn't provide tools for querying such as GraphQL.</td>
  </tr>
  <tr>
    <td>NextUI CSS is designed to help developers focus on the performance and scalability of their applications. </td>
    <td>Tailwind CSS is designed to help developers build user interfaces quickly and efficiently. </td>
 </tr>
  <tr>
    <td>NextUI is ideal for large React applications</td>
    <td>Tailwind CSS is ideal for small-scale React appplications</td>
 </tr>
  <tr>
    <td>NextUI CSS provides pre-built UI components and features that can be used to build websites and applications. </td>
    <td>Tailwind CSS provides a wide range of low-level CSS classes. This means tailwind gives the developers chance to build the UI components themselves. </td>
 </tr>
  <tr>
    <td>NextUI CSS may take more time in setting up and configuring.</td>
    <td>Tailwind CSS takes less time in setting up and configuring.</td>
 </tr>
</tbody></table>

<p>If you are working on a React application for a large-scale project, NextUI is the best choice as a CSS framework. But if you are working on an application that requires you to focus on specific functionality or design requirements, Tailwind is the best choice for you.   </p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In conclusion, Tailwind CSS and Next UI are different tools for developing web and mobile applications. </p>
<p>Tailwind CSS is a utility-first CSS framework designed to help developers build a custom user interface quickly. NextUI is a component-based framework designed to help developers improve the performance and scalability of the application. </p>
<p>Tailwind and NextUI each have strengths and weaknesses, and the best choice for your project depends entirely on the application’s design requirements, needs, and goals. </p>
<p>Tailwind CSS best suits developers looking for a simple solution to build a custom user interface. In contrast, NextUI is best suited for developers looking for CSS templates, so they can focus on other parts of the application, like scalability.</p>
<p>In the end, pick the best framework for you based on your preferences and skills as a developer. Before choosing, carefully evaluate the pros and cons of each tool to make an informed decision when building the best possible application for your users.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ UX vs UI – What's the Difference? Definition and Meaning ]]>
                </title>
                <description>
                    <![CDATA[ When designing a website or an app, the finished product needs to be functional, intuitive, and aesthetically pleasing. This is where UX and UI design come in. UX and UI design are two technical terms that can often be confusing. Many people may use ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ux-vs-ui-whats-the-difference-definition-and-meaning/</link>
                <guid isPermaLink="false">66b1e4c841fdb67461b85280</guid>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI UX ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ux design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dionysia Lemonaki ]]>
                </dc:creator>
                <pubDate>Fri, 14 Oct 2022 16:15:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/10/pexels-picjumbocom-196644.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When designing a website or an app, the finished product needs to be functional, intuitive, and aesthetically pleasing. This is where UX and UI design come in.</p>
<p>UX and UI design are two technical terms that can often be confusing.</p>
<p>Many people may use these two terms interchangeably, incorrectly, or assume they are the same thing. But that is not the case, as they are two different fields.</p>
<p>In a nutshell, UX design focuses on how well the product <em>works</em> and how the user <em>feels</em> when using it, whereas UI design focuses on how that software product <em>looks</em>. </p>
<p>Both are equally important and play a vital role when creating and developing software. </p>
<p>Think of it like this: a pretty UI may attract visitors, but if the product is not functional, the visitors won't stick around. And if the product works properly but doesn't look good, users will feel frustrated and look elsewhere to satisfy their needs.</p>
<p>There needs to be a balance.</p>
<p>With that said, the definition above only scratches the surface.</p>
<p>In this article, you will learn about the differences between UX and UI design and understand what UX and UI designers do in their day-to-day work.</p>
<p>Here is what we will cover:</p>
<ol>
<li><a class="post-section-overview" href="#ux-definition">What is UX design and why is it important?</a></li>
<li><a class="post-section-overview" href="#ux-job">What does a UX designer actually do? The UX design process explained</a><ol>
<li><a class="post-section-overview" href="#phase-1">Understanding phase</a></li>
<li><a class="post-section-overview" href="#phase-2">User research phase</a></li>
<li><a class="post-section-overview" href="#phase-3">Analyze and define phase</a></li>
<li><a class="post-section-overview" href="#phase-4">Ideate and design phase</a></li>
<li><a class="post-section-overview" href="#phase-5">Reporting phase</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#ui-intro">What is UI design?</a><ol>
<li><a class="post-section-overview" href="#ui-skills">What does a UI designer do?</a></li>
</ol>
</li>
<li><a class="post-section-overview" href="#differences">UX VS UI design - what's the difference?</a></li>
</ol>
<h2 id="heading-what-is-ux-design-and-why-is-it-important">What Is UX Design and Why Is It Important? <a></a></h2>
<p>UX design is short for user experience design.</p>
<p>The term was coined and popularized in the early 1990s by Donald Norman, an American researcher, professor, and author, during his time at Apple.</p>
<p>According to Norman:</p>
<blockquote>
<p>User experience encompasses all aspects of the end-user’s interaction with the company, its services, and its products. </p>
</blockquote>
<p>Note that the original definition of UX referred to physical and digital products. Nowadays, UX design mainly relates to digital products. </p>
<p>So, user experience design is a process that involves cultivating a good relationship between a company, the company's software products /services, and the company's clients/customers.</p>
<p>The relationship is based on how a user feels when using the company's products.</p>
<p>UX has to do with feelings, emotions, thoughts, behaviors, and human psychology.</p>
<p>Specifically, good UX depends on the following points:</p>
<ul>
<li>How does a person feel when using the product?</li>
<li>How accessible is it for the person to navigate the product? Can everyone use it?</li>
<li>How usable is the product? Does it work as intended?</li>
<li>How easy is it for the person to navigate and find the necessary information?</li>
<li>How well-planned and intuitive are the steps a user needs to take to accomplish a task?</li>
<li>How logical is the sequence of actions a user needs to perform to accomplish their goal with ease?</li>
<li>Does it make it easy for them to achieve their initial goal?</li>
<li>How well structured is the content available? Does it follow a logical hierarchy?</li>
<li>How useful is the product?</li>
<li>How valuable is the product?</li>
<li>Does it serve a purpose?</li>
</ul>
<p>All of the above are part of what makes good UX design and contribute to the value the software offers.</p>
<p>UX is all about solving problems and always keeping the user in mind.</p>
<p>The end goal is to provide a pleasant and positive experience for the user, create digital products that a user feels comfortable using, and for the user to accomplish their task and have their needs met.</p>
<p>All in all, it focuses on anything and everything that affects the user's journey when trying to solve a problem they have.</p>
<p>Good user experience is vital for every business. If the user/customer is happy and not frustrated, they are more likely to remain loyal.</p>
<p>If users can't use the product with ease, this will result in them giving up on it and looking for an alternative product/service that meets their needs, resulting in a loss for the business. They are unlikely to return to a product after a bad experience.</p>
<h2 id="heading-what-does-a-ux-designer-actually-do-the-ux-design-process-explained">What Does A UX Designer Actually Do? The UX Design Process Explained <a></a></h2>
<p>The UX design process is comprised of many parts. It is a sequential process with the end user always in mind throughout all stages.</p>
<p>The role of a UX designer will vary from business to business and depends on the size of the team they are on. </p>
<p>On a small team, a UX designer will probably be responsible for the whole UX process from start to finish. On a larger team, they might be involved in only one part of the process, such as UX research.</p>
<p>That means a UX designer may not be part of all the stages outlined in the following sections.</p>
<p>Now, let's see the different phases in the UX design process in more detail.</p>
<h3 id="heading-understanding-phase">Understanding Phase <a></a></h3>
<p>The first step in the UX design process is understanding the brand and its users.</p>
<p>Specifically, it is about understanding how the project the UX designer is working on aligns with the brand's mission, vision, and goals. </p>
<p>And it is about understanding the brand's users/customers. </p>
<p>A UX designer can't make assumptions about what the users really want or what problems they are facing when using the product/service. They will only know once they ask the users directly.</p>
<p>The UX designer needs to understand, be in tune with, and empathize with the problems users are facing and their pain points. They need to understand their users' wants and needs.</p>
<p>The key word here is <a target="_blank" href="https://www.freecodecamp.org/news/how-empathy-makes-you-a-better-software-engineer/">empathy</a>! Empathy is the ability to understand the user's hopes, goals, frustrations, and needs. </p>
<p>Once the UX designer has gathered those insights and identified the goals of the brand and the problems the users are facing, they can figure out how to solve problems and provide solutions through their designs and how they can create products/services that solve those problems.</p>
<h3 id="heading-user-research-phase">User Research Phase <a></a></h3>
<p>As mentioned in the section above, a UX designer can't assume, and if they do, they need to question those assumptions by conducting user research.</p>
<p>There are two main methods for conducting user research:</p>
<ul>
<li>Qualitative methods</li>
<li>Quantitative methods</li>
</ul>
<p>Qualitative research methods focus on observational and non-numerical findings, human behaviors, opinions, and emotions. </p>
<p>They include creating customer feedback surveys and asking open-ended questions to understand the key frustrations and pain points and what needs improving.</p>
<p>They also include reaching out to customers directly and conducting 1:1 face-to-face or online user interviews or focus groups of 3-5 target users for usability testing.</p>
<p>Quantitative methods focus on numbers, metrics, and measurable data points. </p>
<p>They include using web analytics tools and researching how the current design is performing. For example, they might find out how many visitors of an e-commerce site are clicking on a particular item and adding it to their cart, or what is the average time spent on the checkout page.</p>
<p>To learn more about user research, check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/why-you-need-a-ux-researcher-on-your-product-team/">Why you need a UX researcher on your product team</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/use-user-reseach-to-create-the-perfect-ui-design/">Use User Research to Create the Perfect UI Design</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/qualitative-vs-quantitative-definition-research-methods-and-data/">Qualitative VS Quantitative Definition – Research Methods and Data</a></li>
</ul>
<h3 id="heading-analyze-and-define-phase">Analyze and Define Phase <a></a></h3>
<p>After gathering user insights, the next phase in the UX design process is for the UX designer to analyze and make sense of all the information and define the problem they need to solve for the user. </p>
<p>They define what their focus should be and what would have the most impact on the users.</p>
<p>At this stage, it is crucial not to lose sight of the user's needs.</p>
<p>For this reason, the UX designer will create user personas based on the research they've gathered.</p>
<p>A user persona is a fictional character that will use the product/service. </p>
<p>It represents a typical target user and identifies their needs, frustrations, behaviors, and goals.</p>
<p>User personas allow UX designers to understand their users better and make wise design decisions.</p>
<p>At this stage, a UX designer might also create use cases. </p>
<p>A use case is a description of how different users might use and interact with the product or service.</p>
<p>Finally, a UX designer might also create journey maps.</p>
<p>A journey map is a visual and graphic representation of how the user will interact with the product/service from start to finish, and a map depicting the whole user journey until the user accomplishes their goal. </p>
<p>Journey maps are useful for understanding the different pain points a user faces when using the product and where those pain points occur, which leads to restructuring the product/service.</p>
<p>To learn more about user personas and use cases, check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-write-user-stories-epics-pesonas/">How To Write User Stories, Epics, &amp; Personas</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/use-cases-and-organizational-structure/">Use cases and organizational structure</a></li>
</ul>
<h3 id="heading-ideate-and-design-phase">Ideate and Design Phase <a></a></h3>
<p>The ideate phase is all about brainstorming and exploring out-of-the-box design ideas.</p>
<p>At this stage, the UX designer will come up with different creative options for how to solve the problem at hand.</p>
<p>They will brainstorm ideas and sketch them on paper before using any digital tools.</p>
<p>They will also create wireframes. Wireframes are rough prototypes and visual representations of what the UI and its various components will look like and which act as a guide and direction for the final design.</p>
<p>They will outline the steps and the complete path a user will take while using the product/service.</p>
<p>The end goal of this phase of the UX design process is to design low or high-fidelity versions of the structure and layout of the product (which may or may not be interactive) and of what the final design will look like. The first design is never the final design.</p>
<p>After continuously iterating the designs, adding new ideas and features,  rigorous testing, and feedback, the UX designer will have a detailed and carefully crafted digital mockup.</p>
<p>The mockup will look almost identical to the final product, which they hand off to the engineering team. The engineering team will build and ship the real-world working product.</p>
<p>To learn more about creating wireframes, check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-wireframe-ux-design-tutorial-website/">What is a Wireframe? This UX Design Tutorial Will Show You.</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/designing-a-website-ui-with-prototyping/">How to Design a Website Prototype from a Wireframe</a></li>
</ul>
<h3 id="heading-reporting-phase">Reporting Phase <a></a></h3>
<p>After the product launches, the next phase of the UX design process is the reporting phase. This includes conducting usability testing, observing how users are using the end product, collecting feedback, and gaining additional insights.</p>
<p>This phase helps the UX designer and their teams understand if the end product solves the initial problem and if it meets the needs of their end users.</p>
<p>At this phase, here are some of the questions that a UX designer may ask:</p>
<ul>
<li>"What did we do right?"</li>
<li>"What <em>didn't</em> we do right, and why?" </li>
<li>"What did we learn?"</li>
<li>"How can we improve in the future?"</li>
<li>"How are users responding to the product/service? Did we solve their problems and pain points?"</li>
</ul>
<p>With that said, the UX design process doesn't end here, since there are continuous iterations and improvements to be made.</p>
<h2 id="heading-what-is-ui-design-and-why-its-important-an-overview-of-ui-design">What Is UI Design and Why It's Important? An Overview of UI Design   <a></a></h2>
<p>So far, you have seen that UX design involves every aspect of a user’s experience when using a product. So what exactly is UI design, then?</p>
<p>Back in the 1970s, to use a computer, you had to use a command line interface (or CLI for short), and users needed to know a programming language to communicate with the machine. </p>
<p>At that time, you needed a lot of technical knowledge and skills to complete a simple task with a computer.</p>
<p>That changed in the 1980s with the introduction of Graphical User Interfaces (or GUIs for short) and personal computers. </p>
<p>A mouse, application icons, buttons, menus, checkboxes, and folders replaced the cryptic command line. Anyone could use a computer. Users could easily create, update, move, and delete files and send emails – no coding required.</p>
<p>The people that created these early graphical user interfaces were called User Interface (or UI for short) designers.</p>
<p>Nowadays, the term user interface involves every visual part of a digital product/service that a user interacts with. This includes mobile apps, websites, screens, touchscreens, keyboards, and wearable technology such as smartwatches, to name a few.</p>
<p>You can think of it as the bridge between the user and technology.</p>
<p>A UI designer is responsible for designing every step that allows a user to interact with the digital product/service. This includes layouts, structure, buttons, colors, and animations.</p>
<p>UI design is all about the <em>look,</em> <em>feel</em>, and <em>aesthetics</em> of a digital product. It involves every visual aspect and appearance of the product the user interacts with.</p>
<p>The end goal of a UI designer is to make something visually appealing to the user that's easy to interact with.</p>
<p>Good UI is vital for drawing attention to potential customers and attracting new users. Everyone appreciates a well-designed, clean, intuitive, and modern design.</p>
<p>But how does all this relate to UX design? You can think of UI design as a subset of UX design and a small part of the UX design process. But it is a separate discipline in its own right. </p>
<h3 id="heading-what-does-a-ui-designer-do-ui-design-skills-definition-and-examples">What Does A UI Designer Do? UI Design Skills Definition and Examples <a></a></h3>
<p>UI designers need to have visual design skills.</p>
<p>Specifically, they need to know the following concepts and disciplines:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-increase-user-engagement-with-your-app-using-color-theory-7c6f5c632570/">Color theory</a>, creating color palettes and creating accessible contrast that ensures both usability and readability.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-design-good-typography/">Typography</a>.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-layout-and-design-a-website-without-any-design-skills-86d94e40b55a/">Layout</a>, spacing, structure, and effective placement of elements on a page.</li>
<li>Graphics, icons, and illustration design.</li>
<li>Interaction design principles, animation effects, and motion design.</li>
<li>Branding – creating a consistent visual identity for a brand/product and communicating the brand's mission and message.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/master-responsive-website-design/">Responsive design</a>, and ensuring the UI looks good on all screen sizes and devices.</li>
<li>Creating <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-a-style-guide-in-figma/">style guides</a>.</li>
<li>Creating <a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-create-a-design-system-in-figma/">design systems</a>.</li>
<li>Knowledge of industry tools such as <a target="_blank" href="https://www.freecodecamp.org/news/ui-design-with-figma-tutorial/">Figma</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/ui-ux-design-tutorial-from-zero-to-hero-with-wireframe-prototype-figma/">Wireframing and prototyping</a>.</li>
</ul>
<h2 id="heading-ux-vs-ui-design-whats-the-difference-the-difference-between-ux-and-ui-design-explained-in-plain-english">UX VS UI Design – What's the Difference? The Difference Between UX and UI Design Explained in Plain English <a></a></h2>
<p>By this point, you hopefully understand what UX and UI design involves.</p>
<p>To summarize their differences:</p>
<p>UX design is how things <em>work</em> and how <em>useful</em> they are. UX design focuses on the whole experience and the journey a user takes when using a product from start to finish. It involves the overall experience a user has when trying to solve a problem. </p>
<p>UX requires knowledge of psychology, cognitive science, human behavior, identifying the user's pain points, and solving their problems.</p>
<p>UI design is how things <em>look</em>. UI design is a subset of UX design and is a more specialized field. It focuses only on the visual aspects that a user interacts with and creates inclusive, accessible, pleasant, and aesthetically pleasing digital interfaces. </p>
<p>UI design also focuses on creating a brand aesthetic that leads to an emotional connection with users/customers.</p>
<p>Both UX and UI design are equally important in creating useful, usable, and modern digital products and services and meeting the needs of their users.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This marks the end of the article – thank you so much for making it to the end!</p>
<p>Hopefully, this guide was helpful, and it gave you some insight into what UX and UI design are, what UX and UI designers do in their day-to-day work, and what the differences between those two terms are.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ CRUD Operations – What is CRUD? ]]>
                </title>
                <description>
                    <![CDATA[ Despite being commonly pronounced /krʌd/, CRUD is not a word. It’s an abbreviation that stands for Create, Read, Update, and Delete or Destroy. In this article, I will show you what CRUD means, and what the individual terms mean and do. I will also s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/crud-operations-explained/</link>
                <guid isPermaLink="false">66adf093f452caf50fb1fddb</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Wed, 15 Jun 2022 16:51:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/06/crud.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Despite being commonly pronounced <code>/krʌd/</code>, CRUD is not a word. It’s an abbreviation that stands for Create, Read, Update, and Delete or Destroy.</p>
<p>In this article, I will show you what CRUD means, and what the individual terms mean and do. I will also show you how create, read, update, and delete operations work in the real world.</p>
<h2 id="heading-what-well-cover">What We'll Cover</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-crud">What is CRUD?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-the-create-operation-and-how-does-it-work">What is the <code>CREATE</code> Operation and How Does it Work? </a></li>
<li><a class="post-section-overview" href="#heading-what-is-the-read-operation-and-how-does-it-work">What is the <code>READ</code> Operation and How Does it Work?</a></li>
<li><a class="post-section-overview" href="#whatisupdateoperationandhowdoesitwork">What is the <code>UPDATE</code> Operation and How Does it Work?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-the-delete-operation-and-how-does-it-work">What is the <code>DELETE</code> Operation and How Does it Work?</a> </li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-what-is-crud">What is CRUD?</h2>
<p>CRUD refers to the four basic operations a software application should be able to perform – Create, Read, Update, and Delete. </p>
<p>In such apps, users must be able to <strong>create data</strong>, have access to the data in the UI by <strong>reading</strong> the data, <strong>update</strong> or <strong>edit</strong> the data, and <strong>delete</strong> the data. </p>
<p>In full-fledged applications, CRUD apps consist of 3 parts: an API (or server), a database, and a user interface (UI). </p>
<p>The API contains the code and methods, the database stores and helps the user retrieve the information, while the user interface helps users interact with the app.</p>
<p>You can make a CRUD app with any of the programming languages out there. And the app doesn’t have to be full stack – you can make a CRUD app with client-side JavaScript. </p>
<p>In fact, the app with which I will be showing you how create, read, update and delete operations work is made with client-side JavaScript. </p>
<p>Each letter in the CRUD acronym has a corresponding HTTP request method. 
| <strong>CRUD Operation</strong>| <strong>HTTP Request Method</strong>| 
| ----------- | ----------- |
| Create| POST |
| Read | GET|
| Update| PUT or PATCH|
| Delete | DELETE|</p>
<h2 id="heading-what-is-the-create-operation-and-how-does-it-work">What is the <code>CREATE</code> Operation and How Does it Work?</h2>
<p>In CRUD, the create operation does what the name implies. It means creating an entry. This entry could be an account, user information, a post, or a task.</p>
<p>As I pointed out earlier, the HTTP protocol that implements a <code>CREATE</code> operation is the POST method.</p>
<p>In a SQL database, to create is to <code>INSERT</code>. In a NoSQL database like MongoDB, you create with the <code>insert()</code> method.</p>
<p>In a user interface, this GIF below shows how the <code>CREATE</code> operation works:
<img src="https://www.freecodecamp.org/news/content/images/2022/06/create-op.gif" alt="create-op" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-the-read-operation-and-how-does-it-work">What is the <code>READ</code> Operation and How Does it Work?</h2>
<p>The <code>READ</code> operation means getting access to the inputs or entries in the UI. That is, seeing it. Again, the entry could be anything from user information to social media posts, and others.</p>
<p>This access could mean the user getting access to the created entries right after creating them, or searching for them. Searching is implemented to allow the user to filter out the entries they don’t need.</p>
<p>The HTTP protocol that implements a <code>READ</code> operation is the GET method.</p>
<p>In a SQL database, to read is to <code>SELECT</code> an entry. In a NoSQL database like MongoDB, you read with the <code>find()</code> or <code>findById()</code> method.
<img src="https://www.freecodecamp.org/news/content/images/2022/06/read-operation.png" alt="read-operation" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-the-update-operation-and-how-does-it-work">What is the <code>UPDATE</code> Operation and How Does it Work?</h2>
<p><code>UPDATE</code> is the operation that allows you to modify existing data. That is, editing the data.</p>
<p>Unlike <code>READ</code>, the <code>UPDATE</code> operation alters the existing data by making changes to it. </p>
<p>PUT and PATCH are the HTTP protocols with which you can implement an <code>UPDATE</code> operation, depending on what you need.</p>
<p><code>PUT</code> should be used when you want the entire entry updated, and PATCH if you don’t want the entire entry to be modified.</p>
<p>In a SQL database, you use <code>UPDATE</code> to update an entry. In a NoSQL database like MongoDB, you can implement an update feature with the <code>findByIdAndUpdate()</code> method.</p>
<p>In a user interface, this GIF below shows how the <code>UPDATE</code> operation works:
<img src="https://www.freecodecamp.org/news/content/images/2022/06/update-op.gif" alt="update-op" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-the-delete-operation-and-how-does-it-work">What is the <code>DELETE</code> Operation and How Does it Work?</h2>
<p>To delete is to get rid of an entry from the UI and the database. </p>
<p><code>DELETE</code> is the HTTP protocol for implementing a <code>DELETE</code> operation.</p>
<p>In a SQL database, <code>DELETE</code> is used to delete an entry. In a NoSQL database like MongoDB, you can implement delete with the <code>findByIdAndDelete()</code> method.
<img src="https://www.freecodecamp.org/news/content/images/2022/06/delete-op.gif" alt="delete-op" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article showed you what CRUD means and what each individual operation in a CRUD app does.</p>
<p>You can think about CRUD in this way:</p>
<ul>
<li>You create a social account and fill in your information - <code>CREATE</code></li>
<li>You get access to the information you entered and people can search for you – <code>READ</code></li>
<li>You get a new job at Google and changed your employment status to employed – <code>UPDATE</code>  </li>
<li>You get tired of social media toxicity and delete your account - <code>DELETE</code></li>
</ul>
<p>To learn how you can make your own CRUD app, check out <a target="_blank" href="https://www.freecodecamp.org/news/learn-crud-operations-in-javascript-by-building-todo-app/">this tutorial</a> by Joy Shaheb of freeCodeCamp.</p>
<p>Keep coding 👋</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Any UI in Flutter ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you will learn how to convert any user interface image, piece, or screen into Flutter code. This is not a tutorial on building an app. It is rather a guide that will help you implement any UI you come across into an app you already h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-any-ui-in-flutter/</link>
                <guid isPermaLink="false">66b9ff4c85b28e0b24537e57</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Obum ]]>
                </dc:creator>
                <pubDate>Wed, 08 Jun 2022 15:22:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/06/anyuicover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you will learn how to convert any user interface image, piece, or screen into <a target="_blank" href="https://flutter.dev">Flutter</a> code.</p>
<p>This is not a tutorial on building an app. It is rather a guide that will help you implement any UI you come across into an app you already have. This tutorial also explains a wide variety of UI concepts in Flutter.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-flutter">What is Flutter?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-widgets-in-flutter">Widgets in Flutter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-widget-tree">The Widget Tree</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-any-ui-in-flutter">How to Implement any UI in Flutter</a><br>  <a class="post-section-overview" href="#heading-1-write-your-code-starting-at-the-top-left-and-move-down-to-the-bottom-right">1. Implement Top Left; Down Right</a><br>  <a class="post-section-overview" href="#heading-2-choose-a-widget">2. Choose a Widget</a><br>  <a class="post-section-overview" href="#heading-3-use-widget-groups">3. Use widget groups</a><br>  <a class="post-section-overview" href="#heading-a-columnrow">a. Column/Row</a><br>  <a class="post-section-overview" href="#heading-b-stack-widget">b. Stack Widget</a><br>  <a class="post-section-overview" href="#heading-4-create-custom-widgets">4. Create custom widgets</a><br>  <a class="post-section-overview" href="#heading-5-add-more-customization">5. Add more customization</a><br>  <a class="post-section-overview" href="#heading-a-container-widget">a. Container Widget</a><br>  <a class="post-section-overview" href="#heading-b-gesturedetector-inkwell">b. GestureDetector / InkWell</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-scrolling-interfaces">How to Implement Scrolling Interfaces</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-about-custompaint">About CustomPaint</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-is-flutter">What is Flutter?</h2>
<blockquote>
<p>Flutter is an open source framework by Google for building beautiful, natively compiled, multi-platform applications from a single codebase. – (s<a target="_blank" href="https://flutter.dev">ource: flutter.dev</a>)</p>
</blockquote>
<p>In Flutter, contrary to most frameworks, <a target="_blank" href="https://dart.dev">Dart</a> is the only programming language you use to code. This is an underemphasized benefit of Flutter. Especially for a tool that can build desktop, mobile, and web applications.</p>
<p>Most <a target="_blank" href="https://en.wikipedia.org/wiki/User_interface_design">UI</a> platforms use more than one language. For example, in front-end web development, you have to write <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML">HTML</a>, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS">CSS</a>, and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">JavaScript</a>. For <a target="_blank" href="https://developer.android.com/">Android</a>, you have to write <a target="_blank" href="https://developer.android.com/kotlin">Kotlin</a> (or <a target="_blank" href="https://developer.android.com/studio/write/java8-support">Java</a>) and <a target="_blank" href="https://developer.android.com/guide/topics/ui/declaring-layout#write">XML</a>. But in Flutter, it's just one language: Dart.</p>
<p>Coupled with the only-one-programming-language benefit, Flutter is simple because everything in Flutter is a widget. For example <a target="_blank" href="https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html">AnimatedWidget</a>, <a target="_blank" href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html">BottomNavigationBar</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container-class.html">Container</a>, <a target="_blank" href="https://api.flutter.dev/flutter/material/Drawer-class.html">Drawer</a>, <a target="_blank" href="https://api.flutter.dev/flutter/material/ElevatedButton-class.html">ElevatedButton</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/FormField-class.html">FormField</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Image-class.html">Image</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Opacity-class.html">Opacity</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Padding-class.html">Padding</a>, ...</p>
<p>This is part of what makes Flutter easy to use – it's basically plain English. <a target="_blank" href="https://docs.flutter.dev/development/ui/widgets">Widget</a> names reflect what they are and their properties are easy to understand.</p>
<h2 id="heading-widgets-in-flutter">Widgets in Flutter</h2>
<p>A widget is a Dart <a target="_blank" href="https://dart.dev/samples#classes">class</a> that either extends <a target="_blank" href="https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html">StatefulWidget</a> or <a target="_blank" href="https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html">StatelessWidget</a>.</p>
<p>Your local <a target="_blank" href="https://docs.flutter.dev/get-started/install">Flutter installation</a> comes with several widgets. To check out the widgets available by default, open the packages folder of your Flutter installation in your preferred editor. Then search across all files for "extends StatefulWidget" and "extends StatelessWidget" and take note of the number of results.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/flutter-packages-widget-count.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As of <a target="_blank" href="https://docs.flutter.dev/development/tools/sdk/release-notes/release-notes-2.10.0">Flutter 2.10</a>, you will get <strong>408</strong> StatefulWidgets and <strong>272</strong> StatelessWidgets. That is a total of <strong>680 widgets</strong> available for you to use and implement UIs.</p>
<p>These widgets typically have all you need. But at times they may not be enough. <a target="_blank" href="https://pub.dev">pub.dev</a>, Dart and Flutter's package manager, have many more widgets you can use to implement UIs.</p>
<p>It is difficult to count the widgets in pub.dev. But searching an empty string (don't enter anything in the search bar and then press the search icon) and setting the SDK to Flutter returns the current total number of published packages.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/pub.dev-widget-count.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>At the time of writing, there are more than 23000 Flutter packages in pub.dev. Each package has <em>at least one</em> widget. This means that you have more than 23000 widgets from pub.dev to implement, in addition to the available 680. This means that you can really implement any UI you want easily in Flutter.</p>
<p>Adding to the many available widgets, you can also create your own widgets as you implement UIs.</p>
<h2 id="heading-the-widget-tree">The Widget Tree</h2>
<p>The following is part of the code you get when you create a new Flutter project and remove the comments:</p>
<pre><code class="lang-dart"> <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            <span class="hljs-keyword">const</span> Text(
              <span class="hljs-string">'You have pushed the button this many times:'</span>,
            ),
            Text(
              <span class="hljs-string">'<span class="hljs-subst">$_counter</span>'</span>,
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: <span class="hljs-string">'Increment'</span>,
        child: <span class="hljs-keyword">const</span> Icon(Icons.add),
      ),
    );
  }
</code></pre>
<p>The parent <code>Scaffold</code> takes the <code>appBar</code>, <code>body</code>, and <code>floatingActionButton</code> parameters. In turn, the <a target="_blank" href="https://api.flutter.dev/flutter/material/AppBar-class.html">AppBar</a> also takes a <code>title</code> parameter that has a <code>Text</code> value.</p>
<p><code>body</code> takes a <code>Center</code> value that has a <code>Column</code> <code>child</code>. The <code>Column</code> in turn has two <code>Text</code>s as <code>children</code>. The <code>FloatingActionButton</code> takes the <code>onPressed</code> callback, 'Increment' <code>tooltip</code>, and an <code>Icon</code> for a <code>child</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot--142--2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Flutter widget tree breakdown</em></p>
<p>This is a simple widget tree. It has parents and descendants. <code>child</code> and <code>children</code> are common properties of most Flutter widgets. As widgets continuously take more widget children, your app gradually grows into a large widget tree.</p>
<p>As you implement UIs in Flutter, bear in mind that you are building a widget tree. You will notice that your code indents inwards from the left margin. It seems to develop some kind of virtual greater than sign (of empty space) at the left.</p>
<p><strong>Note:</strong> Huge indentation levels are a sign that you need to refactor your code. It means that you need to extract some widget hierarchy into a separate widget.</p>
<h2 id="heading-how-to-implement-any-ui-in-flutter">How to Implement Any UI in Flutter</h2>
<h3 id="heading-1-write-your-code-starting-at-the-top-left-and-move-down-to-the-bottom-right">1. Write your code starting at the top left and move down to the bottom right</h3>
<p>You'll implement the UI widget after widget according to each element's position in the UI. So you will first write code for things that appear at the top of the UI. Then you keep writing code for the other items moving down the page until you reach the bottom of that UI.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Screenshot--143-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is intuitive.</p>
<p>On the horizontal axis, go from left to right. If need be, or if it is a <a target="_blank" href="https://medium.com/@carlolucera/flutter-and-directionality-d9ac42197fb8">right-to-left</a> UI, then implement it from right to left instead.</p>
<h3 id="heading-2-choose-a-widget">2. Choose a Widget</h3>
<p>Next you'll need to logically determine the widget you want to use for a given UI element. At a bare minimum, for a given UI element, you will use simple widgets you're familiar with based on what their names say they do.</p>
<p>Chances are the name of what the UI component looks like is the name of the widget. If you find it hard to make a choice, a quick online search will give you the answers. Flutter has a great online community.</p>
<h3 id="heading-3-use-widget-groups">3. Use widget groups</h3>
<p>If a group of UI items is arranged vertically, one after another, use a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Column-class.html">Column</a>. If they are arranged horizontally, one after another, use a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Row-class.html">Row</a>. If they are placed on top of each other, use a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Stack-class.html">Stack</a>, with the floating widgets wrapped in <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned-class.html">Positioned</a> widgets.</p>
<h4 id="heading-a-columnrow">a. Column/Row</h4>
<p>Inside a Column or Row, you can change or adjust how the widgets will align themselves on the main or cross axis. Use their <a target="_blank" href="https://api.flutter.dev/flutter/rendering/CrossAxisAlignment.html">CrossAxisAlignment</a> and <a target="_blank" href="https://api.flutter.dev/flutter/rendering/MainAxisAlignment.html">MainAxisAlignment</a> properties for such adjustments.</p>
<p>For the cross axis, you can align to center, end, start, and stretch. For the main axis, you can align to center, end, space around, space between, space evenly, and end.</p>
<p>In a <code>Column</code>, the vertical axis is the main axis while the horizontal axis is the cross axis. In a <code>Row</code>, the horizontal axis is the main axis while the vertical axis is the cross axis.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/1Untitled-1-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Adapted from https://arzerin.com/2019/11/20/flutter-column/</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/05/Untitled-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Adapted from https://arzerin.com/2019/11/20/flutter-row/</em></p>
<p>In Columns and Rows, if you want a particular child widget to take as much available space as possible, wrap that widget inside an <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Expanded-class.html">Expanded</a> widget. If you are familiar with web frontend, you'll notice that Columns and Rows are like <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex">display: flex;</a> in CSS.</p>
<h4 id="heading-b-stack-widget">b. Stack Widget</h4>
<p>With <code>Stack</code>, the last widget(s) in the <code>children</code>'s list appears on top of the earlier children.</p>
<p>You might have to edit the Stack's <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Stack/alignment.html">alignment</a> to indicate the relative positions of the widgets. Like <a target="_blank" href="https://api.flutter.dev/flutter/painting/AlignmentDirectional/topCenter-constant.html">topCenter</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/AlignmentDirectional/center-constant.html">center</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/AlignmentDirectional/bottomEnd-constant.html">bottomEnd</a>, and so on.</p>
<p>The Stack's size is calculated based on non-positioned widgets (Widgets in the children list not wrapped in a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned-class.html">Positioned</a> parent). When coding, remember that your Stack should either have at least one non-positioned widget, or it should be wrapped in a parent widget that explicitly sets the Stack's size.</p>
<p>Positioned takes any or all of <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned/bottom.html">bottom</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned/top.html">top</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned/left.html">left</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Positioned/right.html">right</a>. They set the child's position relative to the Stack. Negative values move the child in the opposite direction. However, negative values clip parts of the child out. Use <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Stack/clipBehavior.html">clipBehavior: Clip.none</a> on the Stack to show all the parts of the positioned widget.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/Screenshot--148-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Full code</em> <a target="_blank" href="https://gist.github.com/obumnwabude/de06d67b7e636dfb8ae1b852b97624b4"><em>here</em></a><em>.</em></p>
<h3 id="heading-4-create-custom-widgets">4. Create custom widgets</h3>
<p>As you build the widget tree, you will notice two things:</p>
<ol>
<li><p>Either a chunk of the tree grows too big and it is a logical unit on its own.</p>
</li>
<li><p>Or some chunks or sets of widgets might repeat themselves with slight changes.</p>
</li>
</ol>
<p>These are two indications that you should <a target="_blank" href="https://en.wikipedia.org/wiki/Code_refactoring">refactor</a> your code. It means that you should extract out those widgets and define them in another Dart file.</p>
<p>Your <a target="_blank" href="https://docs.flutter.dev/get-started/editor">code editor</a> will help you with refactoring. With or without the editor, all you need to do is:</p>
<ol>
<li><p>Create a new Dart file. The file name should reflect the new widget's name.</p>
</li>
<li><p>Create a new class that extends StatefulWidget or StatelessWidget, depending on if the new widget has <a target="_blank" href="https://api.flutter.dev/flutter/widgets/State-class.html">State</a> or not.</p>
</li>
<li><p>Then return the widget chunk from a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/StatelessWidget/build.html">build</a> method.</p>
</li>
<li><p>(Optional) If need be, your new Dart class can take positional or named parameters to its constructor to customize the widget's look.</p>
</li>
</ol>
<pre><code class="lang-dart"><span class="hljs-comment">// in counter_display.dart</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CounterDisplay</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Column(
        mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(<span class="hljs-string">'You have pushed the button this many times:'</span>),
        Text(<span class="hljs-string">'<span class="hljs-subst">$counter</span>'</span>, style: TextStyle(fontSize: <span class="hljs-number">24</span>)),
      ],
    );
  }
}

<span class="hljs-comment">// in main.dart</span>
<span class="hljs-comment">//</span>
<span class="hljs-comment">// ... </span>
  body: Center(child: CounterDisplay()),
<span class="hljs-comment">// ...</span>
</code></pre>
<p>You will build many custom widgets and they in turn will be descendants to more custom widgets, and that's fine. The widget tree is meant to continuously grow as the need arises.</p>
<h3 id="heading-5-add-more-customization">5. Add more customization</h3>
<p>You won't customize widgets only because of refactoring and <a target="_blank" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">repetitions (DRY code)</a>. You will create custom widgets because of the UI you are implementing.</p>
<p>You will create custom widgets because the many available widgets don't always meet the exact needs of a given UI. You'll need to combine them in some special way to implement a particular UI.</p>
<h4 id="heading-a-container-widget">a. Container Widget</h4>
<p><a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container-class.html">Container</a> is a powerful widget. You can style it in different ways. If you are used to web frontend, you'll notice that it is like a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div">div</a> in HTML.</p>
<p>Container is a base widget. You can use it to create any UI piece.</p>
<p>Some Container parameters are <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/constraints.html">constraints</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/decoration.html">decoration</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/margin.html">margin</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/padding.html">padding</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/transform.html">transform</a>, among others. Of course, Container takes a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/Container/child.html">child</a> which can be any widget.</p>
<p>The decoration property can take a <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration-class.html">BoxDecoration</a>, which in turn can take several other properties. This is the heart of Container's flexibility. BoxDecoration takes parameters like <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/border.html">border</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/borderRadius.html">borderRadius</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/boxShadow.html">boxShadow</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/color.html">color</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/gradient.html">gradient</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/image.html">image</a>, <a target="_blank" href="https://api.flutter.dev/flutter/painting/BoxDecoration/shape.html">shape</a>, among others.</p>
<p>With these parameters and their values, you can implement any UI to your taste. You can use <code>Container</code> instead of the many <a target="_blank" href="https://api.flutter.dev/flutter/material/material-library.html">material widgets</a> that Flutter comes with. That way your app is to your taste.</p>
<h4 id="heading-b-gesturedetector-inkwell">b. GestureDetector / InkWell</h4>
<p><a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector-class.html">GestureDetector</a> as the name implies detects user interactions. Not every UI piece is a button. And while implementing UIs you will need some widgets to react to user actions. In such a case, use GestureDetector.</p>
<p>GestureDetector can detect different types of gestures: taps, double taps, swipes, ... GestureDetector of course takes a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector/child.html">child</a> (which can be any widget), and different callbacks for different gestures like <a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector/onTap.html">onTap</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector/onDoubleTap.html">onDoubleTap</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector/onDoubleTap.html">onPanUpdate</a> (for swipes), ...</p>
<p>Note: By default, when users interact with the empty spaces in the child of GestureDetectors, the callbacks are not called. If you want your GestureDetector to react to gestures on empty space (within its child), then set the <a target="_blank" href="https://api.flutter.dev/flutter/widgets/GestureDetector/behavior.html">behavior</a> property of the GestureDetector to <a target="_blank" href="https://api.flutter.dev/flutter/rendering/HitTestBehavior.html">HitTestBehavior.translucent</a>.</p>
<pre><code class="lang-dart">GestureDetector(
  <span class="hljs-comment">// set behavior to detect taps on empty spaces</span>
  behavior: HitTestBehavior.translucent,
  child: Column(
    children: [
      Text(<span class="hljs-string">'I have space after me ...'</span>),
      SizedBox(height: <span class="hljs-number">32</span>),
      Text(<span class="hljs-string">'... that can detect taps.'</span>),
    ],
  ),
  onTap: () =&gt; <span class="hljs-built_in">print</span>(<span class="hljs-string">'Tapped on empty space.'</span>),
)
</code></pre>
<p><a target="_blank" href="https://api.flutter.dev/flutter/material/InkWell-class.html">InkWell</a> is similar to GestureDetector. It responds to some gestures that GestureDetector responds to. However, it shows ripple effects when interacted with (which GestureDetectors don't).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/QqEZ3.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>From https://stackoverflow.com/q/58285012/13644299</em></p>
<p>InkWell must have a <a target="_blank" href="https://api.flutter.dev/flutter/material/Material-class.html">Material</a> ancestor. So, if your topmost widget is <a target="_blank" href="https://api.flutter.dev/flutter/material/MaterialApp-class.html">MaterialApp</a> you need not worry. Else, wrap the InkWell in a Material.</p>
<p>You should also do this wrapping if you are changing the colors of the InkWell's parent or child. If you don't, the ripple won't show. You also have to set the <a target="_blank" href="https://api.flutter.dev/flutter/material/Material/color.html">color</a> of the Material widget for the ripple to show. You can set the color to <a target="_blank" href="https://api.flutter.dev/flutter/material/Colors/transparent-constant.html">Colors.transparent</a> and Flutter will take care of the rest.</p>
<h2 id="heading-how-to-implement-scrolling-interfaces">How to Implement Scrolling Interfaces</h2>
<p>Scrolling is a little delicate topic. By default, widgets don't scroll in Flutter. If your Column or Row will be scrollable, use a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/ListView-class.html">ListView</a> instead. ListView takes children parameter too.</p>
<p>ListView also has factory constructors like <a target="_blank" href="https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html">ListView.builder</a> and <a target="_blank" href="https://api.flutter.dev/flutter/widgets/ListView/ListView.separated.html">ListView.separated</a>. The builder gives you more control over the build process of the children whereas the separated takes into account a Separator (like <a target="_blank" href="https://api.flutter.dev/flutter/material/Divider-class.html">Divider</a> for example).</p>
<p>By default, ListViews scroll their children vertically. However, you can change the <a target="_blank" href="https://api.flutter.dev/flutter/widgets/ScrollView/scrollDirection.html">scrollDirection</a> of a ListView to <a target="_blank" href="https://api.flutter.dev/flutter/painting/Axis.html">Axis.horizontal</a> to scroll its children horizontally.</p>
<p>At times, you might want to use <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html">SingleChildScrollView</a> instead of ListView. As the name implies, it takes a single <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SingleChildScrollView/child.html">child</a> and it can scroll. You can pass widget groups as its child.</p>
<p><a target="_blank" href="https://docs.flutter.dev/development/ui/widgets/scrolling">There are other scrolling widgets</a>.</p>
<p>But take special note of <a target="_blank" href="https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html">CustomScrollView</a>. It gives you huge control of scrolling, unlike the others. It takes <a target="_blank" href="https://api.flutter.dev/flutter/widgets/CustomScrollView/slivers.html">slivers</a>, which in turn are scrolling widgets with powerful scroll mechanisms.</p>
<p><a target="_blank" href="https://api.flutter.dev/flutter/widgets/SliverFillRemaining-class.html">SliverFillRemaining</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SliverFillViewport-class.html">SliverFillViewport</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SliverGrid-class.html">SliverGrid</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SliverList-class.html">SliverList</a>, <a target="_blank" href="https://api.flutter.dev/flutter/widgets/SliverPersistentHeader-class.html">SliverPersistentHeader</a> among others, are examples of widgets you include in the list of slivers. Most of these widgets take a delegate, which handles how scrolling occurs.</p>
<p>A good case to use CustomScrollView is with <a target="_blank" href="https://api.flutter.dev/flutter/material/SliverAppBar-class.html">SliverAppBar</a>, where you want the AppBar to be expanded by default and shrunk on scroll.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/ap.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Another example could be with a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/DraggableScrollableSheet-class.html">DraggableScrollableSheet</a> where you keep some action button sticked to the bottom.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/bs.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-about-custompaint">About CustomPaint</h2>
<p>This is where Flutter gave ultimate flexibility to the UI world.</p>
<p><a target="_blank" href="https://api.flutter.dev/flutter/widgets/CustomPaint-class.html">CustomPaint</a> is to Flutter what the Canvas API is to HTML or SVG is to images.</p>
<p>CustomPaint is a widget in Flutter that gives you the ability to design and draw without limitations. It gives you a canvas on which you can draw with a <a target="_blank" href="https://api.flutter.dev/flutter/widgets/CustomPaint/painter.html">painter</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/visualizer.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>From https://blog.codemagic.io/flutter-custom-painter/</em></p>
<p>You will rarely use CustomPaint. But be aware that it exists. Because there might be very complex UIs that widget combinations might not implement them and you will have no choice than drawing with <code>CustomPaint</code>.</p>
<p>When that time comes, it won't be hard for you because you are already familiar with other widgets.</p>
<h2 id="heading-summary">Summary</h2>
<p>For a given UI piece, choose a widget, write its code, build the widget with other widgets, and see what great UI you are implementing with Flutter.</p>
<p>Implementing UIs is a major part of mobile, web, and desktop app development. Flutter is a UI toolkit that build cross-platform for those platforms. Flutter's declarative nature and its widget abundance make UI implementation simple.</p>
<p>Keep implementing UIs in Flutter. As you do, it will become second nature to you. And you will be able to implement any UI in Flutter.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ UX Designer Salary – How much do UX/UI Designers Make? Entry Level Range ]]>
                </title>
                <description>
                    <![CDATA[ If you are new to tech, you might have just discovered UI/ UX design and want to pursue that career path. But what is the starting salary for entry level UX/UI designers? In this article, I will provide definitions for UI/ UX design as well as starti... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ux-designer-salary-how-much-do-ux-ui-designers-make-entry-level-range/</link>
                <guid isPermaLink="false">66b8da568cd1c2aa053d49bd</guid>
                
                    <category>
                        <![CDATA[ UI Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UI UX ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ux design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jessica Wilkins ]]>
                </dc:creator>
                <pubDate>Fri, 15 Oct 2021 17:39:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/10/daniel-korpai-bOKIptPzdPk-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you are new to tech, you might have just discovered UI/ UX design and want to pursue that career path. But what is the starting salary for entry level UX/UI designers?</p>
<p>In this article, I will provide definitions for UI/ UX design as well as starting salary information from around the world. </p>
<h2 id="heading-what-is-ux-design">What is UX design?</h2>
<p>The term User Experience refers to how users interact with a product or service. Whenever you login into a website, or play a game on your phone, your interaction with that application is considered user experience.</p>
<p>If you find a product to be easy to use and understand, then that would be considered a positive user experience. But if the product is confusing and frustrating then that would be considered a negative user experience.</p>
<p>If you want to learn more about UX design, and find resources to start learning, then I would suggest reading my article on <a target="_blank" href="https://www.freecodecamp.org/news/user-experience-design-what-does-a-ux-designer-do/">What is UX Design?</a></p>
<h2 id="heading-what-is-ui-design">What is UI design?</h2>
<p>The term User Interface refers to the look and feel of a product. For digital products, UI design focuses on all visual aspects including fonts, colors, spacing, responsive design and more. </p>
<p>Good UI design is considered to be simple, easy to understand and consistent. You don't want users to have to struggle with navigating through the product because it is not intuitive. </p>
<p>Bad UI design is considered to be inconsistent and difficult to understand. If users are confronted with low quality images, inconsistent fonts or poor contrast in visual content, then the user will want to leave the product as soon as possible.</p>
<p>If you are interested in learning the fundamentals of UI design, then I would suggest reading through <a target="_blank" href="https://www.freecodecamp.org/news/learn-ui-design-fundamentals-with-this-free-one-hour-course/">this Per Harald Borgen article</a>. </p>
<h2 id="heading-resources-to-start-learning-uiux-design">Resources to start learning UI/UX design</h2>
<p>Here is a list of resources to learn UI/UX design.</p>
<ul>
<li><a target="_blank" href="https://www.coursera.org/learn/foundations-user-experience-design?specialization=google-ux-design">Foundations of User Experience (UX) Design</a></li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=_Hp_dI0DzY4">The 2019 UI Design Crash Course for Beginners</a></li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=3q3FV65ZrUs">Figma Tutorial – A Free UI Design/Prototyping Tool</a></li>
<li><a target="_blank" href="https://www.uxpin.com/studio/ebooks/">UXpin: Free UX Ebooks</a></li>
</ul>
<h2 id="heading-salaries-around-the-world-for-uiux-designers">Salaries around the world for UI/UX designers</h2>
<p>In this section, I will show you some salaries from around the around for entry level UI/UX designers. </p>
<h3 id="heading-united-states">United States</h3>
<p>This data was pulled from indeed.com, payscale.com, and ZipRecruiter. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.14.02-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.14.26-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.19.05-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This data represents the national average. If you want data for your specific city, then you can search for that in these job sites. </p>
<h3 id="heading-canada">Canada</h3>
<p>This data was pulled from ZipRecruiter. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.28.02-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.28.13-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-mexico">Mexico</h3>
<p>This data was pulled from payscale.com and is in MXN currency.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.35.20-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-uk">UK</h3>
<p>This data was pulled from uk.talent.com. The average entry level salaries for UI/UX designers would start at £35,000. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Screen-Shot-2021-10-15-at-7.46.23-AM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-other-countries-around-the-world">Other Countries around the world</h3>
<p>If you want to explore other UX salaries from around the world, you can look into <a target="_blank" href="https://uxdesignersalaries.com/">https://uxdesignersalaries.com/</a> </p>
<p>You can also use popular job sites in your area, to find out the average entry level salary for UI/UX designers. </p>
<p>I hope you enjoyed this article and best of luck on your UI/UX journey.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Figma? A Design Crash Course ]]>
                </title>
                <description>
                    <![CDATA[ Figma is a powerful design tool that helps you to create anything: websites, applications, logos, and much more. By learning to use Figma, you'll take your first steps into User Interface Design and and User Experience Design. These skills are essent... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/figma-crash-course/</link>
                <guid isPermaLink="false">66be016a2c7c89a851b37647</guid>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Design Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ figma ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Adrian Twarog ]]>
                </dc:creator>
                <pubDate>Mon, 21 Jun 2021 17:52:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/06/figma-crash-course-freecodecamp.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Figma is a powerful design tool that helps you to create anything: websites, applications, logos, and much more.</p>
<p>By learning to use Figma, you'll take your first steps into User Interface Design and and User Experience Design. These skills are essential for building a great portfolio for yourself and potentially for your own company.</p>
<p>In this Figma Crash Course, I will use a real project website from Frontend Mentor which we will recreate in Figma. Learning by building a project is a great way to solidify your knowledge, so I recommend downloading the assets and following along.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/lg7w3Ntfqy0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>I'll break this crash course into a bunch of parts as we explore different features of Figma including:</p>
<ul>
<li><p>Account setup</p>
</li>
<li><p>Design file</p>
</li>
<li><p>Design assets</p>
</li>
<li><p>How to get started – the basics</p>
</li>
<li><p>Frames</p>
</li>
<li><p>Grid columns and rows</p>
</li>
<li><p>Shapes</p>
</li>
<li><p>Import images</p>
</li>
<li><p>Labelling and grouping</p>
</li>
<li><p>Text</p>
</li>
<li><p>Navigation bar</p>
</li>
<li><p>Button design</p>
</li>
<li><p>Header title and scaling</p>
</li>
<li><p>Additional text settings</p>
</li>
<li><p>Hero section and Figma layers</p>
</li>
<li><p>Groups and sections</p>
</li>
<li><p>Fonts and Figma colors</p>
</li>
<li><p>Text line height and spacing</p>
</li>
<li><p>Alignment and snapping</p>
</li>
<li><p>Masking with shapes and images</p>
</li>
<li><p>Copying groups and sections</p>
</li>
<li><p>Updating sections</p>
</li>
<li><p>Color picker and copying sections continued</p>
</li>
<li><p>Color selecting and picking correct hue</p>
</li>
<li><p>Testimonial section</p>
</li>
<li><p>Autolayout</p>
</li>
<li><p>Autolayout options</p>
</li>
<li><p>Autolayout alignment</p>
</li>
<li><p>Autolayout content</p>
</li>
<li><p>Gallery images</p>
</li>
<li><p>Resizing images</p>
</li>
<li><p>Footer</p>
</li>
<li><p>SVG color and overlay edits</p>
</li>
<li><p>Auto layout menu footer</p>
</li>
<li><p>Layers toolbar, hide and lock</p>
</li>
<li><p>Prototype viewer</p>
</li>
<li><p>Frames and pages</p>
</li>
<li><p>Prototype interactions</p>
</li>
<li><p>Prototype interaction navigation to page</p>
</li>
<li><p>Share test and preview prototypes</p>
</li>
<li><p>Export pages</p>
</li>
<li><p>Export groups or sections</p>
</li>
</ul>
<p>In this article, I'll give you a brief overview of some of the main parts of the course so you can get started designing in Figma.</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Let's begin by creating an account on the Figma Website. You can do this here: <a target="_blank" href="https://figma.com">Figma.com</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma1.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Just sign up with a new account (email or Google sign in) – it's completely free</p>
<p>Next we need to get the design assets to use for this project, which we'll grab from Frontend Mentor. You can download them here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.frontendmentor.io/solutions/sunnyside-agency-landing-page-mVy7o6ijV">https://www.frontendmentor.io/solutions/sunnyside-agency-landing-page-mVy7o6ijV</a></div>
<p> </p>
<p>We are going to build our first draft page. Take a look at the default design layout we will be working with below. Select the + sign, or on the right hand side create a new design page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-make-a-frame-in-figma">How to Make a Frame in Figma</h2>
<p>First we will create a frame. This is the same as a page, and it's where our design will live. You can size a frame however you want depending on your requirements.</p>
<p>This example will be a website, so we will select the Desktop Frame – but you can also create mobile application frames, or even custom frames.</p>
<ul>
<li><p>Select the Frame tool from the top menu</p>
</li>
<li><p>Select the Frame size on the right panel</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma3.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-grids-and-columns-in-figma">How to Add Grids and Columns in Figma</h2>
<p>Before we start with the design, you will want to add grids that help you keep the alignment of content for your page consistent.</p>
<p>You can add grids to a frame and customise them. For example, I often prefer 12 grids as this is the default for website development.</p>
<ul>
<li><p>Select the frame you wish to grid</p>
</li>
<li><p>Select Layout Grid on the right panel</p>
</li>
<li><p>Select Columns and 12 units</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma4.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-use-shapes-in-figma">How to Use Shapes in Figma</h2>
<p>Use the shapes and elements in Figma to create squares, circles, lines, and more. These are the fundamentals for creating a design on a page.</p>
<p>I'll start with a simple rectangle which we will use to create our hero navigation section for the top part of this home page.</p>
<ul>
<li><p>Select Square Shape tool</p>
</li>
<li><p>Create a square</p>
</li>
<li><p>Begin to shape and size it</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma5.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-images-in-figma">How to Add Images in Figma</h2>
<p>You can add images from an online source or locally to your page. Images are an important part of designing a website, especially for the hero section.</p>
<p>Let's add a logo as our first image which we will add to the top left of the screen.</p>
<ul>
<li><p>Drag and drop an image from you local computer</p>
</li>
<li><p>Import an image from the shapes image upload option</p>
</li>
<li><p>Resize and place the image on the design</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma6.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-add-text-to-a-design-in-figma">How to Add Text to a Design in Figma</h2>
<p>To add text to a design, select the text tool and place it on the page. The font will default to Roboto, but you can change the font family, size, and color at any stage.</p>
<p>In this example we will use right panel to change the color and size of the font, and later the font family as well.</p>
<ul>
<li><p>Select the Text tool</p>
</li>
<li><p>Add "About" text as the start of the navigation</p>
</li>
<li><p>Ensure the size and color are 24 and white</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma7.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-label-elements-and-create-groups-in-figma">How to Label Elements and Create Groups in Figma</h2>
<p>Working with lots of layers in Figma can get confusing, so label all your elements as soon as you create them. Even better is to group different sections and shapes with labels such as "Hero Background" or "Navbar".</p>
<ul>
<li><p>Select your element/s and right click to group or press Ctrl + G</p>
</li>
<li><p>Name your group</p>
</li>
<li><p>Places groups inside groups for each section of your page to improve readability once your page gets large</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/figma8.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Figma is simple to start using, but there's a lot to learn to use it to its full potential. With the basics of shapes, images, and text, you can easily get started prototyping a website, designing a mobile application, and much more.</p>
<p>If you want to take a deeper dive into these basics, I would recommend watching and following along the <a target="_blank" href="https://youtu.be/lg7w3Ntfqy0">whole video crash course</a> which looks at each aspect in more depth.</p>
<p>You can view the full results of the design below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.figma.com/file/9kw3Z8n5t5l3BHAvyZy39X/Figma-Crash-Course-2021?node-id=0%3A1">https://www.figma.com/file/9kw3Z8n5t5l3BHAvyZy39X/Figma-Crash-Course-2021?node-id=0%3A1</a></div>
<p> </p>
<p>This whole process is part of what I am trying to teach on my own channel and also part of a course/book I am writing on design called <strong>Enhance UI</strong>. The goal is to <strong>help developers understand the fundamentals of design</strong>. Check out my Enhance UI Book below:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.enhanceui.com">https://www.enhanceui.com</a></div>
<p> </p>
<p>I hope you enjoyed this article. If you don't know who I am, I'm Adrian from Australia. I have a tiny channel on Twitter and YouTube, so if you want to know more about me or enjoy my content, check them out sometime. 😉</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/freeCodeCamp.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li><p><strong>You</strong>T<strong>ube:</strong> <a target="_blank" href="https://youtube.com/adriantwarog?sub_confirmation=1">https://youtube.com/adriantwarog</a></p>
</li>
<li><p><strong>Twitter:</strong> <a target="_blank" href="https://twitter.com/adrian_twarog">https://twitter.com/adrian_twarog</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is the Difference Between a Website and a Web Application? ]]>
                </title>
                <description>
                    <![CDATA[ It's typically hard for an end user to tell the difference between a web app and a website. They just type in the URL and boom, the results are there.  And this is what really matters for the user – getting the results for what they are searching. If... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/difference-between-a-website-and-a-web-application/</link>
                <guid isPermaLink="false">66b8dbbf1fe28dd01992d684</guid>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Applications ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hillary Nyakundi ]]>
                </dc:creator>
                <pubDate>Wed, 24 Mar 2021 20:37:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/03/Website--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>It's typically hard for an end user to tell the difference between a web app and a website. They just type in the URL and boom, the results are there. </p>
<p>And this is what really matters for the user – getting the results for what they are searching. If that happens, then that's basically what they care about. </p>
<p>But for you as a developer, you're the one who has to build these products for the user. So you should know their differences well. Do you know the main differences between a <em>Website and a Web Application</em>?</p>
<p>We'll take a basic technical approach in this article. And although there are conflicting opinions among some developers, I will try and highlight some of the key differences which helped me understand how websites and web apps differ.</p>
<p>Lets get started.</p>
<h2 id="heading-what-is-a-website">What is a Website?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/fresh.png" alt="website" width="600" height="400" loading="lazy">
A website is a collection of publicly accessible pages containing either documents, images, audio, text, or other files that users can acces through the internet. </p>
<p>Websites can consist of a single page or many pages. In order for a user to access a website, they will need a URL which they enter in the search bar of their web browser. An example of a URL is https://www.freecodecamp.org/</p>
<h3 id="heading-types-of-websites">Types of websites</h3>
<p>There are two main types of websites:</p>
<ul>
<li><p><strong>Dynamic websites</strong><br>These are more complex and have back-end interactions with the user. They use more advanced languages and databases in addition to the custom HTML, CSS, and JavaScript. Unlike static they display different content depending on various factors.</p>
</li>
<li><p><strong>Static websites</strong><br>These are built using simple languages like HTML, CSS, and JavaScript. They are called static because they don't have a database a user can interact with. They display same content for every user who accesses them.</p>
</li>
</ul>
<p>By understanding these two main types of websites, let's see some examples of websites we have today: </p>
<ul>
<li>Dating websites</li>
<li>Blogging websites</li>
<li>Educational websites</li>
<li>Portfolio websites</li>
<li>Personal websites </li>
<li>Community websites </li>
<li>Government websites</li>
</ul>
<h3 id="heading-characteristics-of-websites">Characteristics of Websites</h3>
<p>As a developer, creating a website that is attractive to the user is one of the core tasks you'll perform. This is because sites are mostly judged by how they appear. The appearance is a main factor that will help determine how long the user will stay on your site. </p>
<p>To help you build beautiful site, here are some tips to keep in mind: </p>
<ul>
<li>It should be <strong>user friendly</strong> so your users can easily learn what the site does, where things are, and so on.</li>
<li>It shoudl contain <strong>quality content</strong>. Bear in mind that visitors will come to your site for the content, so be sure it meets their needs.</li>
<li>It should <strong>be attractive</strong>. Using simple design principles should help you give your website a professional layout and look while at the same time providing quality content. </li>
<li>It should be <strong>easy to navigate</strong>. This will make it easy for users to find what they are looking for. You can do this by adding a menu linking to various parts of the site, for example. </li>
<li>It should <strong>load quickly</strong>. In order to attract more users, be sure your sites loads quickly. This will provide a positive experience to visitors.</li>
<li>It should be <strong>responsive</strong>. Make sure your site is accessible across all devices. Keep in mind that most users are not using computers but rather mobile phones.</li>
</ul>
<h2 id="heading-why-you-need-a-website">Why you need a Website</h2>
<p>The main question you might ask at this point is: Do you actually need a website? Whether it's personal or even for your business? Well the answer is <strong>YES</strong> – and here's why: </p>
<ul>
<li>If you have a business, a website will allow you to showcase your products to anyone with an internet connection, anywhere. </li>
<li>It will help you grow your brand as an individual or as a company. </li>
<li>It helps create social proof, so others can see what you have done and what you do (which helps build credibility).</li>
<li>It helps create an online presence where your clients are able to find you easily. </li>
<li>It can be used as a source of income via advertising, and through monetization.</li>
</ul>
<h2 id="heading-what-is-a-web-application">What is a Web Application?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/11/fresh--1-.png" alt="web application" width="600" height="400" loading="lazy">
A web application, also referred to as a web app, is a computer program with functionality and interactive elements. You use regular web technologies to build it but it also stores data and manipulates it according to a user's needs. </p>
<p>Web apps are extremely customizable and can perform a wide range of tasks and functionalities like creating, reading, updating, and deleting data in the app. They are more complex and difficult to build, so they require an experienced team of developers to create them.</p>
<p>Some popular examples of web applications include: shopping carts, video and photo editing apps, file conversion tools, file scanning apps, email programs, and so on.</p>
<h2 id="heading-characteristics-of-a-web-application">Characteristics of a Web Application</h2>
<ul>
<li>They are scalable and cloud-hosted.</li>
<li>They are cross-platform applications, meaning they can be run on any OS, whether it's macOS, Windows, or Linux. </li>
<li>They should be easily tested with automation. </li>
</ul>
<h2 id="heading-why-you-need-a-web-application">Why you need a Web Application</h2>
<p>Most companies today are shifting gears toward using web applications in addition to or instead of websites. Here are some of the reasons why: </p>
<ul>
<li>Easy to maintain and update</li>
<li><strong>Easily customizable</strong> - you can easily add more designs and features based on your preferences and user needs.</li>
<li><strong>Secure</strong> - data is stored in the cloud, meaning you don't need to worry about data loss if your device is damaged. You can easily retrieve it.</li>
<li>You do not need approval from various app stores, since they can be accessed via the browser.</li>
<li>They support all modern browsers.</li>
</ul>
<h2 id="heading-differences-between-websites-and-web-applications">Differences between Websites and Web Applications</h2>
<p>By this point you already have an understanding of what websites and web apps are. So now we'll highlight their main differences.</p>
<p>Let's have a summary of the key differences to better understand them: </p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Website</td><td>Web Application</td></tr>
</thead>
<tbody>
<tr>
<td>They provide visual and textual content, which can be viewed and read.</td><td>The user does not only view the page contents but also can interact with it and manipulate it, for example filling forms</td></tr>
<tr>
<td>Authentication is not mandatory in regular websites unless you are signing up for something like a newsletter</td><td>They need authentication as they offer much broader services, meaning you will need to have a username and password to access services</td></tr>
<tr>
<td>They will display what has been searched for by the user</td><td>Tasks here are more complex, you will have to browse within to get a specific product in a page</td></tr>
<tr>
<td>Information here is publicly accessible</td><td>Information here is restricted to registered users, meaning without credentials you might not be able to access the data</td></tr>
<tr>
<td>It's easy to develop since it requires only the basic web technologies.</td><td>It's more challenging, because it requires a higher level of security and functionalities based on its purpose.</td></tr>
<tr>
<td>It is cheaper to host</td><td>Expensive to host since more components are required like a database.</td></tr>
</tbody>
</table>
</div><h2 id="heading-wrap-up">Wrap Up</h2>
<p>Well to answer the question, which one should I choose between the two? It's simple:</p>
<ul>
<li>if you need to showcase your skills and products, a website should be your go to.</li>
<li>if you want a online tool to help you automate your process and get visitor interactions like e-commerce, a web application is your go to. </li>
</ul>
<p>As a developer, building websites is a relatively simple process once you know the tools and languages required. You just need the basics which you can get started with on <a target="_blank" href="https://www.freecodecamp.org/">freeCodeCamp</a>. </p>
<p>On the other hand, web apps require deeper knowledge, more planning, and it can be more expensive, too. To help you get started here is a resource you can check out: 
<a target="_blank" href="https://budibase.com/blog/how-to-make-a-web-app/">How to build a modern Web App</a> </p>
<p>Now, with a better understanding of the terms you should also be able to know what you interact with on a daily basis. </p>
<p>If you have read this far I really appreciate it! Do share your valuable opinion by connecting with me on: <a target="_blank" href="https://twitter.com/larymak1">Twitter</a> | <a target="_blank" href="https://www.linkedin.com/in/hillary-nyakundi-3a64b11ab/">LinkedIn</a> | <a target="_blank" href="https://github.com/larymak">GitHub</a></p>
<p>Enjoy Coding ❤</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
