Renderizado en el servidor por defecto
Como PHP, el código de Astro se ejecuta en el servidor. A diferencia de PHP, genera HTML estático por defecto sin JavaScript.
Astro es un framework web para construir sitios web centrados en contenido. Al usar EmDash, Astro reemplaza tu tema de WordPress: maneja la plantilla, el enrutamiento y la renderización.
Esta guía enseña los fundamentos de Astro mapeándolos a conceptos de WordPress que ya comprendes.
Renderizado en el servidor por defecto
Como PHP, el código de Astro se ejecuta en el servidor. A diferencia de PHP, genera HTML estático por defecto sin JavaScript.
Cero JS a menos que lo añadas
WordPress carga jQuery y scripts del tema automáticamente. Astro no envía nada al navegador a menos que lo añadas explícitamente.
Arquitectura basada en componentes
En lugar de etiquetas de plantilla e includes dispersos, construye con componentes componibles y autocontenidos.
Enrutamiento basado en archivos
Sin reglas de reescritura o query_vars. La estructura de archivos en src/pages/ define tus URLs directamente.
Los temas de WordPress tienen una estructura plana con nombres de archivo mágicos. Astro usa directorios explícitos:
| WordPress | Astro | Propósito |
|---|---|---|
index.php, single.php | src/pages/ | Rutas (URLs) |
template-parts/ | src/components/ | Piezas de UI reutilizables |
header.php + footer.php | src/layouts/ | Envoltorios de página |
style.css | src/styles/ | CSS global |
functions.php | astro.config.mjs | Configuración del sitio |
Un proyecto típico de Astro podría verse así:
src/├── components/ # Reusable UI (Header, PostCard, etc.)├── layouts/ # Page shells (Base.astro)├── pages/ # Routes - files become URLs│ ├── index.astro # → /│ ├── posts/│ │ ├── index.astro # → /posts│ │ └── [slug].astro # → /posts/hello-world│ └── [slug].astro # → /about, /contact, etc.└── styles/ └── global.cssLos archivos .astro son el equivalente de Astro a las plantillas PHP. Cada archivo tiene dos partes:
---) — Código del lado del servidor, como PHP en la parte superior de una plantilla---// Frontmatter: runs on server, never sent to browserinterface Props { title: string; excerpt: string; url: string;}
const { title, excerpt, url } = Astro.props;---<!-- Template: outputs HTML --><article class="post-card"> <h2><a href={url}>{title}</a></h2> <p>{excerpt}</p></article>Diferencias clave con PHP:
interface Props para autocompletado del editor y validación.Las plantillas de Astro usan {curly braces} en lugar de etiquetas <?php ?>. La sintaxis es similar a JSX pero genera HTML puro.
---import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");const showTitle = true;---{showTitle && <h1>Entradas recientes</h1>}
{posts.length > 0 ? ( <ul> {posts.map(post => ( <li> <a href={`/posts/${post.id}`}>{post.data.title}</a> </li> ))} </ul>) : ( <p>No se encontraron entradas.</p>)}<?php$posts = new WP_Query(['post_type' => 'post']);$show_title = true;?>
<?php if ($show_title): ?> <h1>Entradas recientes</h1><?php endif; ?>
<?php if ($posts->have_posts()): ?> <ul> <?php while ($posts->have_posts()): $posts->the_post(); ?> <li> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a> </li> <?php endwhile; wp_reset_postdata(); ?> </ul><?php else: ?> <p>No se encontraron entradas.</p><?php endif; ?>| Patrón | Propósito |
|---|---|
{variable} | Mostrar un valor |
{condition && <Element />} | Renderizado condicional |
{condition ? <A /> : <B />} | If/else |
{items.map(item => <Li>{item}</Li>)} | Bucles |
Los componentes reciben datos a través de props (como argumentos de función) y slots (como puntos de inserción de do_action).
---interface Props { title: string; featured?: boolean;}
const { title, featured = false } = Astro.props;---
<article class:list={["card", { featured }]}> <h2>{title}</h2> <slot /> <slot name="footer" /></article>Uso:
<Card title="Hello" featured> <p>Este contenido va al slot principal.</p> <footer slot="footer">Contenido del pie</footer></Card><?php// Usage: get_template_part('template-parts/card', null, [// 'title' => 'Hello',// 'featured' => true// ]);
$title = $args['title'] ?? '';$featured = $args['featured'] ?? false;$class = $featured ? 'card featured' : 'card';?><article class="<?php echo esc_attr($class); ?>"> <h2><?php echo esc_html($title); ?></h2> <?php // No hay equivalente directo a slots. // WordPress usa do_action() para patrones similares: do_action('card_content'); do_action('card_footer'); ?></article>$argsEn WordPress, get_template_part() pasa datos a través del array $args. Las props de Astro están tipadas y se desestructuran:
---// Type-safe with defaultsinterface Props { title: string; count?: number;}const { title, count = 10 } = Astro.props;---WordPress usa do_action() para crear puntos de inserción. Astro usa slots:
| WordPress | Astro |
|---|---|
do_action('before_content') | <slot name="before" /> |
| Área de contenido por defecto | <slot /> |
do_action('after_content') | <slot name="after" /> |
La diferencia: los slots reciben elementos hijos en el sitio de llamada, mientras que los hooks de WordPress requieren llamadas add_action() separadas en otro lugar.
Los layouts envuelven páginas con una estructura HTML común: el <head>, el encabezado, el pie de página y cualquier cosa compartida entre páginas. Esto reemplaza header.php + footer.php.
---import "../styles/global.css";
interface Props { title: string; description?: string;}
const { title, description = "Mi sitio con EmDash" } = Astro.props;---<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="description" content={description} /> <title>{title}</title> </head> <body> <header> <nav><!-- Navigation --></nav> </header>
<main> <slot /> </main>
<footer> <p>© {new Date().getFullYear()}</p> </footer> </body></html>Usa el layout en una página:
---import Base from "../../layouts/Base.astro";---<Base title="Inicio"> <h1>Bienvenido</h1> <p>El contenido de la página se inserta en el slot.</p></Base>Astro ofrece varios enfoques de estilización. El más distintivo son los estilos con alcance.
Los estilos en una etiqueta <style> se limitan automáticamente a ese componente:
<article class="card"> <h2>Título</h2></article>
<style> /* Solo afecta a .card en ESTE componente */ .card { padding: 1rem; border: 1px solid #ddd; }
h2 { color: navy; }</style>El HTML generado incluye nombres de clase únicos para evitar fugas de estilos. Se acabaron las guerras de especificidad.
Para estilos de todo el sitio, crea un archivo CSS e impórtalo en un layout:
---import "../styles/global.css";---La directiva class:list reemplaza la construcción manual de cadenas de clases:
---const { featured, size = "medium" } = Astro.props;---<article class:list={[ "card", size, { featured, "has-border": true }]}>Salida: <article class="card medium featured has-border">
<?php$classes = ['card', $size];if ($featured) $classes[] = 'featured';if (true) $classes[] = 'has-border';?><article class="<?php echo esc_attr(implode(' ', $classes)); ?>">Astro no envía JavaScript por defecto. Este es el mayor cambio mental respecto a WordPress.
Para interacciones simples, añade una etiqueta <script>:
<button id="menu-toggle">Menú</button><nav id="mobile-menu" hidden> <slot /></nav>
<script> const toggle = document.getElementById("menu-toggle"); const menu = document.getElementById("mobile-menu");
toggle?.addEventListener("click", () => { menu?.toggleAttribute("hidden"); });</script>Los scripts se agrupan y deduplican automáticamente. Si este componente aparece dos veces en una página, el script se ejecuta una sola vez.
Para interactividad más compleja, Astro puede cargar componentes de JavaScript (React, Vue, Svelte) bajo demanda. Esto es opcional: la mayoría de los sitios funcionan bien solo con etiquetas <script>.
---import SearchWidget from "../../components/SearchWidget.jsx";---<!-- Only load JavaScript when the search box scrolls into view --><SearchWidget client:visible />| Directiva | Cuándo se carga JavaScript |
|---|---|
client:load | Inmediatamente al cargar la página |
client:visible | Cuando el componente entra en el viewport |
client:idle | Cuando el navegador está inactivo |
Astro utiliza enrutamiento basado en archivos. Los archivos en src/pages/ se convierten en URLs:
| Archivo | URL |
|---|---|
src/pages/index.astro | / |
src/pages/about.astro | /about |
src/pages/posts/index.astro | /posts |
src/pages/posts/[slug].astro | /posts/hello-world |
src/pages/[...slug].astro | Cualquier ruta (catch-all) |
Para contenido de un CMS, usa la sintaxis de corchetes para segmentos dinámicos:
---import { getEmDashCollection, getEmDashEntry } from "emdash";import Base from "../../../layouts/Base.astro";import { PortableText } from "emdash/ui";
// For static builds, define which pages to generateexport async function getStaticPaths() { const { entries: posts } = await getEmDashCollection("posts"); return posts.map(post => ({ params: { slug: post.id }, props: { post }, }));}
const { post } = Astro.props;---<Base title={post.data.title}> <article> <h1>{post.data.title}</h1> <PortableText value={post.data.content} /> </article></Base>| WordPress | Astro |
|---|---|
Jerarquía de plantillas (single-post.php) | Archivo explícito: posts/[slug].astro |
Reglas de reescritura + query_vars | Estructura de archivos |
$wp_query determina la plantilla | La URL se mapea directamente al archivo |
add_rewrite_rule() | Crear archivos o carpetas |
Una referencia para encontrar el equivalente en Astro/EmDash de las características de WordPress:
| WordPress | Astro/EmDash |
|---|---|
| Jerarquía de plantillas | Enrutamiento basado en archivos en src/pages/ |
get_template_part() | Importar y usar componentes |
the_content() | <PortableText value={content} /> |
the_title(), the_*() | Acceder vía post.data.title |
| Etiquetas de plantilla | Expresiones de plantilla {value} |
body_class() | Directiva class:list |
| WordPress | Astro/EmDash |
|---|---|
WP_Query | getEmDashCollection(type, filters) |
get_post() | getEmDashEntry(type, id) |
get_posts() | getEmDashCollection(type) |
get_the_terms() | Acceder vía entry.data.categories |
get_post_meta() | Acceder vía entry.data.fieldName |
get_option() | getSiteSettings() |
wp_nav_menu() | getMenu(location) |
| WordPress | Astro/EmDash |
|---|---|
add_action() | Hooks de EmDash, middleware de Astro |
add_filter() | Hooks de EmDash |
add_shortcode() | Bloques personalizados de Portable Text |
register_block_type() | Bloques personalizados de Portable Text |
register_sidebar() | Áreas de widgets de EmDash |
| Plugins | Integraciones de Astro + plugins de EmDash |
| WordPress | Astro/EmDash |
|---|---|
register_post_type() | Crear colección en la interfaz de administración |
register_taxonomy() | Crear taxonomía en la interfaz de administración |
register_meta() | Añadir campo al esquema de la colección |
| Estado de la entrada | Estado de la entrada (borrador, publicado, etc.) |
| Imagen destacada | Campo de referencia a medios |
| Bloques de Gutenberg | Bloques de Portable Text |
El salto de WordPress a Astro es significativo pero lógico:
Comienza con la guía Primeros pasos para construir tu primer sitio EmDash, o explora Trabajar con contenido para aprender cómo consultar y renderizar datos del CMS.