{"id":34832128,"url":"https://github.com/jahilldev/astro-auto-load","last_synced_at":"2026-05-25T09:31:54.411Z","repository":{"id":328242635,"uuid":"1114765389","full_name":"jahilldev/astro-auto-load","owner":"jahilldev","description":"Astro loader function pattern support. Co-locate data fetching with consumer components.","archived":false,"fork":false,"pushed_at":"2025-12-16T01:21:20.000Z","size":165,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-15T15:54:43.620Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jahilldev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-11T21:17:06.000Z","updated_at":"2025-12-15T00:13:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jahilldev/astro-auto-load","commit_stats":null,"previous_names":["jahilldev/astro-auto-load"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jahilldev/astro-auto-load","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jahilldev%2Fastro-auto-load","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jahilldev%2Fastro-auto-load/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jahilldev%2Fastro-auto-load/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jahilldev%2Fastro-auto-load/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jahilldev","download_url":"https://codeload.github.com/jahilldev/astro-auto-load/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jahilldev%2Fastro-auto-load/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33469405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T06:32:55.349Z","status":"ssl_error","status_checked_at":"2026-05-25T06:32:35.322Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-25T15:56:15.189Z","updated_at":"2026-05-25T09:31:54.400Z","avatar_url":"https://github.com/jahilldev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# astro-auto-load\n\nAutomatic component-level data loading for Astro SSR. Co-locate your data fetching logic with your components while **eliminating async waterfalls** through recursive loader extraction.\n\n## Key Features\n\n✨ **True Parallel Execution** - All loaders execute simultaneously, even for deeply nested components  \n🎯 **Recursive Extraction** - Discovers entire component tree at build-time (direct imports AND slot-based composition)  \n⚡ **Zero Waterfalls** - Achieves ~67% performance improvement by eliminating sequential async chains  \n🔒 **Type-Safe** - Full TypeScript support with automatic type inference  \n🎨 **Zero Config** - Drop-in integration, works automatically with no manual setup\n🧩 **Flexible Composition** - Supports both direct imports and slot-based patterns\n\n## The Problem\n\nIn typical Astro SSR apps, you face a choice:\n\n1. **Props drilling** - Pass data from page to deeply nested components (verbose and brittle, and couples component trees). Not fun for complex apps.\n2. **Fetch in components** - Nice DX, but Astro resolves promises sequentially (async waterfall), hurting render times and TTFB.\n\n## The Solution\n\n`astro-auto-load` uses **recursive loader extraction** to discover your entire component tree at build-time, extract all loader functions, and execute them in a single parallel batch.\n\n### Performance Impact\n\nThe performance benefit depends on your component structure:\n\n#### 🚀 **Sibling Components** (Major Win!)\n\n**Before** (Traditional Async):\n\n```\nPage renders: \u003cComponent1 /\u003e, \u003cComponent2 /\u003e, \u003cComponent3 /\u003e\nEach component: ~50ms data fetch (sequential)\nTotal: ~150ms waterfall\n```\n\n**After** (astro-auto-load):\n\n```\nPage renders: \u003cComponent1 /\u003e, \u003cComponent2 /\u003e, \u003cComponent3 /\u003e\nEach component: ~50ms data fetch (parallel!)\nTotal: ~50ms\n```\n\n**Result:** ~67% faster! All sibling components execute in parallel ⚡\n\n#### 🎯 **Nested Components** (Win with Recursive Extraction!)\n\n**Before** (Traditional Async):\n\n```\n\u003cParent\u003e → \u003cChild\u003e → \u003cGrandchild\u003e\nEach: ~50ms data fetch (sequential due to nesting)\nTotal: ~150ms waterfall\n```\n\n**After** (astro-auto-load):\n\n```\n\u003cParent\u003e → \u003cChild\u003e → \u003cGrandchild\u003e\nAll loaders extracted and executed in parallel!\nTotal: ~50ms\n```\n\n**Result:** ~67% faster! Recursive extraction eliminates waterfalls even for nested components ⚡\n\n#### ✅ **Real-World Benefit**\n\nThe plugin achieves **true parallel execution** for:\n\n- ✅ **Sibling components** - ~67% faster\n- ✅ **Nested components** (direct imports OR slot-based) - ~67% faster via recursive extraction\n- ✅ **Complex component trees** - all loaders execute simultaneously\n\nThis works through **recursive loader extraction**: the plugin discovers your entire component tree at build-time (including slot-based children), extracts all loader functions, and registers them upfront so they execute in a single parallel batch. Verified by [E2E tests](test/e2e.test.ts).\n\n## Installation\n\n```bash\nnpm install astro-auto-load\n```\n\n## Setup\n\n### 1. Add the integration\n\nIn `astro.config.mjs`:\n\n```js\nimport { defineConfig } from 'astro/config';\nimport autoLoad from 'astro-auto-load';\n\nexport default defineConfig({\n  output: 'server', // required\n  integrations: [autoLoad()],\n});\n```\n\n**That's it!** The middleware is automatically injected and loaders run in parallel.\n\n### 2. Add TypeScript support (recommended)\n\nCreate `src/env.d.ts` if it doesn't exist:\n\n```ts\n/// \u003creference types=\"astro/client\" /\u003e\n/// \u003creference types=\"astro-auto-load/augment\" /\u003e\n```\n\nThis ensures `Astro.locals.autoLoad` is properly typed.\n\n## Usage\n\n### Basic Example\n\nDefine a loader in your component:\n\n```astro\n---\n// src/components/Post.astro\nimport { getLoaderData } from 'astro-auto-load/runtime';\n\nexport const loader = async (context) =\u003e {\n  const res = await fetch(`https://api.example.com/posts/${context.params.id}`);\n  return res.json();\n};\n\nconst data = await getLoaderData();\n---\n\n\u003carticle\u003e\n  \u003ch2\u003e{data.title}\u003c/h2\u003e\n  \u003cp\u003e{data.body}\u003c/p\u003e\n\u003c/article\u003e\n```\n\nOr with TypeScript:\n\n```astro\n---\n// src/components/Post.astro\nimport { type Context, getLoaderData } from 'astro-auto-load/runtime';\n\nexport const loader = async (context: Context) =\u003e {\n  const res = await fetch(`https://api.example.com/posts/${context.params.id}`);\n  return res.json();\n};\n\n// Type inference works automatically! ✨\nconst data = await getLoaderData\u003ctypeof loader\u003e();\n---\n\n\u003carticle\u003e\n  \u003ch2\u003e{data.title}\u003c/h2\u003e\n  \u003cp\u003e{data.body}\u003c/p\u003e\n\u003c/article\u003e\n```\n\n### Alternative: Using `defineLoader` for Context Types\n\nIf you prefer implicit context typing, use `defineLoader`:\n\n```astro\n---\n// src/components/Post.astro\nimport { defineLoader, getLoaderData } from 'astro-auto-load/runtime';\n\nexport const loader = defineLoader(async (context) =\u003e {\n  // context is automatically typed as Context ✨\n  const res = await fetch(`https://api.example.com/posts/${context.params.id}`);\n  return res.json();\n});\n\n// Type inference works automatically! ✨\nconst data = await getLoaderData\u003ctypeof loader\u003e();\n---\n\n\u003carticle\u003e\n  \u003ch2\u003e{data.title}\u003c/h2\u003e\n  \u003cp\u003e{data.body}\u003c/p\u003e\n\u003c/article\u003e\n```\n\n### Using Route Parameters\n\nLoaders automatically receive route parameters through the `context` object:\n\n```astro\n---\n// src/pages/posts/[id].astro\nimport Post from '../../components/Post.astro';\n---\n\n\u003cPost /\u003e\n```\n\nWhen you visit `/posts/123`, the `Post` component's loader receives `context.params.id === \"123\"` automatically.\n\n### Deduplication\n\nIf multiple components request the same data, use the built-in dedupe helper:\n\n```astro\n---\nexport const loader = async (context) =\u003e {\n  // Dedupe by unique key - only executes once per unique key per request\n  return context.dedupe(\n    `story-${context.params.id}`,\n    async () =\u003e {\n      const res = await fetch(`https://api.example.com/stories/${context.params.id}`);\n      return res.json();\n    }\n  );\n};\n---\n```\n\n## How It Works\n\nThe integration uses **recursive loader extraction** to achieve true parallel execution:\n\n1. **Build-time (Vite Plugin)**:\n   - Recursively discovers your entire component tree (including slot-based children)\n   - Extracts all `loader` functions from discovered components\n   - Injects extracted loaders into parent frontmatter with unique registration keys\n   - Marks extracted children to skip duplicate registration\n\n2. **Runtime (Middleware)**:\n   - Sets up `AsyncLocalStorage` to track loaders during each request\n\n3. **Runtime (Component Execution)**:\n   - Parent component registers all extracted loaders (children + self) upfront\n   - Child components detect their loader was already extracted and skip registration\n   - First call to `getLoaderData()` triggers parallel execution of ALL registered loaders\n   - Results are cached in `Astro.locals.autoLoad` for the remainder of the request\n   - All components retrieve their data using `await getLoaderData()`\n\n**Benefits:**\n\n- ✅ **True parallel execution** - even nested component loaders execute simultaneously\n- ✅ **Works with slot-based composition** - recursive extraction discovers all children\n- ✅ **Zero waterfalls** - all loaders in the component tree execute in one batch\n- ✅ **Type-safe** - automatic type inference via `getLoaderData\u003ctypeof loader\u003e()`\n- ✅ **Automatic** - no manual configuration needed (auto-wrapper for pages without loaders)\n\n## API Reference\n\n### `Context`\n\nThe context object passed to every loader function:\n\n```ts\ninterface Context {\n  /** Route parameters (e.g., { id: \"123\" } for /posts/[id]) */\n  params: Record\u003cstring, string\u003e;\n\n  /** Full URL object */\n  url: URL;\n\n  /** Original Request object */\n  request: Request;\n\n  /** Dedupe helper to prevent duplicate async calls */\n  dedupe: \u003cT\u003e(key: string, fn: () =\u003e Promise\u003cT\u003e) =\u003e Promise\u003cT\u003e;\n}\n```\n\n### `getLoaderData\u003cT\u003e()`\n\nRetrieves the loaded data for the current component. Must be called with `await` as loaders execute asynchronously.\n\n```ts\nconst data = await getLoaderData\u003cData\u003e();\n```\n\n### `autoLoadMiddleware`\n\nThe middleware handler that sets up the loader execution context. Automatically injected unless you have a custom `src/middleware.ts` file.\n\n## Advanced Usage\n\n### Custom Middleware Composition\n\n**Important:** The integration automatically injects middleware **only if you don't have a `src/middleware.ts` file**.\n\n#### If you have existing middleware:\n\nIf you already have a `src/middleware.ts` file with `export const onRequest`, you **must** manually include `autoLoadMiddleware`:\n\n```ts\n// src/middleware.ts\nimport { defineMiddleware, sequence } from 'astro:middleware';\nimport { autoLoadMiddleware } from 'astro-auto-load/middleware';\n\nconst myMiddleware = defineMiddleware(async (context, next) =\u003e {\n  // Your custom logic\n  console.log('Request:', context.url.pathname);\n  return next();\n});\n\n// IMPORTANT: Include autoLoadMiddleware in your sequence!\nexport const onRequest = sequence(myMiddleware, autoLoadMiddleware);\n```\n\n#### If you don't have middleware:\n\nThe integration automatically injects it for you - no `src/middleware.ts` needed! ✨\n\n**Why?** Astro uses **either** your manual `src/middleware.ts` export **or** integration-injected middleware, but not both. If you export `onRequest` yourself, you take full control and must include `autoLoadMiddleware` in your chain.\n\n### Routes Automatically Skipped\n\nThe middleware automatically skips the following paths for performance:\n\n- `/_astro/*` - Astro build assets\n- `/assets/*` - Static assets\n- `/api/*` - API routes\n\nThese routes bypass loader execution entirely.\n\n### Skipping Additional Routes\n\nTo skip additional paths (e.g., admin routes), create a wrapper middleware:\n\n```ts\n// src/middleware.ts\nimport { defineMiddleware, sequence } from 'astro:middleware';\nimport { autoLoadMiddleware } from 'astro-auto-load/middleware';\n\nconst conditionalAutoLoad = defineMiddleware(async (context, next) =\u003e {\n  // Skip admin routes\n  if (context.url.pathname.startsWith('/admin')) {\n    return next();\n  }\n\n  // Otherwise, run autoLoadMiddleware (which has its own built-in skips)\n  return autoLoadMiddleware(context, next);\n});\n\nexport const onRequest = conditionalAutoLoad;\n```\n\n## TypeScript\n\nThe package includes full TypeScript support with automatic type inference.\n\n### Automatic Type Inference (Recommended)\n\nSimply pass `typeof loader` to `getLoaderData`:\n\n```astro\n---\nimport { type Context, getLoaderData } from 'astro-auto-load/runtime';\n\nexport const loader = async (context: Context) =\u003e {\n  return {\n    name: 'Hugo',\n    age: 42,\n    hobbies: ['coding', 'cats']\n  };\n};\n\nconst data = await getLoaderData\u003ctypeof loader\u003e();\n// data is { name: string; age: number; hobbies: string[] }\n---\n```\n\n### Extracting Types for Reuse\n\nIf you need the type elsewhere, extract it using the `Loader` helper:\n\n```astro\n---\n// src/components/ParentComponent.astro\nimport { type Loader, getLoaderData } from 'astro-auto-load/runtime';\nimport { ChildComponent } from './ChildComponent.astro';\n\nexport const loader = async () =\u003e ({ count: 42 });\n\nexport type Data = Loader\u003ctypeof loader\u003e; // { count: number }\n\nconst data = await getLoaderData\u003cData\u003e();\n---\n\n\u003cChildComponent data={data} /\u003e\n```\n\n```astro\n---\n// src/components/ChildComponent.astro\nimport type { Data } from './ParentComponent.astro'\n\ntype Props {\n  data: Data;\n}\n\nconst { data } = Astro.props;\n---\n\n\u003cdiv\u003e{data.count}\u003c/div\u003e\n```\n\n## Limitations\n\n- **Only works in SSR mode** (not static builds)\n- **Per-request execution** - Loaders execute on each request; results are cached within the request but not across requests\n- **Loaders cannot access component props** - Loaders receive the `context` object (route params, URL, request) but not props passed to the component\n- **Build-time discovery** - Component tree is analyzed at build time, so dynamic imports or runtime-conditional components won't have their loaders extracted\n\n**What IS supported:**\n\n- ✅ Direct imports (`import Child from './Child.astro'`)\n- ✅ Slot-based composition (children passed via `\u003cslot /\u003e`)\n- ✅ Deeply nested component trees (any depth)\n- ✅ Conditional rendering with `{condition \u0026\u0026 \u003cComponent /\u003e}` (loader is extracted, just won't execute if component doesn't render)\n- ✅ Component reuse (same component used multiple times)\n\n**What is NOT supported:**\n\n- ❌ Dynamic imports (`const Component = await import('./Component.astro')`)\n- ❌ Static site generation (requires SSR)\n\n### Server Islands\n\n**✅ Fully Supported!**\n\nServer Islands work automatically because each Server Island request creates its own execution context. No special configuration needed!\n\n**How it works:**\n\n- **Regular SSR pages:** Middleware sets up context → Components register loaders → First `getLoaderData()` call executes all loaders in parallel\n- **Server Islands:** Same process runs independently for each Server Island request\n\nThe lazy execution model ensures that only the loaders needed for the rendered components execute, whether in the initial page or in a Server Island. ✨\n\n## Troubleshooting\n\n### Error: \"Middleware not configured\"\n\n**Full error:**\n\n```\n[astro-auto-load] Middleware not configured. Ensure autoLoadMiddleware is running.\n```\n\n**Cause:** You have a custom `src/middleware.ts` file, and `autoLoadMiddleware` is not included.\n\n**Solution:** Manually add `autoLoadMiddleware` to your middleware chain:\n\n```ts\n// src/middleware.ts\nimport { sequence } from 'astro:middleware';\nimport { autoLoadMiddleware } from 'astro-auto-load/middleware';\n\nexport const onRequest = sequence(\n  // your other middleware...\n  autoLoadMiddleware,\n);\n```\n\n### Error: \"Module URL not found\"\n\n**Full error:**\n\n```\n[astro-auto-load] Module URL not found. This should be auto-injected by the Vite plugin.\n```\n\n**Cause:** The Vite plugin transformation failed or the integration wasn't added to `astro.config.mjs`.\n\n**Solution:** Ensure the integration is properly installed:\n\n```js\n// astro.config.mjs\nimport autoLoad from 'astro-auto-load';\n\nexport default defineConfig({\n  output: 'server', // required\n  integrations: [autoLoad()],\n});\n```\n\n## License\n\nMIT\n\n## Contributing\n\nIssues and PRs welcome! This is an experimental integration.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjahilldev%2Fastro-auto-load","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjahilldev%2Fastro-auto-load","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjahilldev%2Fastro-auto-load/lists"}