ui-stack-astro
Building with Astro: when it fits, zero JavaScript by default, islands with client directives, content collections with schemas, layouts and components, images with astro:assets, view transitions, integrations for React, Vue or Svelte, server output and adapters, and build checks. Read when the project uses Astro or is a content site choosing a stack.
- License 许可
- Apache-2.0
- 所属仓库
- enowdev/enowxcli
- 最近更新
- 2026-10-06
- Stars
- ★ 67
- Forks
- 21
01 前言 Front Matter
- name
- ui-stack-astro
- description
- Building with Astro: when it fits, zero JavaScript by default, islands with client directives, content collections with schemas, layouts and components, images with astro:assets, view transitions, integrations for React, Vue or Svelte, server output and adapters, and build checks. Read when the project uses Astro or is a content site choosing a stack.
03 SKILL.md 正文
Astro
Astro used by habit becomes a single-page app in a wrapper: client:load on
every component, content typed into arrays, full-size images in public/,
a menu script that dies after the first client-side navigation. Its point
is the reverse: HTML from the server, JavaScript only where a part must
behave. This covers Astro 6 and 7 (current in 2026, Node 22.12 or later);
check package.json, as Astro 5 still had Astro.glob and
<ViewTransitions />.
1. When it fits
- Content read more than operated: docs, blogs, product sites, portfolios,
a local business, a catalogue. Not an app behind a sign-in with shared
state on every screen: Vite with React, SvelteKit or Next.js instead
(
ui-stack-react,ui-stack-svelte,ui-stack-next). npm create astro@lateststarts a project;npx astro add <name>installs an integration and edits the config.
src/pages/ routes: index.astro, blog/[...id].astro, rss.xml.ts, 404.astro
src/layouts/ Base.astro: <html lang>, head, header, footer, <slot />
src/components/ .astro components; islands in their framework (Search.tsx)
src/content/ Markdown, MDX, data; the schemas in src/content.config.ts
src/assets/ images Astro optimises (public/ is served untouched)
2. Components
---
// The frontmatter runs on the server (build or request time), never in the browser.
import type { HTMLAttributes } from "astro/types";
interface Props extends HTMLAttributes<"a"> { variant?: "primary" | "quiet" }
const { variant = "quiet", class: className, ...rest } = Astro.props;
---
<a class:list={["button", `button--${variant}`, className]} {...rest}><slot /></a>
- Named slots (
<slot name="aside" />) with fallback content;Astro.slots.has("aside")drops an empty wrapper.<style>is scoped to the component; Markdown rendered inside is not its markup, so reach it with:global()from a wrapper (.prose :global(h2)). - Expressions are escaped;
set:htmlis not, so it takes only trusted or sanitised HTML.is:inlinescripts skip bundling: only for tiny head scripts, such as setting the theme before the first paint. - Astro 7's compiler errors on unclosed tags and no longer repairs invalid
nesting, and its default
compressHTML: "jsx"drops whitespace between inline elements as JSX does: write{" "}where a space must stay.
3. Islands: JavaScript only where it behaves
A framework component with no directive renders to HTML and ships nothing;
a client: directive makes it an island that hydrates.
| Directive | Hydrates | For |
|---|---|---|
| none | never | anything that only displays |
client:visible |
when scrolled into view | below the fold: a chart, a carousel, comments |
client:idle |
when the browser is idle | low-priority parts already in view |
client:media="(max-width: 767px)" |
when the query matches | a phone-only menu |
client:load |
at once | what the first screen needs now: search, the menu button |
client:only="react" |
in the browser, no server HTML | parts that need window (a map), with a fallback |
- The laziest that works (
client:visible={{ rootMargin: "200px" }}starts early);client:loadeverywhere is a single-page app with extra steps. Props are serialised (plain data, no functions); children arrive as HTML. Islands share no context: use Nano Stores (nanostoreswith@nanostores/reactor@nanostores/vue). - Small behaviour needs no framework. A
<script>in an.astrofile is bundled, typed and run once as a module; as a custom element it works for every instance and after client-side navigation:
<copy-code data-code={code}><button type="button">Copy</button></copy-code>
<script>
customElements.define("copy-code", class extends HTMLElement {
connectedCallback() {
const button = this.querySelector("button")!;
button.onclick = async () => {
await navigator.clipboard.writeText(this.dataset.code ?? "");
button.textContent = "Copied";
};
}
});
</script>
4. Content collections
// src/content.config.ts
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";
const blog = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
schema: ({ image }) => z.object({
title: z.string().max(70), description: z.string().max(160),
published: z.coerce.date(), cover: image().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
---
// src/pages/blog/[...id].astro
import { getCollection, render } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
return posts.map((post) => ({ params: { id: post.id }, props: { post } }));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<article class="prose"><h1>{post.data.title}</h1><Content /></article>
- A file that breaks the schema fails the build, naming file and field;
types come with it (
CollectionEntry<"blog">).render()also returnsheadings;reference("authors")links collections;file()loads one JSON or YAML file; live collections (Astro 6 and later) load per request. zis Zod 4 fromastro/zod(z.email(),z.url());Astro.globis gone (a collection, orimport.meta.glob). MDX vianpx astro add mdx.- Shiki highlights Markdown code (
markdown.shikiConfig.themes, light and dark,ui-themes). Astro 7 renders Markdown with Sätteri: remark and rehype plugins needmarkdown.processor: unified({ remarkPlugins, rehypePlugins }),unifiedimported from@astrojs/markdown-remark.
5. Routing and rendering
- File-based (
src/pages/about.astrois/about),404.astro, endpoints such asrss.xml.tsexportingGET. Static by default; a page run per request (search, a signed-in area, a form) needs an adapter (npx astro add node,vercel,netlify,cloudflare) andexport const prerender = false;output: "server"only when most pages are dynamic. <Recommendations server:defer>with aslot="fallback"placeholder (a server island) puts one per-person part on an otherwise cached page.src/middleware.ts(defineMiddleware) for sessions and redirects viaAstro.locals; Actions (astro:actions) for form posts, validated on the server.astro:envtypes the environment;PUBLIC_variables ship to every browser, so secrets never carry that prefix.
6. Images and fonts
---
import { Picture } from "astro:assets";
import shopFront from "../assets/shop-front.jpg";
---
<Picture src={shopFront} formats={["avif", "webp"]} alt="The shop front on [street]"
widths={[480, 800, 1200]} sizes="(min-width: 1024px) 50vw, 100vw" priority />
- Images imported from
src/are optimised and sized from the file;public/is copied untouched; remote ones needimage.remotePatterns;altis required (empty for decoration). Both are lazy by default:priority(eager, high fetch priority) only on the first screen's largest image;layout="constrained"writessrcsetandsizes. - The Fonts API self-hosts at build:
fonts: [{ provider: fontProviders.fontsource(), name: "Fraunces", cssVariable: "--font-display" }]in the config,<Font cssVariable="--font-display" preload />(fromastro:assets) in the head, preload above the fold only.
7. Styles, themes, view transitions
- Tokens as custom properties in one file the base layout imports;
Tailwind v4 via
npx astro add tailwind(tokens in@theme,ui-stack-tailwind); anis:inlinehead script sets the theme before the first paint (ui-themes). - Between pages, the browser's own transitions and no script:
@view-transition { navigation: auto; }inside@media (prefers-reduced-motion: no-preference)(Chromium, Safari 18.2 and later; others just load) (motion-interface). <ClientRouter />(astro:transitions) when state must survive navigation (transition:persist,transition:name); it turns its animations off under reduced motion. Its trap: bundled scripts run once, so page setup goes indocument.addEventListener("astro:page-load", setup)or in custom elements.
8. Head, docs, other frameworks
- A head component:
<title>, description, canonical (new URL(Astro.url.pathname, Astro.site)), Open Graph;sitein the config (canonical,@astrojs/sitemapand@astrojs/rssneed it);langon<html>;i18nrouting (frontend-seo,i18n);prefetch: true;security: { csp: true }for a Content Security Policy. - Docs:
npm create astro@latest -- --template starlight(sidebar, Pagefind search, both themes, code blocks with copy,:::noteasides,<Tabs syncKey="pkg">, edit links;ui-page-docs), themed through--sl-color-accent,--sl-fontand component overrides. npx astro add react(orvue,svelte): one framework, following its skill (ui-stack-react,ui-stack-vue,ui-stack-svelte).
Check it
npx astro check(it offers to install@astrojs/checkandtypescript): types in.astrofiles, props, collections.npx astro buildfails on a schema mismatch, a broken import, a missingaltor an unclosed tag; indist/, display-only pages load no script.- The
previewtool:urlhttp://localhost:4321/,startnpm run dev -- --port 4321. With<ClientRouter />, navigate three pages and use each interactive part again. Thenui_check.
Avoid
client:load on every component; a whole React app in one island for a
content page; client:only for content that belongs in the HTML; content
in hand-typed arrays; Astro.glob; content images in public/;
set:html with anything unsanitised; secrets under PUBLIC_; output: "server" for a site that could be static; scripts that die after a
client-side navigation; a font stylesheet from a CDN blocking first paint.
04 同仓库更多 Skills
demo-skill
Project-scope demo skill for tests.
writing
Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.
ui
Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.
ui-themes
Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.
ui-structure
Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.
ui-stack-vue
Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.