">
 

22 Astro Best Practices: The Bookmark-Worthy Tips

Iniciado por joomlamz, 30 de Maio de 2026, 12:00

Respostas: 0   |   Visualizações: 15

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

22 Astro Best Practices: The Bookmark-Worthy Tips



Tópico: 22 Astro Best Practices: The Bookmark-Worthy Tips
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------


22 Astro Best Practices: The Bookmark-Worthy Tips


At QuotyAI I'm using Astro to build landing pages and blog posts, so I have hands-on experience how to use it properly and how to vibe-code without headache.

Astro is the best framework for content sites right now - #1 in developer satisfaction in the State of JS 2025 survey, with Cloudflare backing it since January 2026. But like any tool, it rewards people who use it the way it was designed.

This is the reference I wish I had when I started. Whether you're building your first Astro project or vibe-coding a blog at 2am, these are the habits worth forming from day one.

Heads up on versions: This article covers Astro 6.x (released March 2026) and Astro 6.4 (released May 2026). Some APIs from older tutorials are now deprecated - those are called out explicitly below. Always check the upgrade guide when moving between majors.



🖼️ Assets & Media




1. Use <Image /> instead of <img />


Astro's built-in <Image /> component does a lot of work at build time that plain <img> tags leave on the table: it converts images to WebP, generates the right width and height attributes to prevent layout shift, and compresses everything without you touching a single config file.

---
import { Image } from 'astro:assets';
import hero from '../assets/hero.png';
---

<!-- ✅ Optimized: converted to WebP, compressed, no layout shift -->
<Image src={hero} alt="Hero image" />

<!-- ❌ Skips all of that -->
<img src="/hero.png" alt="Hero image" />

For art-direction scenarios (different images at different breakpoints), reach for <Picture /> instead.



2. Use the Astro 6 Built-in Fonts API


Almost every website uses custom fonts, but getting them right is surprisingly complicated - performance tradeoffs, privacy concerns, self-hosting, fallback generation, and preload hints. Astro 6 added a built-in Fonts API that handles all of it for you.

Configure your fonts in astro.config.mjs:

// astro.config.mjs
import { defineConfig, fontProviders } from 'astro/config';

export default defineConfig({
fonts: [
{
name: 'Inter',
cssVariable: '--font-inter',
provider: fontProviders.fontsource(), // or fontProviders.google()
},
],
});

Then drop a <Font /> component in your base layout:

---
// src/layouts/Layout.astro
import { Font } from 'astro:assets';
---

<head>
<Font cssVariable="--font-inter" preload />
<style is:global>
body { font-family: var(--font-inter); }
</style>
</head>

Behind the scenes, Astro downloads and caches the font for self-hosting, generates optimized fallbacks, adds font-display: swap, and inserts the right <link rel="preload"> hints. Zero manual configuration.

Why not Google Fonts CDN? It costs you a third-party DNS lookup, a network round trip, and hands font delivery to Google. The Fonts API self-hosts everything from your own CDN automatically.



🎨 Styling




3. Use Tailwind v4 via the Vite plugin


The old @astrojs/tailwind integration is deprecated for Tailwind v4. Use @tailwindcss/vite instead - it runs Tailwind inside Vite's pipeline, which means faster HMR, smaller production CSS, and no separate PostCSS pass.

npm install tailwindcss @tailwindcss/vite

// astro.config.mjs
import { defineConfig, fontProviders } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});

⚠️ Deprecation: @astrojs/tailwind is the old v3 integration. Don't use it for new projects.



4. Your config lives in CSS now (Tailwind v4)


In v4, tailwind.config.js is gone. Design tokens go directly in your CSS with @theme {}:

/* src/styles/global.css */
@import "tailwindcss";

@theme {
--color-brand: oklch(0.75 0.18 175);
--font-family-sans: var(--font-inter); /* wire up your Fonts API variable */
}

Import it once in your base layout and Vite handles the rest.



⚡ Interactivity (Islands)




5. Use plain .astro components by default - not React


This is the most important mindset shift when coming from Next.js: you don't need a JS framework for most of your UI.

.astro components are server-rendered, ship zero JavaScript, and support props, slots, and scoped styles. They cover headers, navbars, cards, footers, and anything that doesn't need client-side state. Reach for React, Vue, or Svelte only when you genuinely need interactivity.

---
// src/components/Card.astro - zero JS shipped, fully capable
const { title, description } = Astro.props;
---

<article class="card">
<h2>{title}</h2>
<p>{description}</p>
</article>

If you're coming from Next.js: Astro is not a React framework with SSG bolted on. It's an HTML-first framework that lets you optionally add React for interactive components. That distinction matters a lot for performance.



6. Pick the right client:* directive


When you do need a JavaScript island, be intentional about when it hydrates:

Directive
When it hydrates
Best for

client:load
Immediately on load
Above-fold interactive UI

client:idle
When browser is idle
Non-critical widgets

client:visible
When scrolled into view
Below-fold components

client:only="react"
Client only, no SSR
Browser-API-dependent components

The most common mistake is reaching for client:load everywhere. If a component is below the fold, client:visible means its JavaScript won't even be requested until the user scrolls to it.

<!-- ❌ Loads and hydrates immediately, even if never seen -->
<HeavyChart client:load />

<!-- ✅ Only hydrates when scrolled into view -->
<HeavyChart client:visible />



7. Islands load in parallel - use that


Unlike traditional SPAs where a heavy component blocks the page, Astro's islands hydrate independently. A heavy chart at the bottom won't block a lightweight nav at the top.

Structure intentionally: high-priority interactive components near the top with client:load, everything else lower with client:visible or client:idle.



🚀 Navigation & Perceived Performance




8. Enable built-in prefetching


One config line makes all internal links prefetchable on hover - navigation feels instant because the page is already in memory before the user clicks.

// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true,
defaultStrategy: 'hover', // also: 'tap', 'viewport'
},
});

For specific links, you can opt in without prefetchAll:

<a href="/blog/my-post" data-astro-prefetch="viewport">Read more</a>

⚠️ Deprecation: @astrojs/prefetch (the old integration package) was deprecated in Astro 3.5. Use the built-in prefetch config option above.



9. Add View Transitions for SPA-feel without the SPA cost


One import in your base layout gives you smooth, animated page transitions without shipping a full client-side router:

---
// src/layouts/Layout.astro
import { ClientRouter } from 'astro:transitions';
---

<head>
<ClientRouter />
</head>

Elements with matching transition:name attributes morph between pages. It's one of Astro's most underrated features.



📝 Content & Developer Experience




10. Use Content Collections for all your Markdown


Content Collections give you type-safe frontmatter with schema validation. No more post.data.title returning undefined at runtime.

// src/content/config.ts
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod'; // ← correct import in Astro 6

const blog = defineCollection({
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});

export const collections = { blog };

⚠️ Deprecation: Older tutorials use import { z } from 'astro:content'. In Astro 6, Zod 4 is bundled separately - import from 'astro/zod' instead.

Astro 6's Content Layer API also supports live collections that fetch at request time (no rebuild needed for CMS content changes), using defineLiveCollection() in src/live.config.ts.



11. Set up TypeScript path aliases


Stop writing ../../../components/Card.astro. Configure aliases once in tsconfig.json:

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@lib/*": ["src/lib/*"]
}
}
}

Every import becomes clean:

import Card from '@components/Card.astro';
import { formatDate } from '@lib/utils';



12. Use MDX when your content needs components


Plain Markdown is great for text. MDX is great for text plus interactive demos, custom callouts, and embedded components.

npx astro add mdx

---
title: My Post
---

import CodeSandbox from '@components/CodeSandbox.astro';

Here's a live example:

<CodeSandbox src="https://..." />

And then the article continues in plain Markdown...



13. Use the <Code /> component for dynamic code blocks


Astro ships a built-in <Code /> component powered by Shiki - the same highlighter used for Markdown code fences, but available as a component in .astro and .mdx files. This is the right tool whenever you need to render code that's dynamic at build time: from a file, a CMS, a variable, or a prop.

---
import { Code } from 'astro:components';

const snippet = await Astro.glob('./examples/*.ts');
---

<!-- Syntax highlight any string of code -->
<Code code={`const foo = 'bar';`} lang="js" />

<!-- Dynamic code from a file or CMS -->
<Code code={snippet[0].default} lang="ts" theme="github-dark" />

<!-- Inline code rendering -->
<p>Use <Code code="npm run dev" lang="bash" inline /> to start.</p>

No extra packages, no configuration. It supports all Shiki themes, all languages, and even Shiki transformers for things like diff highlighting and line focus effects.

You can also set your global Markdown code block theme in astro.config.mjs:

export default defineConfig({
markdown: {
shikiConfig: {
themes: {
light: 'github-light',
dark: 'github-dark',
},
},
},
});

Note: <Code /> does not inherit shikiConfig from your Markdown settings - pass theme directly as a prop when you need a specific look.



14. Use the modern Markdown processor (Astro 6.4)


Astro 6.4 introduced a new pluggable markdown.processor API and a Rust-based processor called Sätteri that's dramatically faster than the default unified pipeline.

If you don't use remark/rehype plugins, switch to Sätteri:

npm install @astrojs/markdown-satteri

// astro.config.mjs
import { satteri } from '@astrojs/markdown-satteri';

export default defineConfig({
markdown: {
processor: satteri(),
},
});

If you do use remark/rehype plugins, migrate to the new unified processor API:

// astro.config.mjs
import { unified } from '@astrojs/markdown-remark';
import remarkToc from 'remark-toc';

export default defineConfig({
markdown: {
processor: unified({
remarkPlugins: [remarkToc],
}),
},
});

⚠️ Deprecation: Top-level markdown.remarkPlugins, markdown.rehypePlugins, markdown.gfm, and markdown.smartypants are deprecated in Astro 6.4 and will be removed in Astro 8. Move them into unified({...}).



15. Use Astro.logger for structured troubleshooting


console.log works, but it disappears into a wall of build output with no context. Astro 6.2 introduced an experimental structured logger you can use directly in your pages and components via Astro.logger.

Enable it in astro.config.mjs:

// astro.config.mjs
import { defineConfig, logHandlers } from 'astro/config';

export default defineConfig({
experimental: {
logger: logHandlers.console(), // or .json({ pretty: true }) for structured output
},
});

Then use it anywhere in your Astro frontmatter:

---
const posts = await getCollection('blog');

Astro.logger.info(`Rendering blog index with ${posts.length} posts`);

if (posts.length === 0) {
Astro.logger.warn('No posts found - check your content directory');
}
---

Three levels: info, warn, error. Errors go to stderr, the rest to stdout. For CI pipelines and log aggregators, use logHandlers.json() to get structured output that's easy to parse:

experimental: {
logger: logHandlers.json({ pretty: true, level: 'warn' }) // only warn+error
}

You can also pass --experimentalJson to astro build on the CLI without touching your config.



🌐 i18n - Set It Up from Day One




16. Add i18n routing before you have routes to regret


Retrofitting internationalization onto an existing site means restructuring your entire src/pages/ directory and updating every internal link. Do it at the start, even if you only support one language today.

// astro.config.mjs
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: ['en', 'vi', 'ja'], // add more later
routing: {
prefixDefaultLocale: false, // /blog instead of /en/blog
},
},
});

Use getRelativeLocaleUrl() for all internal links so they stay locale-aware:

---
import { getRelativeLocaleUrl } from 'astro:i18n';
const { currentLocale } = Astro;
---

<a href={getRelativeLocaleUrl(currentLocale, '/blog')}>Blog</a>

Organize content by locale in your collections:

src/content/blog/
en/post-1.md
vi/post-1.md

Even if you're launching in one language, the folder structure and i18n config cost you nothing now and save a painful migration later.



🔍 SEO & Discoverability




17. Add @astrojs/sitemap


One integration, automatic sitemap generation from all your routes:

npx astro add sitemap

// astro.config.mjs
import sitemap from '@astrojs/sitemap';

export default defineConfig({
site: 'https://yourdomain.com', // required
integrations: [sitemap()],
});

Astro generates /sitemap-index.xml at build time. Submit it to Google Search Console and you're done.



18. Always set site: in your config


This single field unlocks Astro.site throughout your project, makes canonical URLs work correctly, and is required for the sitemap integration.

export default defineConfig({
site: 'https://yourdomain.com',
});



19. Commit to a trailing slash strategy


Google doesn't care if you use /blog/ or /blog, but it does care if you mix both. Pick one:

export default defineConfig({
trailingSlash: 'always', // or 'never'
});

Inconsistency creates duplicate-content issues that quietly hurt your SEO.



20. Add an RSS feed


Two files and your content is subscribable - useful for readers, aggregators, and Dev.to's feed import feature.

npm install @astrojs/rss

// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'My Blog',
description: 'My thoughts on dev stuff',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}



🌍 Deployment




21. Deploy to edge CDN platforms


Astro's output is static HTML by default. That means it belongs on a CDN with global edge delivery - not a traditional server. Cloudflare Pages, Netlify, and Vercel all support Astro with zero config.

# Cloudflare Pages (first-class support since Cloudflare acquired Astro)
npx astro add cloudflare

This is where all the build-time work pays off. Your "server" is just files on a CDN, served from the closest data center to each visitor.



22. Be explicit about output: 'static'


It's the default, but stating it communicates intent:

export default defineConfig({
output: 'static', // pre-render everything at build time
});

If a teammate adds a server route by accident, it'll be immediately obvious something doesn't fit the architecture.



TL;DR


Category
Habit

Images
Use <Image />

Fonts
Built-in Fonts API (Astro 6) - handles self-hosting, fallbacks, and preload

Styling
Tailwind v4 via @tailwindcss/vite, @theme {} in CSS

Interactivity
Default to .astro, not React; match client:* to component priority

Performance
Enable prefetch, add View Transitions

Content
Content Collections + import { z } from 'astro/zod' + MDX + <Code /> + Sätteri

Logging

Astro.logger for structured troubleshooting (Astro 6.2+)

i18n
Set it up on day one, not day 100

SEO
Sitemap, site:, trailing slash consistency, RSS

Deployment
Edge CDN, explicit output: 'static'

Astro rewards developers who lean into its defaults. Ship static HTML, hydrate surgically, optimize at build time - and you'll have a fast site almost by accident.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: