Astro JS Tutorial: Quick Start Astro Guide
Tags: astro, webdev, javascript, svelte
Published: 2022-04-18 10:20:40

---
title: "Astro JS Tutorial: Quick Start Astro Guide"
published: "true"
description: "Astro JS tutorial: a quick start guide to building your first Astro app using React and Svelte and how you can deploy your it to the cloud."
tags: "astro, webdev, javascript, svelte"
canonical_url: "https://rodneylab.com/astro-js-tutorial/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5hbpg1o60hnn7j6f81n.png"
---

## 🧑🏽‍🎓 Astro JS Tutorial

In this Astro JS tutorial, we will see how to create an AstroJS site from spinning up a local development server right up to getting the app into the cloud and hosted. Although we will build a fairly simple site, we will see some important Astro features like hydration in action.  We will also take advantage of Astro&rsquo;s Islands architecture to add Svelte and React components. This is just a quick introduction to Astro so be sure to explore the related posts and videos listed at the bottom of the page to boldly go further!

## 🧱 Astro JS Tutorial: What we&rsquo;re Building

We build a simple app which shows YouTube videos in Svelte and React as well as an Astro Markdown component.  We will do that in three steps.  First of all is the setup, which should take a couple of minutes.  Then we will add our own content to the minimal app we created in the previous step.  Finally, we will see how to deploy the app to the cloud.

## 🧑🏽‍🦼 Quick Start

You spin up a new Astro project from the command line.  First you need to create a new directory for the project, then change into that directory and spin up Astro.  We will be using Svelte and React in our Astro JS tutorial app.  We can configure Astro to use these as part of the setup.  Let&rsquo;s do that now, running these commands:

```shell
mkdir astro-js-tutorial && cd $_
pnpm init astro
pnpm install
pnpm astro add react svelte
pnpm run dev
```

In the `init astro` step, select **Minimal**. Then in the `astro add` step enter yes so Astro configures React and Svelte for you.  After the final step you should have a dev server running.  The Terminal will give you the URL for it so you can open it in your browser.  Typically it will be running on <a aria-label="Open dev server running on defaulu port 3000" href="http://localhost:3000/">`http://localhost:3000/`</a>.  However, Astro will automatically find a free port if there is already something running on port `3000`.


![Astro JS Tutorial: Terminal Screenshot > @example/minimal@0.0.1 dev > astro dev 🚀  astro  v1.0.0-beta.7 started in 45ms ┃ Local    http://localhost:3001/ ┃ Network  use --host to expose ▶ This is a  beta  prerelease build Feedback? https://astro.build/issues](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ae13mxtual802sklcfx.png)

Open up the browser using the given URL and you should just see the word &lsquo;Astro&rsquo; in black text.  We will add our own content to this minimal app next.


![Astro JS Tutorial: Minimal Astro app browser screenshot, contains the word 'Astro' in black text and nothing else](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mo1g9o715jdtqsb2b4t3.png)

### `astro.config.mjs`

Just before moving on, open up `astro.config.mjs` in the project root folder.  This is Astro&rsquo;s main config file.  You will see the setup tool has imported the necessary Svelte and React integration packages and added them to the config for you.

## 🖋 Content

In this section we are going to build out our app, and learn a little about the structure of an Astro project in the process.  We will also see how to add vanilla CSS styling in Svelte.

Astro routing works similar to NextJS, Remix and <a aria-label="Open Svelte Kit psots " href="https://rodneylab.com/tags/sveltekit">SvelteKit</a>.  Astro generates pages from files in `src/routes`.  So the file you saw in the browser is generated from the content in `src/routes/index.astro`. Let&rsquo;s look at that file next.

### `src/routes/index.astro`

Replace the content in the file with this:

```astro
---
import "$styles/styles.css";
import { Markdown } from "astro/components";
import ReactVideo from "$components/Video.jsx";
import SvelteVideo from "$components/Video.svelte";
---

<html lang="en-GB">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.png" />
    <meta name="viewport" content="width=device-width" />

    <title>RodneyLab Minimal Astro Example</title>
  </head>
  <body>
    <main class="container">
      <h1>Astro JS Tutorial Site</h1>
      <p>
        This demo is not endorsed by Ben Awad, just thought the video content
        was fitting!
      </p>
      <ReactVideo client:load />
      <SvelteVideo client:load />
      <section class="mdx-container">
        <Markdown>
          ## Astro in 100 Seconds

          <div class="video-container">
          <iframe 
          title="Astro in 100 Seconds"
          width="560"
          height="315"
          src="https://www.youtube-nocookie.com/embed/dsTXcSeAZq8"
          frameborder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowfullscreen
          ></iframe>
          </div>
        </Markdown>
      </section>
    </main>
  </body>
</html>

<style>
  .container {
    display: flex;
    flex-direction: column;
    background: hsl(
      var(--colour-dark-hue) var(--colour-dark-saturation)
        var(--colour-dark-luminance)
    );
    color: hsl(
      var(--colour-light-text-hue) var(--colour-light-text-saturation)
        var(--colour-light-text-luminance)
    );
    padding: var(--spacing-8) var(--spacing-0) var(--spacing-32);
  }

  .container a {
    color: hsl(
      var(--colour-secondary-hue) var(--colour-secondary-saturation)
        var(--colour-secondary-luminance)
    );
  }
  .mdx-container {
    display: flex;
    flex-direction: column;
    background: hsl(
      var(--colour-alternative-hue) var(--colour-alternative-saturation)
        var(--colour-alternative-luminance)
    );
    align-items: center;
    width: 100%;
    padding: var(--spacing-8) var(--spacing-0) var(--spacing-12);
    color: hsl(
      var(--colour-light-text-hue) var(--colour-light-text-saturation)
        var(--colour-light-text-luminance)
    );
  }
</style>
```

We have a fair bit in here.  Broadly the file has three sections.  The first is the frontmatter in lines `1`&ndash;`6`.  In the first line we import our global stylesheet.  We will create that file in a moment. We also add the components which we will use to add video here.  This might be new if you are coming from an HTML / JavaScript / CSS background.  Though the other two sections might look a little more familiar.  In the frontmatter we can write JavaScript which defines any variables we want to use in the following section.  In our case, we are just importing items which we will use in the main markup.

The main markup (lines `8`&ndash;`44`) looks a lot like HTML, with a `head` and `body` section. As well as regular HTML elements, we use our ReactVideo and SvelteVideo components.

The final section contains styles.  The styles in this file will be automatically scoped just to the content of this file.  Our app will not work for the moment; we need to add some of the new content we reference in the frontmatter first.

#### Hydration

By default Astro does not load JavaScript &mdash; it will not hydrate the page. By including the `client:load` directive in lines `23` &amp; `24`, we tell Astro to hydrate our components.  Because of this, when we click the buttons in the app, our JavaScript code will change the background colour.

### `tsconfig.json`
We will put our components in a new `src/components` directory.  You might notice though that the import statement mentions `$components`.  This is just an alias for `src/components` which we will use for convenience.  For the alias to work, we need to update the `tsconfig.json` file in the project root folder. Let&rsquo;s do that:

```json
{
  "compilerOptions": {
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "$components/*": ["src/components/*"],
      "$styles/*": ["src/styles/*"]
    }
  }
}
```

You will notice we also defined `$styles`.  You can add any other aliases that might make sense for the project you are working on.  Note we only add these for convenience, they are optional and as an example we could instead have written `import '../styles/styles.css'`.  I prefer the alias, as well as looking neater, on larger projects where you might have to traverse multiple directories to get to your file, the syntax is more manageable.

### `src/styles/styles.css`

Speaking of styles, lets add our global styles. Astro works with SCSS, Tailwind and other styling frameworks.  To keep this project simple, and also because modern CSS is now quite powerful, we stick to vanilla CSS.  Create a `src/styles` folder and inside, add a `styles/css` file with the following content:

```css
:root {
  /* colours */
  /* honey-yellow */
  --colour-brand-hue: 39;
  --colour-brand-saturation: 92%;
  --colour-brand-luminance: 57%;

  /* safety orange blaze orange */
  --colour-secondary-hue: 21;
  --colour-secondary-saturation: 89%;
  --colour-secondary-luminance: 52%;

  /* moss green */
  --colour-alternative-hue: 84;
  --colour-alternative-saturation: 29%;
  --colour-alternative-luminance: 43%;

  /* pine tree */
  --colour-dark-text-hue: 100;
  --colour-dark-text-saturation: 18%;
  --colour-dark-text-luminance: 13%;

  /* old lace */
  --colour-light-text-hue: 50;
  --colour-light-text-saturation: 66%;
  --colour-light-text-luminance: 95%;

  --colour-dark-hue: 206;
  --colour-dark-saturation: 46%;
  --colour-dark-luminance: 37%;

  /* spacing */
  --spacing-px: 1px;
  --spacing-0: 0;
  --spacing-4: 1rem;
  --spacing-6: 1.5rem;
  --spacing-8: 2rem;
  --spacing-12: 3rem;
  --spacing-32: 8rem;
}

/* roboto-regular - latin */
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 400;
  src: local(""), url("/fonts/roboto-v29-latin-regular.woff2") format("woff2"),
    url("/fonts/roboto-v29-latin-regular.woff") format("woff");
}

/* roboto-700 - latin */
@font-face {
  font-family: "Roboto";
  font-style: normal;
  font-weight: 700;
  src: local(""), url("/fonts/roboto-v29-latin-700.woff2") format("woff2"),
    url("/fonts/roboto-v29-latin-700.woff") format("woff");
}

body {
  margin: var(--spacing-0);
  background: hsl(var(--colour-dark-hue) var(--colour-dark-saturation));
  font-family: Roboto;
  text-align: center;
}

h1 {
  font-size: 3.815rem;
  margin-bottom: var(--spacing-12);
}

h2 {
  font-size: 1.802rem;
  margin-bottom: var(--spacing-8);
}

p {
  margin: var(--spacing-6) var(--spacing-6) var(--spacing-8);
}

button {
  cursor: pointer;
  margin: var(--spacing-6);
  border: solid var(--spacing-px)
    hsl(
      var(--colour-light-text-hue) var(--colour-light-text-saturation)
        var(--colour-light-text-luminance)
    );
  border-radius: 50%;
  padding: var(--spacing-8);
}

.video-container {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  max-width: 560px;
  margin: var(--spacing-0) var(--spacing-6);
}

.video-container iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.screen-reader-text {
  border: 0;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  margin: -1px;
  width: 1px;
  overflow: hidden;
  position: absolute !important;
  word-wrap: normal !important;
}

.react-container {
  display: flex;
  flex-direction: column;
  background: hsl(
    var(--colour-secondary-hue) var(--colour-secondary-saturation)
      var(--colour-secondary-luminance)
  );
  align-items: center;
  width: 100%;
  padding: var(--spacing-8) var(--spacing-0);
  color: hsl(
    var(--colour-light-text-hue) var(--colour-light-text-saturation)
      var(--colour-light-text-luminance)
  );
}

.react-container-alt {
  background: hsl(
    var(--colour-alternative-hue) var(--colour-alternative-saturation)
      var(--colour-alternative-luminance)
  );
}

.react-button {
  background: hsl(
    var(--colour-alternative-hue) var(--colour-alternative-saturation)
      var(--colour-alternative-luminance)
  );
}

.react-button-alt {
  background: hsl(
    var(--colour-secondary-hue) var(--colour-secondary-saturation)
      var(--colour-secondary-luminance)
  );
}
```

In lines `42`&ndash;`58` you will notice we have some self-hosted fonts.  We will download those to our project shortly. At the bottom of the file you will see we have some style for our react component.  We take a different approach for the Svelte component taking advantage of in-built scoped styles, similar to what we have for the `index.astro` page.

### `src/components/Video.jsx`

Next we can paste in the React code.  Create a `src/components` folder and then create a Video.jsx file with the following content inside:


```jsx
import { useState } from "react";

export const ReactExample = function ReactExample() {
  const [altColours, setAltColours] = useState(false);

  return (
    <section
      className={`react-container${altColours ? " react-container-alt" : ""}`}
    >
      <h2>Example React Component</h2>
      <div className="video-container">
        <iframe
          width="560"
          height="315"
          src="https://www.youtube-nocookie.com/embed/PJ0QSJpJn2U"
          title="Should you Stop Using React"
          frameBorder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowFullScreen
        />
      </div>
      <button
        className={`react-button${altColours ? " react-button-alt" : ""}`}
        onClick={() => {
          setAltColours(!altColours);
        }}
      >
        <span className="screen-reader-text">Toggle colours</span>
      </button>
    </section>
  );
};

export default ReactExample;
```

### `src/components/Video.svelte`

The final component we need to add is the Svelte one.  The app should work again once we have this in place.  Create the `Video.svelte` file in the components folder, with this content:

```svelte
<script>
  $: altColours = false;
</script>

<section class={`container${altColours ? " container-alt" : ""}`}>
  <h2>Svelte Component</h2>
  <div class="video-container">
    <iframe
      title="Trying Svelte for the Third Time"
      width="560"
      height="315"
      src="https://www.youtube-nocookie.com/embed/xgER1OutVvU"
      frameborder="0"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowfullscreen
    />
  </div>
  <button
    class={`button${altColours ? " button-alt" : ""}`}
    on:click={() => {
      altColours = !altColours;
    }}><span class="screen-reader-text">Toggle colours</span></button
  >
</section>

<style>
  .container {
    display: flex;
    flex-direction: column;
    background: hsl(
      var(--colour-brand-hue) var(--colour-brand-saturation)
        var(--colour-brand-luminance)
    );
    align-items: center;
    width: 100%;
    padding: var(--spacing-8) var(--spacing-0);
    color: hsl(
      var(--colour-dark-text-hue) var(--colour-dark-text-saturation)
        var(--colour-dark-text-luminance)
    );
  }

  .container-alt {
    background: hsl(
      var(--colour-secondary-hue) var(--colour-secondary-saturation)
        var(--colour-secondary-luminance)
    );

    color: hsl(
      var(--colour-light-text-hue) var(--colour-light-text-saturation)
        var(--colour-light-text-luminance)
    );
  }

  .button {
    background: hsl(
      var(--colour-secondary-hue) var(--colour-secondary-saturation)
        var(--colour-secondary-luminance)
    );
  }

  .button-alt {
    background: hsl(
      var(--colour-brand-hue) var(--colour-brand-saturation)
        var(--colour-brand-luminance)
    );
  }
</style>
```

### Fonts

We mentioned that we are using self-hosted fonts above.  For the hosting to work,we need to include the fonts in our repo so our host can serve them. <a href="https://google-webfonts-helper.herokuapp.com/api/fonts/roboto?download=zip&subsets=latin&variants=700,regular&formats=woff,woff2">Download the Roboto Font in Regular, 400 and 700</a>.  Extract the zip and then create a new `fonts` folder in the project&rsquo;s public folder.  Drop the four unzipped files in that folder.  The public folder is for anything which we do not need Astro (or Vite, under the hood) to process.  As well as fonts, web manifest files for PWA and favicons fall into this category.

We won&rsquo;t optimise fonts here, just to get finished a little quicker.  There is a <a href="https://rodneylab.com/astro-self-hosted-fonts">nice video which focusses on self-hosted fonts in Astro</a> together with optimisation.  If you are interested in optimisation, do take a look.  You can save 80% on some fonts files, especially where, for example you only use the 900 weight font in titles.

## 🍧 Hosting

The app should be working just fine now, with a nice Roboto sans serif font and all the colours.  Try pressing the buttons below the React and Svelte components to check they work.  You should notice the background colour change.

The next step is to build the site locally to check it is all working as expected.  Run these commands to build and preview the site (stop the dev server with <kbd>ctrl</kbd> <code>+</code> <kbd>C</kbd> first):

```shell
pnpm run build
pnpm run preview
```

If all is well, commit the code to a git repo and upload it to your GitHub or GitLab account, so we can host it as a static site.  You might notice your site gets built to the `dist` directory in your project.  There is no need to include this in your repo as your host will generate the site there for you.

It is worth adding a `.nvmrc` file to the project root folder whichever host you are using.  This will tell the host know which version of node to use.  We will go for the long-term support (LTS) version which is `16` at the time of writing:

```plaintext
16
```

### Configuration

Although we have used `pnpm` in this tutorial to build the site, for maximum compatibility, in the cloud use `npm run build` as your build command.  We just mentioned that Astro outputs projects to the `dist` directory, so on your host console, set the **build output directory** or **publish directory** to `dist`.

Here are screenshots for <a aria-label="Open Netlify Astro deploy full details" href="https://docs.netlify.com/integrations/frameworks/astro/">Netlify</a> and <a aria-label="Open Cloud flare pages Astro deploy full details" href="https://developers.cloudflare.com/pages/framework-guides/astro/">Cloudflare Pages</a> which should help you out.  Other services will be similar.  Select the Astro preset if your host has one, then just check the build command and output / publish directory is `dist`.

![Astro JS Tutorial: Astro JS Tutorial: Netlify hosting screenshot shows build configuration with base directory blank,build command set to 'npm run build' and publish directory set to 'dist'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6g90ueepcrno6rk2q3b.png)

![Astro JS Tutorial: Astro JS Tutorial: Cloudflare pages hosting screenshot shows build configuration with Framework preset set to 'Astro', build command set to 'npm run build' and build output directory set to 'dist'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0durz71swagpxozwlns.png)

## 🙌🏽 Astro JS Tutorial: Wrapping Up

In this post we have run through the pipeline for building a static Astro site. We have seen:

- how to spin up a new Astro project with Svelte and React integrations,
- how you can add global CSS styles, local scoped styles and style React components with plain CSS,
- configuration for deploying your static Astro site to the cloud.

The <a aria-label="Open the Astro JS tutorial code in the Rodney Lab Git Hub Repo" href="https://github.com/rodneylab/astro/tree/main/demos/astro-js-tutorial">Astro JS tutorial code is in the Rodney Lab GitHub repo</a>.  You can also <a aria-label="Open the Astro blog Markdown starter in Stack Blitz" href="https://stackblitz.com/github/rodneylab/astro/tree/main/demos/astro-js-tutorial">try it on Stackblitz</a>.

I hope you found this article useful and am keen to hear how you will the starter on your own projects as well as possible improvements.

## 🙏🏽 Astro JS Tutorial: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a>. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.