Learning Astro


What is Astro?

  • Astro is a modern web framework for building fast, content-focused websites. It is particularly good for blogs, documentation sites, portfolios, marketing sites, landing pages, and other sites where most of the page is content rather than highly interactive application UI.

  • Astro is a web framework, similar in category to - Next.js, Nuxt, SvelteKit, Remix, Gatsby, Jekyll, etc.; but with a different approach.

    Traditional Frameworks Astro Framework
    Browser
          ↓
    JavaScript application
          ↓
    Render page
          ↓
    User sees content
    Astro
        ↓
    Build HTML
        ↓
    Browser Receives HTML
        ↓
    User Immediately sees content
  • JavaScript is added only where you actually need interactivity.


Why Use Astro?

  • Modern websites often ship enormous amounts of JavaScript. Imagine a blog page containing the elements where there’s almost no interactivity, yet traditional React application could potentially send JavaScript for something that doesn’t need JavaScript. Instead, Astro renders most of the page into plain HTML and CSS.

  • Astro follows Islands Architecture: In the below diagram, Astro calls that interactive portion an island. Most of the page doesn’t need JavaScript, only the comments widget can be using JavaScript if required.

    ┌───────────────────────────────┐
    │ Navbar                        │
    ├───────────────────────────────┤
    │ Blog Article                  │
    │ Lots of Text                  │
    │ [Interactive Comments Widget] │
    │ More Article Text             │
    └───────────────────────────────┘
  • Astro is not limited to its own component system. You can use Astro components, React, Vue, Svelte, Preact, Solid, or other framework integrations. It is an all-in-one web framework. It includes everything you need to create a website, built-in.

  • Astro has first-class Markdown support. For a blog, you can keep almost all of your article content in separate .md files and let Astro handle routing, layouts, HTML generation, RSS, SEO, etc.

  • Astro also allows Markdown files to contain Frontmatter alongside their content. Frontmatter is a block of structured metadata placed at the absolute beginning of a markdown file that can be used to display blog information without maintaining a separate database.

    ---
    title: "What is Astro?"
    description: "A beginner's guide to Astro."
    pubDate: 2026-08-15
    author: "Rishabh"
    tags:
      - Astro
      - Web Development
    ---
    
    # What is Astro?
    
    Your article starts here...
  • Astro supports both .md and .mdx files side-by-side, though .mdx requires adding an official integration. It also supports extended Markdown syntax natively.

MDX is an extension of standard Markdown that lets you write JSX, use JavaScript expressions, and import UI components directly inside your documents.


Essential Prerequisites for Learning Astro

  • HTML

    • Basic HTML attributes.
    • Headings, paragraphs, links, images & forms.
    • Semantic elements (header, nav, main, section, article, etc.)
  • CSS

    • Selectors, Box model, Flexbox & Grid.
    • Responsive design.
    • Basic positioning.
  • JavaScript

    • Variables, functions, arrays, objects, if/else & loops.
    • DOM basics & ES6 syntax.
    • Promises and async/await — useful later, but not required to start.
  • Node.js + npm

    • Know what Node.js is.
    • Installing packages.
    • npm install, npm run dev, npm run build
  • Basic command line

    • Creating files/folders.
    • cd, dir / ls
  • Basic Git (optional)

    • Working with Git.
    • git init, git add, git commit, git push

Setting Up Required Dev Tools

  • Node.js: Astro supports even-numbered Node.js versions. The current minimum supported version is v22.12.0. Odd-numbered versions like v23 are not supported.

    To check to see whether you already have a compatible version installed you can use following command in your terminal:

    node -v               # v24.19.0
    npm -v                # 11.17.0
  • For MDX support install Astro’s official MDX integration using below command:

    npx astro add mdx
  • Code Editor: VS Code is an excellent choice for Astro and is the officially recommended text editor by the Official Astro Documentation. You can use below mentioned VS Code extensions to make your coding fast and error free.

    • Astro - Official Astro language support
    • Prettier - Code formatter
    • Markdown All in One - Useful if you’re writing lots of Markdown
  • Git Setup: Follow this quick guide to install and setup git Setting up Git.


Creating Astro Project

  1. Run the following command using your preferred package manager.

    npm create astro@latest              // npm
      # or
    pnpm create astro@latest             // pnpm
      # or
    yarn create astro                    // yarn
  2. Enter y to install create-astro.

  3. When the prompt asks you where to create the project, type in the name of a folder to create a new directory for your project, e.g. ./my_blogs

  4. You will see a short list of starter templates to choose from. Use the arrow keys (up and down) to navigate to the minimal (empty) template, and then press return (enter) to submit your choice.

    astro   Launch sequence initiated.
    
     dir   Where should we create your new project?
           ./my_blogs
    
    tmpl   How would you like to start your new project?
           — A basic, helpful starter project (recommended)
           — Use blog template
           — Use docs (Starlight) template
           > Use minimal (empty) template
  5. When the prompt asks you whether or not to install dependencies, enter y.

  6. When the prompt asks you whether or not to initialize a new git repository, enter y.

  7. When the install wizard is complete, you no longer need this terminal. You can now open VS Code to continue.


Running Project In Dev Mode

  1. In VS Code open the Astro project folder that you created during the setup earlier and setup required extensions will provide syntax highlighting and autocompletions for your Astro code.

  2. Toggle the visibility of terminal inside VS Code using Ctrl + J or Cmd ⌘ + J in macOS.

  3. Start the Astro local dev server by running the below command into VS Code’s terminal:

    npm run dev                    // npm
      # or
    pnpm run dev                   // pnpm
      # or
    yarn run dev                   // yarn
  4. Astro starts a local development server at http://localhost:4321/. Enter this into the browser to see a live preview of your new Astro website!

    Here’s what the Astro “Empty Project” starter website should look like in the browser preview. Preview of astro project

  5. Keep the server running in the VS Code terminal and hide it by toggling its visibility.


Understanding Astro Project Structure

  • Overall Structure

    The project currently looks like this:

    my_blogs/                          # Project root directory.
    
    ├── .astro/                        # These are are Astro generated internal files/dirs containing
    │   ├── collections/               # dev data, project settings, content collection information,
    │   ├── dev.json                   # and TypeScript type declarations used by Astro and editor,
    │   ├── settings.json              # so you generally don’t edit or manage them manually.
    │   └── types.d.ts
    
    ├── .vscode/                       # VS Code project-specific configuration directory
    │   ├── extensions.json            # containing settings that can be shared with anyone
    │   └── launch.json                # who opens the project in VS Code.
    
    ├── node_modules/
    
    ├── public/
    │   ├── favicon.ico
    │   └── favicon.svg
    
    ├── src/
    │   └── pages/
    │       └── index.astro
    
    ├── .gitignore                     # Tells Git which files and folders not to track or commit.
    ├── AGENTS.md                      # Instruction file intended for AI coding agents or dev tools.
    ├── CLAUDE.md                      # Instruction file for Claude/AI coding agents.
    ├── astro.config.mjs
    ├── package-lock.json
    ├── package.json
    ├── README.md                      # This is project documentation written in markdown.
    └── tsconfig.json                  # The configuration file for TypeScript, defining
                                       # how TypeScript should check, interpret, and
                                       # compile your project's .ts and .tsx files.
  • node_modules/

    • This is the folder where npm stores all the actual JavaScript/Node.js packages installed for your project, such as Astro, Vite, and TypeScript, along with their own dependencies.

    • When you run npm install, npm reads the dependencies listed in package.json and downloads them into node_modules/.

    • You normally should never edit this folder manually; if it becomes corrupted or causes problems, you can delete it and run npm install again to recreate it.

    • Since node_modules/ can be very large and can always be regenerated from package.json and the lock file, it is normally not committed to Git and is therefore included in .gitignore.

  • public/

    • This is an Astro directory used for static assets that should be copied directly to the final website without being processed.
    • It can contain files such as images, PDFs, favicon.svg, hello.txt, and downloadable documents.
    • The folder structure is preserved in the generated website; for example, public/hello.txt becomes accessible at /hello.txt.
  • src/

    • This is the main directory where your Astro website’s source code and content live. Most of your development work will happen inside this directory.

    • It initially contains files such as src/pages/index.astro, but as the project grows, it can include directories like components/, content/, layouts/, pages/, styles/, and assets/.

  • src/pages

    • This is one of the important directories in Astro because it controls your website’s routing through file-based routing. In simple terms, the files you create inside src/pages/ determine the URLs of your website.
    • For example, index.astro becomes / (the homepage), about.astro becomes /about, and contact.astro becomes /contact. Thus, each page file generally corresponds to a route on your website.
  • Understanding .astro File

    • The .astro extension means this is an Astro component/page.

    • This file has two main parts: Frontmatter and the Template section.

    • The frontmatter is the section at the top of the file, enclosed between two sets of ---. It contains JavaScript/TypeScript code that runs during the Astro build/rendering process. You can use it to define variables, import components, fetch data, process content, and prepare everything your page needs.

    • Everything after the closing --- is the template. This section looks similar to HTML, but Astro also allows you to use expressions, components, and Astro-specific syntax. It defines what gets rendered on the webpage.

  • astro.config.mjs

    • It is one of the important configuration files in an Astro project. It tells Astro how the project should behave and how it should be built or run.

    • The .mjs extension indicates that it is a JavaScript ES module. This file can configure things such as integrations, site URL, base path, build behavior, server settings, adapters, Markdown, redirects, experimental features, and image handling.

  • package.json

    • It is one of the important files in a Node/Astro project because it describes the project and defines its dependencies, development dependencies, project metadata, and npm scripts.

    • The scripts section inside it is especially important because it defines commands that can be run with npm run. For example, npm run dev executes astro dev to start the development server, npm run build executes astro build to create the production build, and npm run preview executes astro preview to preview that build.

  • package-lock.json

    • It is an npm-generated file that records the exact versions and dependency tree resolved for your project. While package.json specifies the dependencies your project needs, package-lock.json records the specific versions actually resolved by npm, making installations more consistent and reproducible across different environments.

    • You generally should not edit it manually; npm updates it automatically when necessary, such as when running npm install.


Creating GitHub Repository

  • Use GitHub to store your repository and connect it to a web host to deploy it live on the internet. Feel free to use any alternative online Git provider if you prefer.

  • Before creating the GitHub remote repository, let’s make an initial commit by making the following change to the src/pages/index.astro file.

    <body>
    <h1>Astro</h1>
      <h1>My Astro Blog</h1>
    </body>

    Then commit the changes using the following commands:

    cd my_blogs
    git add .
    git commit -m "Initial commit"
  • Option A: Create a remote repository on GitHub with the same name as your local repository, then link and push your code to it.

    git remote add origin https://github.com/<USERNAME>/my_blogs.git
    
    git branch -M main
    git push -u origin main
  • Option B: If you have a local repository and the corresponding remote repository does not yet exist, you can push the repository to GitHub, creating the remote repository in the process.

    For a public repository:

    git branch -M main
    gh repo create my_blogs --source=. --public --push

    For a private repository:

    git branch -M main
    gh repo create my_blogs --source=. --private --push

    If you encounter an error such as gh: command not found, install the GitHub CLI using the following command: sudo apt install gh and authorize using the command: gh auth login.


Deploy Project

  • You can use various providers to deploy your website. We’ll be using Cloudflare Pages. Follow these steps to set up a Cloudflare Page and use the configuration options below when connecting it to your GitHub repository.

    Framework preset: Astro
    Build command: npm run build
    Build output directory: dist


Pages

  • Adding Pages (.astro files) in src/pages/.

  • Adding Posts (.md files) in src/pages/posts.

  • Adding JS elements in page’s frontmatter and using them in template section.

  • Adding stylesheet src/styles/global.css.


Components

  • Astro components, usually defined inside src/components/ allow us to reuse code for common elements across your website. For example:

    • A Navigation component that presents a menu of links to your pages.
    • A Footer component to include at the bottom of each page.
    • A Social Media component, used in the Footer, that links to profile pages.
    • An interactive Menu component to toggle the Navigation on mobile
  • The directory src/components/ holds .astro files that will generate HTML but that will not become new pages on your website.

  • Create a new file: src/components/Navigation.astro add navigation links for each of the pages. For example:

    ---
    ---
    <a href="/">Home</a>
    <a href="/about/">About</a>
    <a href="/blog/">Blogs</a>
    • Import your new component into the frontmatter section of all the pages:
    import Navigation from '../components/Navigation.astro';
    • Add the new navigation component to the template section of all the pages:
    <Navigation />
  • Similarly, you can add other common components like Footer.astro, Social.astro, Header.astro, Menu.astro, etc.

  • JavaScript Components: We can use a directory like src/scripts/ to store common JavaScript scripts that can be imported and used wherever required.

Any JavaScript written in a component’s frontmatter is executed at build time only and is not sent to the browser.
This is better because it makes the important distinction clear: the frontmatter code does not become client-side JavaScript.

Optionally, Astro can send JavaScript to the browser to add client-side interactivity to components.
Client-side JavaScript is sent to the user’s browser when it is included in a component’s <script> tag or loaded as an external script.


Layouts

  • A layout is a reusable component that defines the common structure shared by multiple pages.

  • They are usually stored in the src/layouts/ directory.

  • <slot />: This is a special Astro element that acts as a placeholder for content passed into a component.

    • For example, we can define a layout component src/layouts/BaseLayout.astro.

    • This layout can then be used by different pages like this:

    ---
    import BaseLayout from "../layouts/BaseLayout.astro";
    const pageTitle = "Home Page";
    ---
    
    <BaseLayout>
      <h2>My Blog's Subtitle</h2>        <!--content between its opening and closing tags-->
    </BaseLayout>
    • We can place <slot /> anywhere in BaseLayout.astro where we want the content passed to the component to be displayed.
    • Astro renders the content passed between the component’s opening and closing tags at the location of . This allows components, especially layouts, to define a reusable structure while letting each page or component provide its own content.

    The <slot /> allows you to inject (or “slot in”) child content written between opening and closing <Component> ... </Component> tags to any Component.astro file.

  • Astro.props: Props (short for properties) are values passed from a parent component to a child component. They allow a component to receive data from the component that uses it, making the component reusable with different data.

    A parent component or page can pass one or more props to a component:

    <BaseLayout pageTitle={pageTitle}>
      <h2>My Blog's Subtitle</h2>
    </BaseLayout>
    
    <!-- or -->
    
    <BaseLayout pageTitle={pageTitle} description="A technical blog." >
    <h2>My Blog's Subtitle</h2>
    </BaseLayout>

    The component (the layout in this case) can access the passed props through Astro.props:

    ---
    const { title } = Astro.props;
    ---

    Or, when multiple props are passed:

    ---
    const { title, description } = Astro.props;
    ---
  • Astro automatically treats the Markdown content as the slot content when the Markdown file uses that layout: When using a common layout for Markdown posts (.md files), you don’t need to wrap the Markdown content in component tags to make it available to <slot />. Astro automatically passes the rendered Markdown content to the layout’s <slot />.

    For example, create a new layout at src/layouts/MarkdownPostLayout.astro:

    ---
    const { frontmatter } = Astro.props;
    ---
    <meta charset="utf-8" />
    <h1>{frontmatter.title}</h1>
    <p>{frontmatter.pubDate.toString().slice(0,10)}</p>
    <p><em>{frontmatter.description}</em></p>
    <p>Written by: {frontmatter.author}</p>
    <slot />

    You can then apply this layout to a Markdown post, such as src/pages/posts/post-1.md, using the layout property in the frontmatter:

    ---
    layout: ../../layouts/MarkdownPostLayout.astro
    title: 'My First Blog Post'
    pubDate: 2026-07-01
    description: 'This is the first post of my new Astro blog.'
    author: 'Astro Learner'
    ---
    
    ## Welcome to my _new blog_ about ...

    Here, the Markdown content (## Welcome...) is automatically rendered and passed to in MarkdownPostLayout.astro

  • Nested layouts: You can reuse an existing layout inside another layout to combine their functionality. Since you already have a ‘BaseLayout.astro’ that defines the overall layout of your pages, you can nest it inside ‘MarkdownPostLayout.astro’. ‘MarkdownPostLayout.astro’ provides additional templating for common blog post properties such as title and date, while ‘BaseLayout.astro’ ensures that your blog posts share the same overall look and feel as the rest of your site.

    To do this, import BaseLayout.astro in src/layouts/MarkdownPostLayout.astro and use it to wrap the entire template content.

    ---
    import BaseLayout from "./BaseLayout.astro";
    const { frontmatter } = Astro.props;
    ---
    
    <BaseLayout pageTitle={frontmatter.title}>
      <p>{frontmatter.pubDate.toString().slice(0, 10)}</p>
      <p><em>{frontmatter.description}</em></p>
      <p>Written by: {frontmatter.author}</p>
      <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
      <hr />
      <slot />
    </BaseLayout>

    Here, we have removed the <h1> and <meta> tags from this file because they are already handled by BaseLayout.astro.


Astro APIs

  • import.meta.glob(): It allows you to import multiple files from your local filesystem at once using wildcard patterns. It scans your project directory for files matching the specified pattern and returns them as an object, where the object keys are the file paths. It is particularly useful for working with Markdown/MDX blog posts, images, JSON files, and other files that need to be discovered dynamically.

    For example, suppose you have a page that links to all the posts in the src/pages/posts directory:

    <ul>
      <li><a href="/posts/post-1/">Post 1</a></li>
      <li><a href="/posts/post-2/">Post 2</a></li>
      <li><a href="/posts/post-3/">Post 3</a></li>
    </ul>

    Instead of manually adding each post to the list, we can generate the entire list dynamically using import.meta.glob():

    ---
    const allPosts = Object.values(
        import.meta.glob('./posts/*.md', { eager: true }),
    );
    ---
    <ul>
      {allPosts.map((post: any) => (
          <li>
            <a href={post.url}>{post.frontmatter.title}</a>
          </li>
      ))}
    </ul>

    Here, ./posts/*.md matches all Markdown files in the posts directory, while { eager: true } tells Astro to import all matching files immediately. We then use Object.values() to get the imported files as an array, allowing us to iterate over them with .map().

  • Dynamic Routes: Files with square brackets in their names (like [slug].astro) are used to create dynamic routes. They let one template generate many pages based on changing URL parameters or data. Square brackets indicate a dynamic route parameter.

    Now suppose you have:

    src/pages/tags/[tag].astro

    [tag] is not treated as the literal word tag. Instead, Astro treats it as a placeholder. So the same file can represent:

    /tags/astro/
    /tags/blogging/
    /tags/successes/
    /tags/community/

    The value inside [tag] changes. Think of it like:

    /tags/[tag]/
    
         variable

    Dynamic Parameters extracted from the current URL is stored in Astro.params object. In the above example, if you visit host_url/tags/astro, Astro.params will essentially contain:

    {
      tag: "astro"
    }

    The tag parameter can be accessed as:

    const tag = Astro.params.tag;
    
    // or
    
    const { tag } = Astro.params;

    Astro also supports Rest Parameters, which allow a single file to match URL paths of any depth, and Multiple Dynamic Parameters, which allow a single file to capture multiple variables from a URL simultaneously.

  • getStaticPaths(): This function tells a dynamic page (like [tag].astro) which URL paths to pre-render into static HTML files during the build process. It is required for dynamic routes when your site is in static generation mode.

    Here, we have defined 5 static pages in src/pages/tags/[tag].astro that Astro will generate:

    export async function getStaticPaths() {
      return [
        { params: { tag: "astro" } },                 // host_url/tags/astro
        { params: { tag: "successes" } },             // host_url/tags/successes
        { params: { tag: "community" } },             // host_url/tags/community
        { params: { tag: "blogging" } },              // host_url/tags/blogging
        { params: { tag: "setbacks" } },              // host_url/tags/setbacks
      ];
    }
    
    const { tag } = Astro.params;

    Astro calls getStaticPaths() function internally during the build process. It uses the returned array to determine which routes to generate. Each generated page uses the same template defined in src/pages/tags/[tag].astro, with the corresponding tag value available through Astro.params.tag.

  • Using props in dynamic routes: You can add following props to your getStaticPaths() function in order to make data from all your blog posts available to each page route:

    export async function getStaticPaths() {
      const allPosts = Object.values(
        import.meta.glob("../posts/*.md", { eager: true }),
      );
    
      return [
        { params: { tag: "astro"}, props: {posts: allPosts}},
        { params: { tag: "successes"}, props: {posts: allPosts}},
        { params: { tag: "community"}, props: {posts: allPosts}},
        { params: { tag: "blogging"}, props: {posts: allPosts}},
        { params: { tag: "setbacks"}, props: {posts: allPosts}},
      ];
    }
    
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    
    const filteredPosts = posts.filter((post: any) => post.frontmatter.tags?.includes(tag));

    And now, this information can be used in various ways just like here we are using it to display links to all the posts containing the selected tag:

    <h2>Posts tagged with {tag}</h2>
    <ul>
      {
        filteredPosts.map((post: any) => (
          <li>
            <a href={post.url}>{post.frontmatter.title}</a>
          </li>
        ))
      }
    </ul>

    Each post can define its attributes(metadata) in the frontmatter, for example:

    ---
    title: "My First Blog Post"
    tags: ["successes"]
    ---

    If you need information to construct the page routes, write it inside getStaticPaths().
    To receive information in the HTML template of a page route, write it outside getStaticPaths().

    Also, we can create an array containing all the existing tags used across all your posts using Astro’s built-in TypeScript support:

    export async function getStaticPaths() {
      const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));
    
      const uniqueTags = [...new Set(allPosts.map((post: any) => post.frontmatter.tags).flat())];
    
      return uniqueTags.map((tag) => {
        const filteredPosts = allPosts.filter((post: any) => post.frontmatter.tags.includes(tag));
        return {
          params: { tag },
          props: { posts: filteredPosts },
        };
      });
    }
    
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    
    const filteredPosts = posts.filter((post: any) => post.frontmatter.tags?.includes(tag));

Adding RSS Feed

  • An RSS Feed (Really Simple Syndication) is a standardized way for websites to publish updates that people can subscribe to. It generates a non-HTML document with information about all of your blog posts that can be read by Feed Readers. This document is updated every time your site is rebuilt.

  • Astro provides an official integration package that makes it easy to add an RSS feed to your website. Install it using:

npm install @astrojs/rss
  • Create a new file in src/pages/ called rss.xml.js and add the following code:
import rss, { pagesGlobToRssItems } from '@astrojs/rss';

export async function GET(context) {
  return rss({
    title: 'Astro Learner | Blog',
    description: 'My journey learning Astro',
    site: context.site,
    items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')),
    customData: `<language>en-us</language>`,
  });
}
  • Add the site property to the Astro configuration with your website’s unique URL:
import { defineConfig } from "astro/config";

export default defineConfig({
  site: "https://example.com"
});
  • You can now access your RSS feed at http://localhost:4321/rss.xml. It will display the raw, unformatted XML containing an item for each of your .md files.

Astro Islands

  • Astro helped pioneer and popularize a frontend architecture pattern called Islands Architecture.

  • Islands Architecture works by rendering the majority of a page as fast, static HTML, while adding smaller “islands” of JavaScript only where interactivity or personalization is needed—for example, an image carousel, search bar, or interactive form.

  • This approach avoids the large, monolithic JavaScript payloads that can slow down the responsiveness of many modern JavaScript web frameworks.

  • Astro can use Preact, a lightweight JavaScript library for building interactive user interfaces, to create these interactive islands.

  • You can add Preact to an Astro project with the following command:

npx astro add preact
  • Creating a Preact Greeting Banner: This component will take an array of greeting messages as a prop and show the first as a welcome message. The user can click a button to get a new random message.

    • Create a new file in src/components/ named Greeting.jsx and add following code:
    import { useState } from 'preact/hooks';
    
    export default function Greeting({messages}) {
    
      const randomMessage = () => messages[(Math.floor(Math.random() * messages.length))];
      const [greeting, setGreeting] = useState(messages[0]);
    
      return (
        <div>
          <h3>{greeting}! Thank you for visiting!</h3>
          <button onClick={() => setGreeting(randomMessage())}>
            New Greeting
          </button>
        </div>
      );
    }
    • Import and use this component on your Home page index.astro:
    ---
    import Greeting from '../components/Greeting';
    const pageTitle = "Home Page";
    ---
    <h1>{pageTitle}</h1>
    <Greeting client:load messages={["Hi", "Hello", "Howdy", "Hey there"]} />
    • The client:load directive tells Astro to load and execute the component’s JavaScript on the client as soon as the page loads, making the component interactive. This process hydrates the component, making it a hydrated component.

    Hydrated Component: A component whose already-rendered HTML has been connected to its client-side JavaScript logic and is therefore interactive.

    Hydration: The process of attaching client-side JavaScript behavior to already-rendered HTML so that the component becomes interactive.

  • Back on Dry Land: You can go pretty far with just vanilla JavaScript and CSS!

    • Let’s build a clickable icon to let your users toggle between light or dark mode using another <script> tag for interactivity without sending any framework JavaScript to the browser.

    • Create a new file at src/components/ThemeIcon.astro and paste the following code into it:

    ---
    ---
    <button id="themeToggle" aria-label="Toggle theme">
      <svg aria-hidden="true" width="30px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
        <path class="sun" fill-rule="evenodd" d="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"/>
        <path class="moon" fill-rule="evenodd" d="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"/>
      </svg>
    </button>
    
    <style>
      #themeToggle { border: 0; background: none; }
    
      .sun { fill: black; }
      .moon { fill: transparent; }
    
      :global(.dark) .sun { fill: transparent; }
      :global(.dark) .moon { fill: white; }
    </style>
    • Import and add the <ThemeIcon /> component to index.astro as shown below:
    ---
    ...
    const pageTitle = "Home Page";
    import ThemeIcon from "../components/ThemeIcon.astro";
    ---
    
    <ThemeIcon />
    <h1>{pageTitle}</h1>
    ...
    • Open global.css and define the styles for dark mode:
    html.dark {
      background-color: #0d0950;
      color: #fff;
    }
    
    .dark .menu {
      background-color: #fff;
      color: #000;
    }
    
    .dark .nav-links a:hover,
    .dark .nav-links a:focus {
      color: #0d0950;
    }
    
    .dark .nav-links a {
      color: #fff;
    }
    
    .dark a {
      color: #ff9776;
    }
    • Finally, add interactivity to an Astro component, use a <script> tag src/components/ThemeIcon.astro after your <style> tag:
    ...
      :global(.dark) .sun { fill: transparent; }
      :global(.dark) .moon { fill: white; }
    </style>
    
    <script is:inline>
      window.localStorage.setItem("theme", "light");
    
      const handleToggleClick = () => {
        const element = document.documentElement;
        element.classList.toggle("dark");
        const isDark = element.classList.contains("dark");
        localStorage.setItem("theme", isDark ? "dark" : "light");
      };
    
      document.getElementById("themeToggle")?.addEventListener("click", handleToggleClick);
    </script>

    Thus, we’ve created an interactive Astro component using plain JavaScript, without hydrating it as an Astro island. Astro renders the component’s HTML and CSS as usual, while the inline <script> adds the client-side behavior needed for the light/dark theme toggle.


Upgrade Dependencies

Upgrade to the latest version of Astro, and upgrade all integrations to their latest versions by running the following commands in your terminal:

npx @astrojs/upgrade