// @ts-check /** * Astro configuration for Greg Kilmer's profile site. * * Stack: * - @astrojs/node (standalone) -> Node server with API routes for the Cosmos homelab. * - @astrojs/react -> React 19 islands for interactive components (mobile menu, * theme toggle, animated hero background, contact form). * - @astrojs/mdx -> MDX content collections (projects, experience). * - @tailwindcss/vite -> Tailwind v4 via the Vite plugin (CSS-first config in styles). * - astro-icon + mdi set -> render Material Design Icons (no emojis, per spec). * * The `standalone` output produces `dist/server/entry.mjs`, launched by `npm run serve` * or by the Docker runtime. API routes under src/pages/api/* are served by the same server. */ import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import react from '@astrojs/react'; import mdx from '@astrojs/mdx'; import tailwindcss from '@tailwindcss/vite'; // `SITE_URL` lets us produce absolute URLs in metadata / API responses. Defaults to the // production domain from the spec; override locally or in CI as needed. const SITE_URL = process.env.SITE_URL ?? 'https://gregkilmer.com'; // Astro exposes the configured server host/port to the Node adapter via env at runtime. // We read them here only for documentation; the adapter picks them up automatically. const PORT = Number(process.env.PORT ?? 4321); const HOST = process.env.HOST ?? '0.0.0.0'; /** * React-refresh preamble, injected into the layout in dev (see * BaseLayout.astro). Returned as a string so the layout can emit it as an * inline module script that runs BEFORE any island module evaluates. * * Why this is needed: Vite 8 (Rolldown) + @vitejs/plugin-react 5.x does not * inject this preamble through Astro's SSR dev HTML pipeline, so the compiled * islands call $RefreshSig$() at module-eval time, throw, and never hydrate — * surfacing as "_jsxDEV is not a function" and leaving every React island * (carousel, mobile menu, contact/order forms, animated background) dead. * * Production builds are unaffected: the client bundle uses jsx/jsxs directly * and never touches $RefreshSig$. Remove this once @astrojs/react ships * plugin-react 6, which handles Rolldown natively. */ export const REACT_REFRESH_PREAMBLE = "import { injectIntoGlobalHook } from '/@react-refresh';\n" + 'injectIntoGlobalHook(window);\n' + 'window.$RefreshReg$ = () => {};\n' + 'window.$RefreshSig$ = () => (type) => type;'; export default defineConfig({ // Used for canonical URLs, sitemaps, and absolute metadata. Keep this in sync with the // deployed domain on your Cosmos reverse proxy. site: SITE_URL, // Server output mode. `standalone` emits a self-contained Node server we can run in a // container. This is what enables /api/* routes to execute server-side. output: 'server', adapter: node({ mode: 'standalone', }), // Page-to-page transitions using the native View Transitions API. Persists the nav and // animates content swaps. Honored automatically by Astro's . // (Icons are rendered inline at build time via src/components/Icon.astro, reading // directly from @iconify-json/mdi; no astro-icon integration needed.) integrations: [ react(), mdx(), ], vite: { plugins: [tailwindcss()], build: { // Never inline assets as data URIs. The default (4096 bytes) causes Vite // to inline the smallest font subsets (e.g. cyrillic-ext woff2) as // data:font/woff2 base64, which (a) requires data: in font-src and (b) // triggers Chromium "OTS parsing error: Size of decompressed WOFF 2.0 is // less than compressed size" warnings on those inlined subsets. Forcing // every asset to a real file keeps font-src tight at 'self' and avoids // the OTS parse errors. assetsInlineLimit: 0, }, }, server: { host: HOST, port: PORT, }, // Markdown rendering options shared by content collections and MDX. markdown: { shikiConfig: { theme: 'github-dark-dimmed', wrap: true, }, }, });