Setting Up Astro for a Real Project
Astro is a static site generator that's gotten a lot of attention lately, mostly because it lets you use any UI framework you want—React, Vue, Svelte, whatever—while shipping almost nothing to the browser by default. The core idea is island architecture. You build pages as islands of interactivity surrounded by static HTML. It sounds fancy but it's pretty straightforward once you actually build something with it. Here's how I got started. You need Node.js installed, preferably version 18 or newer. Run npm create astro@latest in whatever directory you want your project in. It'll walk you through a few questions—whether you want a TypeScript project, linting, and Git initialization. Just say yes to the defaults if you're not sure. Then cd into the folder and run npm install followed by npm run dev. That's it. Your site is running on localhost:4321. The project structure matters more than most tutorials admit. You've got a src/pages directory that maps directly to your URL routes. A file called index.astro becomes your homepage. A file called about.md automatically gets rendered as /about. There's also src/components for reusable pieces and public for assets that just sit there untouched. The router is content-first, which means file-based routing works exactly like you'd expect from Next.js or Nuxt but without the page directory magic.
Installing Astro in an Existing Codebase
If you already have a project and just want to bolt Astro onto it, that's easy too. Run npm install astro and then npx astro add react or whatever framework you're using. Astro has a CLI command that edits your config for you, adds the adapter, and sets everything up. It's convenient but I've seen it break things in my own projects. Once I ran it and suddenly my postcss config was duplicated and Tailwind stopped compiling correctly. The fix was deleting my node_modules, clearing the Astro cache, and re-adding the integration manually instead of relying on the auto-wizard. When you're building out a page, the .astro extension uses a syntax that looks like HTML but has some tricks. You can import components from other files, run server-side JavaScript, and inject content all inside what looks like a template. Here's a minimal example: ```astro
<!-- src/pages/index.astro -->
<Layout title="Home">
<main>
<h1>Hello</h1>
<Counter client:load />
</main>
</Layout>
---
import Layout from '../components/Layout.astro';
import Counter from '../components/Counter.astro';
---
That --- separator is the frontmatter. Code between those lines runs on the server at build time. Everything below it is the template. The client:load directive tells Astro to hydrate the Counter component only when the page finishes loading in the browser. That's the whole island thing in action. Without that directive, the component stays inert and ships zero JavaScript to the client. You'll probably want to deploy this somewhere. Astro has official adapters for Vercel, Netlify, Cloudflare, and Node. The one you pick determines how npm run build behaves. For a static site, use @astrojs/vercel/static or @astrojs/cloudflare. For SSR, go with @astrojs/vercel or @astrojs/node. The adapter gets added to your astro.config.mjs file. A typical config looks like this: ```js
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
export default defineConfig({
adapter: vercel(),
integrations: ['@astrojs/react'],
});
```
Get the Full Details

One thing that trips people up is the difference between client:load, client:idle, client:visible, and client:only. These are your hydration strategies. client:load hydrates immediately when the page loads. client:idle waits until the main thread is free. client:visible only hydrates when the component scrolls into view. client:only skips the server render entirely and hydrates purely on the client. Most beginners throw client:load on everything because it's the default, but that defeats the performance advantage of the framework. I've seen sites where every component was hydrated on load and the total JavaScript payload was nearly identical to what you'd ship with a regular React app. That's a bad look for Astro.
Things Nobody Tells You About Astro
Content collections are one of those features that look simple but have real power. If you put markdown files in src/content/blog/, Astro will typecheck them, validate their frontmatter, and give you autocomplete. You define a schema in src/content/config.ts and that's basically it. But here's the catch—every time you add a new field to your schema, it only applies to files you explicitly reference through the collection API. Old data that doesn't match gets ignored at build time with a warning, not an error. That's convenient until it isn't, and then you're spending an hour tracking down why a field disappeared from your output. Another thing: Astro's partial hydration is great until you need something that requires full-page interactivity. If your entire site is essentially a single React app that needs to be interactive from the get-go, you're better off with something like Next.js or Remix. Astro shines when you have a content-heavy site with occasional interactive components. Blog posts, documentation, marketing pages—that's where it earns its keep. A dashboard or a real-time chat interface is going to fight you at every turn. I ran into a specific problem recently that took me way too long to solve. I was building a documentation site with hundreds of pages and heavy use of content collections. The build time went from about 40 seconds to nearly 6 minutes once I hit around 400 pages. The culprit was how Astro resolves sibling components. Every page was importing components that imported other components, and the dependency graph was denser than it needed to be. The workaround was splitting my layout system into two separate component hierarchies—one for the shell and one for the content—and using Astro's Slot mechanism more aggressively instead of nesting deep component trees. Build time dropped back down to under a minute. It's not a bug, it's just how the compiler works, but it's not obvious from the docs.
Common Pitfalls and How to Avoid Them
Styling in Astro is surprisingly flexible but also surprisingly confusing at first. You can use CSS modules, Tailwind, global stylesheets, or even inline styles. The problem is that multiple stylesheets can conflict if you don't understand scoping. Astro gives you CSS modules by default when you name a file something like Page.module.css. Those styles are automatically scoped to that component. But if you import a global stylesheet in your Layout component and also have scoped styles in your page component, they can override each other in unexpected ways. I learned this the hard way when my button styles randomly broke after adding a new page component. The fix was being explicit about what's global and what's scoped, and keeping a strict convention across the project. Image optimization is another area where Astro makes decent choices by default but can confuse you. The built-in <Image> component handles resizing and format conversion automatically. It uses your configured adapter's image service—Sharp on Node, Cloudflare Images on Cloudflare, etc. But if you reference an image outside of that component, you're on your own. A regular <img> tag won't get any optimization. This is easy to miss when you're converting an existing project and not all your images go through the optimized component. The dev server is fast but not free. Hot module replacement works well for CSS and static assets. For component changes in .astro files, it reloads the whole page. That's fine for most cases but annoying if you're debugging layout issues and constantly losing scroll position. There's no hot reload for changes to your content collection schema either—you have to restart the dev server entirely. Again, not a dealbreaker, but something to be aware of when you're iterating fast.

If you're starting a new project and your main goal is SEO with some light interactivity, Astro is a solid pick. If you need heavy client-side state management or real-time features, you're better off elsewhere. The ecosystem is mature enough for production use now. The docs are good. The community is smaller than React or Vue but active and generally helpful on Discord. My recommendation is to start small, get a blog or landing page working end to end, and then expand from there. Don't try to architect the perfect system on day one.