diff --git a/examples/astro/kitchen-sink/.gitignore b/examples/astro/kitchen-sink/.gitignore
new file mode 100644
index 000000000..f26f0594a
--- /dev/null
+++ b/examples/astro/kitchen-sink/.gitignore
@@ -0,0 +1,22 @@
+# Local environment variables
+.env
+.env.local
+.env.production
+
+# Build output
+dist/
+.astro/
+
+# Dependencies
+node_modules/
+
+# Logs
+*.log
+
+# Editor
+.vscode/
+.idea/
+
+# OS
+.DS_Store
+Thumbs.db
diff --git a/examples/astro/kitchen-sink/README.md b/examples/astro/kitchen-sink/README.md
new file mode 100644
index 000000000..433c933ee
--- /dev/null
+++ b/examples/astro/kitchen-sink/README.md
@@ -0,0 +1,138 @@
+# @faustjs/astro Template Hierarchy Example
+
+This example demonstrates how to use the `@faustjs/astro` package to automatically discover WordPress templates in an Astro project using the WordPress template hierarchy.
+
+## What This Example Shows
+
+- **Template Discovery**: Automatically finds WordPress template files in your `wp-templates` directory
+- **Content Collections**: Uses Astro's content collections to organize and query templates
+- **Template Hierarchy**: Follows WordPress template hierarchy rules for organizing templates
+- **Template Resolution**: SSR catch-all route that dynamically resolves WordPress URLs to templates
+
+## Project Structure
+
+```
+src/
+├── layouts/
+│ └── WordPressLayout.astro # Shared layout for all templates
+├── content.config.js # Content collection configuration
+└── pages/
+ ├── index.astro # Demo page showing discovered templates
+ ├── 404.astro # Custom 404 page
+ ├── [...uri].astro # Catch-all route implementing template resolution
+ └── wp-templates/ # WordPress template files (protected from direct access)
+ ├── index.astro # Home/blog template
+ ├── page.astro # Page template
+ ├── single-post.astro # Single post template
+ └── archive.astro # Archive template
+```
+
+## How It Works
+
+1. **Template Files**: Place your WordPress templates in `src/pages/wp-templates/`
+2. **Auto-Discovery**: The `createTemplateCollection()` function finds all template files
+3. **Content Collections**: Templates are available as an Astro content collection with `id` and `path`
+4. **SSR Resolution**: The `[...uri].astro` route dynamically fetches content and resolves templates
+5. **Shared Layout**: All templates use `WordPressLayout.astro` for consistent styling
+6. **Template Protection**: Templates can't be accessed directly (e.g., `/wp-templates/page` returns 404)
+
+## Running the Example
+
+```bash
+# Install dependencies (includes GraphQL requirements)
+pnpm install
+
+# Copy environment configuration
+cp .env.example .env
+
+# Edit .env to set your WordPress URL
+# WORDPRESS_URL=https://your-wordpress-site.com
+
+# Start development server (SSR mode)
+pnpm dev
+
+# Build for production (outputs server bundle)
+pnpm build
+```
+
+## Dependencies
+
+This example requires the following dependencies (automatically installed):
+
+- `@faustjs/astro` - Astro integration with WordPress template hierarchy
+- `@faustjs/template-hierarchy` - Core template hierarchy logic
+- `astro` - Astro framework (peer dependency)
+- `graphql` - GraphQL core library (peer dependency)
+- `graphql-tag` - GraphQL query parsing (peer dependency)
+
+## WordPress Setup
+
+This example requires a WordPress site with WPGraphQL plugin installed:
+
+1. **Install WPGraphQL**: Install the [WPGraphQL plugin](https://www.wpgraphql.com/) on your WordPress site
+2. **Set Environment Variable**: Copy `.env.example` to `.env` and set your WordPress URL
+3. **GraphQL Endpoint**: The endpoint should be `https://your-site.com/graphql`
+
+### Supported WordPress Content
+
+The example automatically discovers and creates routes for:
+
+- **Posts**: Individual blog posts with categories and tags
+- **Pages**: Static pages like About, Contact, etc.
+- **Category Archives**: Archive pages for each category
+- **Tag Archives**: Archive pages for each tag
+- **Home Page**: Main blog index
+
+## Template Discovery
+
+The `@faustjs/astro` package automatically discovers templates in your `wp-templates` directory:
+
+- **index.astro** → Home template
+- **page.astro** → Page template
+- **single-post.astro** → Single post template
+- **archive.astro** → Archive template
+
+Each template is automatically added to the content collection with an `id` and `path`.
+
+## Template Resolution Demo
+
+The catch-all route (`[...uri].astro`) fetches real WordPress content and demonstrates URL resolution:
+
+- `/` → `index.astro` (home page)
+- `/about` → `page.astro` (WordPress page)
+- `/hello-world` → `single-post.astro` (WordPress post)
+- `/category/uncategorized` → `archive.astro` (category archive)
+- `/tag/fun` → `archive.astro` (tag archive)
+
+_Note: Actual URLs depend on your WordPress content_
+
+## Content Collection Usage
+
+```js
+// Get all templates
+import { getCollection } from 'astro:content';
+const templates = await getCollection('templates');
+
+// Find a specific template by id
+const pageTemplate = templates.find((t) => t.id === 'page');
+
+// Each template has: { id: string, path: string }
+console.log(pageTemplate); // { id: 'page', path: 'wp-templates/page' }
+```
+
+## Real-World Usage
+
+This example shows how a real WordPress headless site works with SSR:
+
+1. **WordPress GraphQL API**: Dynamically fetches content from WordPress on each request
+2. **Server-Side Rendering**: Renders pages on-demand with fresh WordPress content
+3. **Template Resolution**: Uses WordPress template hierarchy to render content
+4. **Content Types**: Handles posts, pages, categories, and tags dynamically
+5. **SEO Ready**: Generates proper HTML with fresh WordPress content
+6. **404 Handling**: Automatically redirects to 404 for non-existent content
+
+## Learn More
+
+- [WordPress Template Hierarchy](https://developer.wordpress.org/themes/basics/template-hierarchy/)
+- [Astro Content Collections](https://docs.astro.build/en/guides/content-collections/)
+- [@faustjs/template-hierarchy](../packages/template-hierarchy/)
diff --git a/examples/astro/kitchen-sink/astro.config.mjs b/examples/astro/kitchen-sink/astro.config.mjs
new file mode 100644
index 000000000..2ec71dc14
--- /dev/null
+++ b/examples/astro/kitchen-sink/astro.config.mjs
@@ -0,0 +1,33 @@
+import { defineConfig } from 'astro/config';
+import tailwindcss from '@tailwindcss/vite';
+
+export default defineConfig({
+ output: 'server',
+ // Enable SSR for dynamic WordPress content
+ vite: {
+ resolve: {
+ alias: {
+ '@': '/src',
+ },
+ },
+ server: {
+ watch: {
+ // Explicitly watch the packages directories
+ ignored: [
+ '**/node_modules/**',
+ '!**/node_modules/@faustjs/**', // Watch @faustjs packages
+ ],
+ followSymlinks: true,
+ },
+ },
+ optimizeDeps: {
+ // Don't pre-bundle workspace packages so changes are picked up immediately
+ exclude: [
+ '@faustjs/astro',
+ '@faustjs/template-hierarchy',
+ '@faustjs/graphql',
+ ],
+ },
+ plugins: [tailwindcss()],
+ },
+});
diff --git a/examples/astro/kitchen-sink/package.json b/examples/astro/kitchen-sink/package.json
new file mode 100644
index 000000000..664a4cec2
--- /dev/null
+++ b/examples/astro/kitchen-sink/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@faustjs/astro-kitchen-sink-example",
+ "private": true,
+ "version": "0.1.0",
+ "license": "0BSD",
+ "type": "module",
+ "scripts": {
+ "dev": "astro dev",
+ "start": "astro dev",
+ "build": "astro check && astro build",
+ "preview": "astro preview",
+ "astro": "astro"
+ },
+ "dependencies": {
+ "@faustjs/astro": "workspace:*",
+ "@faustjs/data-fetching": "workspace:*",
+ "@faustjs/template-hierarchy": "workspace:*",
+ "@tailwindcss/vite": "^4.1.12",
+ "astro": "^5.1.1",
+ "graphql": "^16.9.0",
+ "graphql-tag": "^2.12.6",
+ "tailwindcss": "^4.1.11"
+ },
+ "devDependencies": {
+ "typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=24"
+ }
+}
diff --git a/examples/astro/kitchen-sink/src/components/BlogPostItem.astro b/examples/astro/kitchen-sink/src/components/BlogPostItem.astro
new file mode 100644
index 000000000..420595e43
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/components/BlogPostItem.astro
@@ -0,0 +1,35 @@
+---
+const { title, date, excerpt, uri, featuredImage } = Astro.props?.post ?? {};
+---
+
+
+
+ {
+ new Date(date).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ })
+ }
+
+
+ {
+ featuredImage && (
+
+ )
+ }
+
+
+
+
+
+ Read more
+
diff --git a/examples/astro/kitchen-sink/src/components/Header.astro b/examples/astro/kitchen-sink/src/components/Header.astro
new file mode 100644
index 000000000..a22290c8b
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/components/Header.astro
@@ -0,0 +1,18 @@
+---
+import { GET_LAYOUT } from '@/queries/getLayout';
+import { print } from 'graphql';
+
+const { data } = await Astro.locals.client?.request(print(GET_LAYOUT));
+---
+
+
diff --git a/examples/astro/kitchen-sink/src/components/Layout.astro b/examples/astro/kitchen-sink/src/components/Layout.astro
new file mode 100644
index 000000000..01f18537a
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/components/Layout.astro
@@ -0,0 +1,23 @@
+---
+import Header from './Header.astro';
+import '../styles/globals.css';
+
+const pageTitle = Astro.props.pageTitle;
+---
+
+
+
+
+
+
+
+
+ {pageTitle}
+
+
+
+
+
+
+
+
diff --git a/examples/astro/kitchen-sink/src/content.config.js b/examples/astro/kitchen-sink/src/content.config.js
new file mode 100644
index 000000000..e8c52b83b
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/content.config.js
@@ -0,0 +1,7 @@
+import { defineCollection } from "astro:content";
+import { createTemplateCollection } from "@faustjs/astro";
+
+// Set up WordPress template collection
+const templates = defineCollection(createTemplateCollection());
+
+export const collections = { templates };
diff --git a/examples/astro/kitchen-sink/src/env.d.ts b/examples/astro/kitchen-sink/src/env.d.ts
new file mode 100644
index 000000000..7b59111d0
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/env.d.ts
@@ -0,0 +1,7 @@
+declare namespace App {
+ interface Locals {
+ templateData?: import('./lib/templateHierarchy').TemplateData;
+ isPreview?: boolean;
+ client?: import('./lib/types').GraphQLClient;
+ }
+}
diff --git a/examples/astro/kitchen-sink/src/pages/404.astro b/examples/astro/kitchen-sink/src/pages/404.astro
new file mode 100644
index 000000000..048321584
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/404.astro
@@ -0,0 +1,26 @@
+---
+import Layout from '@/components/Layout.astro';
+
+// Set the response status to 404
+Astro.response.status = 404;
+---
+
+
+
+
+
404
+
Page Not Found
+
+ Sorry, the page you're looking for doesn't exist.
+
+
+
+
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/[...identifier].astro b/examples/astro/kitchen-sink/src/pages/[...identifier].astro
new file mode 100644
index 000000000..81882980d
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/[...identifier].astro
@@ -0,0 +1,44 @@
+---
+import {
+ uriToTemplate,
+ createDefaultClient,
+ setGraphQLClient,
+} from '@faustjs/astro';
+import { getAuthString } from '@/utils/getAuthString.js';
+
+// Determine if we are in preview mode based on the URL parameter and the secret
+const isPreview =
+ Astro.url.searchParams.get('preview') === 'true' &&
+ import.meta.env.WP_PREVIEW_SECRET === Astro.url.searchParams.get('secret');
+
+const headers = isPreview ? { Authorization: getAuthString() } : undefined;
+
+// Set up GraphQL client using environment variable
+const client = createDefaultClient(import.meta.env.WORDPRESS_URL, headers);
+setGraphQLClient(client);
+
+const { identifier = '/' } = Astro.params;
+
+// Fetch template data based on the URI and preview mode
+const templateData = await uriToTemplate(
+ isPreview
+ ? {
+ id: identifier,
+ asPreview: true,
+ }
+ : { uri: identifier },
+);
+
+// Make data available to other templates and components
+Astro.locals.templateData = templateData;
+Astro.locals.isPreview = isPreview || false;
+Astro.locals.client = client;
+
+// If a template was found, rewrite to that template's path
+if (templateData.template) {
+ return Astro.rewrite(templateData.template.path);
+}
+
+// If no template was found, redirect to 404
+return Astro.rewrite('/404');
+---
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro
new file mode 100644
index 000000000..26ee4def3
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro
@@ -0,0 +1,72 @@
+---
+import Layout from '@/components/Layout.astro';
+import { GET_ARCHIVE } from '@/queries/getArchive';
+import { print } from 'graphql';
+
+/**
+ * This is a template for the WordPress template hierarchy.
+ * It will be used to render the WordPress templates.
+ * It should never be used directly.
+ * This check confirms that the template is being used in the correct context.
+ * If the template is being used directly, it will redirect to the 404 page.
+ */
+if (Astro.url.pathname === Astro.originPathname) {
+ return Astro.rewrite('/404');
+}
+
+const uri = Astro.locals.templateData?.seedNode?.uri;
+
+const { data, error } = await Astro.locals.client.request(print(GET_ARCHIVE), {
+ uri,
+});
+
+if (error) {
+ console.error('Error fetching data:', error);
+ return Astro.rewrite('/500');
+}
+
+if (!data?.nodeByUri) {
+ console.error('HTTP/404 - Not Found in WordPress:', uri);
+ return Astro.rewrite('/404');
+}
+
+const categoryData = data?.nodeByUri;
+const { posts, name } = categoryData || {};
+---
+
+
+
+
{name}
+
+ {
+ posts?.edges?.map((item: any) => {
+ const post = item.node;
+
+ return (
+
+
+
+ {post.featuredImage && (
+
+ )}
+
+
+ By {post.author.node.name} on{' '}
+ {new Date(post.date).toLocaleDateString()}
+
+
+
+
+ );
+ })
+ }
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro
new file mode 100644
index 000000000..ea5cdb354
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro
@@ -0,0 +1,42 @@
+---
+import Layout from '@/components/Layout.astro';
+import BlogPostItem from '@/components/BlogPostItem.astro';
+import { GET_POSTS } from '@/queries/getPosts';
+import { print } from 'graphql';
+
+/**
+ * This is a template for the WordPress template hierarchy.
+ * It will be used to render the WordPress templates.
+ * It should never be used directly.
+ * This check confirms that the template is being used in the correct context.
+ * If the template is being used directly, it will redirect to the 404 page.
+ */
+if (Astro.url.pathname === Astro.originPathname) {
+ return Astro.rewrite('/404');
+}
+
+const databaseId = Astro.locals.templateData?.seedNode?.databaseId;
+
+const { data, error } = await Astro.locals.client.request(print(GET_POSTS));
+
+if (error) {
+ console.error('Error fetching data:', error);
+ return Astro.rewrite('/500');
+}
+
+if (!data.posts) {
+ console.error('HTTP/404 - Not Found in WordPress:', databaseId);
+ return Astro.rewrite('/404');
+}
+
+const posts = data?.posts;
+---
+
+
+ {
+ posts?.edges?.map((item: any) => {
+ const post = item.node;
+ return ;
+ })
+ }
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro
new file mode 100644
index 000000000..9bd4d8452
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro
@@ -0,0 +1,53 @@
+---
+import Layout from '@/components/Layout.astro';
+
+/**
+ * This is the index template for the WordPress template hierarchy.
+ * It serves as the fallback template for any content that doesn't have
+ * a more specific template (like single, page, archive, etc.).
+ * This template should never be accessed directly.
+ */
+if (Astro.url.pathname === Astro.originPathname) {
+ return Astro.rewrite('/404');
+}
+
+const templateData = Astro.locals.templateData;
+const seedNode = templateData?.seedNode;
+const contentType = seedNode?.__typename || 'content';
+---
+
+
+
+
+
+ Template Not Found
+
+
+ No specific template was found for the {contentType} type.
+ This content is being displayed using the default fallback template.
+
+
+
+
+
+
+
+ Debug Information
+
+
+
{JSON.stringify(
+ templateData,
+ null,
+ 2
+ )}
+
+
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro
new file mode 100644
index 000000000..1f59f8f05
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro
@@ -0,0 +1,58 @@
+---
+import Layout from '@/components/Layout.astro';
+import { GET_PAGE } from '@/queries/getPage';
+import { print } from 'graphql';
+
+/**
+ * This is a template for the WordPress template hierarchy.
+ * It will be used to render the WordPress templates.
+ * It should never be used directly.
+ * This check confirms that the template is being used in the correct context.
+ * If the template is being used directly, it will redirect to the 404 page.
+ */
+if (Astro.url.pathname === Astro.originPathname) {
+ return Astro.rewrite('/404');
+}
+
+const isPreview = Astro.locals.isPreview;
+const databaseId = Astro.locals.templateData?.seedNode?.databaseId;
+
+const { data, error } = await Astro.locals.client.request(print(GET_PAGE), {
+ databaseId,
+ asPreview: isPreview,
+});
+
+if (error) {
+ console.error('Error fetching data:', error);
+ return Astro.rewrite('/500');
+}
+
+if (!data.page) {
+ console.error('HTTP/404 - Not Found in WordPress:', databaseId);
+ return Astro.rewrite('/404');
+}
+
+const { title, content, featuredImage } = data?.page || {};
+---
+
+
+
+
+
+ {title}
+
+
+
+ {
+ featuredImage && (
+
+ )
+ }
+
+
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro
new file mode 100644
index 000000000..c00431300
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro
@@ -0,0 +1,73 @@
+---
+import Layout from '@/components/Layout.astro';
+import { GET_POST } from '@/queries/getPost.js';
+import { print } from 'graphql';
+
+/**
+ * This is a template for the WordPress template hierarchy.
+ * It will be used to render the WordPress templates.
+ * It should never be used directly.
+ * This check confirms that the template is being used in the correct context.
+ * If the template is being used directly, it will redirect to the 404 page.
+ */
+if (Astro.url.pathname === Astro.originPathname) {
+ return Astro.rewrite('/404');
+}
+
+const isPreview = Astro.locals.isPreview;
+const databaseId = Astro.locals.templateData?.seedNode?.databaseId;
+
+const { data, error } = await Astro.locals.client.request(print(GET_POST), {
+ databaseId,
+ asPreview: isPreview,
+});
+
+if (error) {
+ console.error('Error fetching data:', error);
+ return Astro.rewrite('/500');
+}
+
+if (!data.post) {
+ console.error('HTTP/404 - Not Found in WordPress:', databaseId);
+ return Astro.rewrite('/404');
+}
+
+const { title, content, featuredImage } = data?.post || {};
+---
+
+
+
+
+
+ {title}
+
+
+
+ {
+ featuredImage && (
+
+ )
+ }
+
+
+
+
+
+
diff --git a/examples/astro/kitchen-sink/src/queries/getArchive.js b/examples/astro/kitchen-sink/src/queries/getArchive.js
new file mode 100644
index 000000000..fc59b7f14
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/queries/getArchive.js
@@ -0,0 +1,68 @@
+import gql from 'graphql-tag';
+
+export const GET_ARCHIVE = gql`
+ query GetArchivePage($uri: String!) {
+ nodeByUri(uri: $uri) {
+ ... on Category {
+ name
+ posts {
+ edges {
+ node {
+ id
+ title
+ content
+ date
+ uri
+ featuredImage {
+ node {
+ id
+ sourceUrl
+ altText
+ mediaDetails {
+ width
+ height
+ }
+ }
+ }
+ author {
+ node {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ ... on Tag {
+ name
+ posts {
+ edges {
+ node {
+ id
+ title
+ content
+ date
+ uri
+ featuredImage {
+ node {
+ id
+ sourceUrl
+ altText
+ mediaDetails {
+ width
+ height
+ }
+ }
+ }
+ author {
+ node {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/examples/astro/kitchen-sink/src/queries/getLayout.js b/examples/astro/kitchen-sink/src/queries/getLayout.js
new file mode 100644
index 000000000..f278ee1af
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/queries/getLayout.js
@@ -0,0 +1,10 @@
+import { gql } from 'graphql-tag';
+
+export const GET_LAYOUT = gql`
+ query GetLayout {
+ generalSettings {
+ title
+ description
+ }
+ }
+`;
diff --git a/examples/astro/kitchen-sink/src/queries/getPage.js b/examples/astro/kitchen-sink/src/queries/getPage.js
new file mode 100644
index 000000000..76646f1a5
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/queries/getPage.js
@@ -0,0 +1,27 @@
+import gql from 'graphql-tag';
+
+export const GET_PAGE = gql`
+ query GetPage($databaseId: ID!, $asPreview: Boolean = false) {
+ page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
+ title
+ content
+ date
+ author {
+ node {
+ name
+ }
+ }
+ featuredImage {
+ node {
+ id
+ sourceUrl
+ altText
+ mediaDetails {
+ width
+ height
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/examples/astro/kitchen-sink/src/queries/getPost.js b/examples/astro/kitchen-sink/src/queries/getPost.js
new file mode 100644
index 000000000..30ba7b8d7
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/queries/getPost.js
@@ -0,0 +1,27 @@
+import gql from 'graphql-tag';
+
+export const GET_POST = gql`
+ query GetPost($databaseId: ID!, $asPreview: Boolean = false) {
+ post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
+ title
+ content
+ date
+ author {
+ node {
+ name
+ }
+ }
+ featuredImage {
+ node {
+ id
+ sourceUrl
+ altText
+ mediaDetails {
+ width
+ height
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/examples/astro/kitchen-sink/src/queries/getPosts.js b/examples/astro/kitchen-sink/src/queries/getPosts.js
new file mode 100644
index 000000000..9cfcdd4e1
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/queries/getPosts.js
@@ -0,0 +1,33 @@
+import gql from 'graphql-tag';
+
+export const GET_POSTS = gql`
+ query GetPosts {
+ posts {
+ edges {
+ node {
+ id
+ title
+ content
+ date
+ uri
+ featuredImage {
+ node {
+ id
+ sourceUrl
+ altText
+ mediaDetails {
+ width
+ height
+ }
+ }
+ }
+ author {
+ node {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/examples/astro/kitchen-sink/src/styles/globals.css b/examples/astro/kitchen-sink/src/styles/globals.css
new file mode 100644
index 000000000..d4b507858
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/styles/globals.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/examples/astro/kitchen-sink/src/utils/getAuthString.js b/examples/astro/kitchen-sink/src/utils/getAuthString.js
new file mode 100644
index 000000000..666bc6721
--- /dev/null
+++ b/examples/astro/kitchen-sink/src/utils/getAuthString.js
@@ -0,0 +1,8 @@
+// Forming the authentication string for WordPress App Password
+// More info: https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/
+
+export const getAuthString = () =>
+ 'Basic ' +
+ Buffer.from(
+ import.meta.env.WP_USERNAME + ':' + import.meta.env.WP_APP_PASSWORD,
+ ).toString('base64');
diff --git a/examples/astro/kitchen-sink/tsconfig.json b/examples/astro/kitchen-sink/tsconfig.json
new file mode 100644
index 000000000..547e4a6ce
--- /dev/null
+++ b/examples/astro/kitchen-sink/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/packages/astro/templateHierarchy.js b/packages/astro/templateHierarchy.js
index a0e471f20..5ef364d02 100644
--- a/packages/astro/templateHierarchy.js
+++ b/packages/astro/templateHierarchy.js
@@ -20,7 +20,7 @@ console.log(
* @param {import('@faustjs/template-hierarchy').UriToTemplateBaseParams & { graphqlClient?: import('./types.js').GraphQLClient }} options - Resolution options
* @returns {Promise} The resolved template data
*/
-export async function uriToTemplate({ uri, graphqlClient }) {
+export async function uriToTemplate({ uri, graphqlClient, id, asPreview }) {
/** @type {import('@faustjs/template-hierarchy').TemplateData} */
const returnData = {
uri,
@@ -28,20 +28,30 @@ export async function uriToTemplate({ uri, graphqlClient }) {
availableTemplates: undefined,
possibleTemplates: undefined,
template: undefined,
+ seedNode: undefined,
};
// Get the GraphQL client - use provided one or get configured one
const client = getGraphQLClient(graphqlClient);
- const { data, error } = await getSeedQuery({ uri, graphqlClient: client });
+ const { data, error } = await getSeedQuery({
+ uri,
+ graphqlClient: client,
+ id,
+ asPreview,
+ });
returnData.seedQuery = { data, error };
+ const seedNode = data?.nodeByUri || data?.contentNode;
+
+ returnData.seedNode = seedNode ?? error;
+
if (error) {
console.error('Error fetching seedQuery:', error);
return returnData;
}
- if (!data.nodeByUri) {
+ if (!seedNode) {
console.error('HTTP/404 - Not Found in WordPress:', uri);
returnData.template = { id: '404 Not Found', path: '/404' };
@@ -58,7 +68,7 @@ export async function uriToTemplate({ uri, graphqlClient }) {
return returnData;
}
- const possibleTemplates = getPossibleTemplates(data.nodeByUri);
+ const possibleTemplates = getPossibleTemplates(seedNode);
returnData.possibleTemplates = possibleTemplates;
diff --git a/packages/nextjs/pages/templateHierarchy.js b/packages/nextjs/pages/templateHierarchy.js
index 43e365e3f..e9afbc41b 100644
--- a/packages/nextjs/pages/templateHierarchy.js
+++ b/packages/nextjs/pages/templateHierarchy.js
@@ -70,8 +70,6 @@ export async function uriToTemplate({
const possibleTemplates = getPossibleTemplates(seedNode);
- console.log(possibleTemplates);
-
returnData.possibleTemplates = possibleTemplates;
if (!possibleTemplates || possibleTemplates.length === 0) {
diff --git a/packages/template-hierarchy/seedQueryExecutor.js b/packages/template-hierarchy/seedQueryExecutor.js
index 15a4a4dd5..9eec53d08 100644
--- a/packages/template-hierarchy/seedQueryExecutor.js
+++ b/packages/template-hierarchy/seedQueryExecutor.js
@@ -22,8 +22,6 @@ export async function getSeedQuery({ uri, id, asPreview, graphqlClient }) {
asPreview,
});
- console.log('Seed query result:', uri, id, asPreview, result);
-
return {
data: result.data || result,
error: result.error || null,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index dad6121ba..7e18df114 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -66,6 +66,37 @@ importers:
specifier: ^3.0.2
version: 3.0.2
+ examples/astro/kitchen-sink:
+ dependencies:
+ '@faustjs/astro':
+ specifier: workspace:*
+ version: link:../../../packages/astro
+ '@faustjs/data-fetching':
+ specifier: workspace:*
+ version: link:../../../packages/data-fetching
+ '@faustjs/template-hierarchy':
+ specifier: workspace:*
+ version: link:../../../packages/template-hierarchy
+ '@tailwindcss/vite':
+ specifier: ^4.1.12
+ version: 4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))
+ astro:
+ specifier: ^5.1.1
+ version: 5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3)
+ graphql:
+ specifier: ^16.9.0
+ version: 16.11.0
+ graphql-tag:
+ specifier: ^2.12.6
+ version: 2.12.6(graphql@16.11.0)
+ tailwindcss:
+ specifier: ^4.1.11
+ version: 4.1.11
+ devDependencies:
+ typescript:
+ specifier: ^5.6.3
+ version: 5.8.3
+
examples/astro/template-hierarchy:
dependencies:
'@faustjs/astro':
@@ -88,7 +119,7 @@ importers:
specifier: ^5.6.3
version: 5.8.3
- examples/nextjs/previews:
+ examples/nextjs/kitchen-sink:
dependencies:
'@faustjs/data-fetching':
specifier: workspace:*
@@ -1273,60 +1304,117 @@ packages:
'@tailwindcss/node@4.1.11':
resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==}
+ '@tailwindcss/node@4.1.12':
+ resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==}
+
'@tailwindcss/oxide-android-arm64@4.1.11':
resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
+ '@tailwindcss/oxide-android-arm64@4.1.12':
+ resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
'@tailwindcss/oxide-darwin-arm64@4.1.11':
resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
+ '@tailwindcss/oxide-darwin-arm64@4.1.12':
+ resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
'@tailwindcss/oxide-darwin-x64@4.1.11':
resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
+ '@tailwindcss/oxide-darwin-x64@4.1.12':
+ resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
'@tailwindcss/oxide-freebsd-x64@4.1.11':
resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
+ '@tailwindcss/oxide-freebsd-x64@4.1.12':
+ resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11':
resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12':
+ resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.11':
resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.12':
+ resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.11':
resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.12':
+ resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.11':
resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.12':
+ resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
'@tailwindcss/oxide-linux-x64-musl@4.1.11':
resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ '@tailwindcss/oxide-linux-x64-musl@4.1.12':
+ resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
'@tailwindcss/oxide-wasm32-wasi@4.1.11':
resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==}
engines: {node: '>=14.0.0'}
@@ -1339,25 +1427,58 @@ packages:
- '@emnapi/wasi-threads'
- tslib
+ '@tailwindcss/oxide-wasm32-wasi@4.1.12':
+ resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.11':
resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.12':
+ resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.11':
resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.12':
+ resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
'@tailwindcss/oxide@4.1.11':
resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==}
engines: {node: '>= 10'}
+ '@tailwindcss/oxide@4.1.12':
+ resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==}
+ engines: {node: '>= 10'}
+
'@tailwindcss/postcss@4.1.11':
resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==}
+ '@tailwindcss/vite@4.1.12':
+ resolution: {integrity: sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+
'@tybys/wasm-util@0.10.0':
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
@@ -3700,6 +3821,9 @@ packages:
tailwindcss@4.1.11:
resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==}
+ tailwindcss@4.1.12:
+ resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==}
+
tapable@2.2.2:
resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
engines: {node: '>=6'}
@@ -5029,42 +5153,88 @@ snapshots:
source-map-js: 1.2.1
tailwindcss: 4.1.11
+ '@tailwindcss/node@4.1.12':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.5.1
+ lightningcss: 1.30.1
+ magic-string: 0.30.17
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.12
+
'@tailwindcss/oxide-android-arm64@4.1.11':
optional: true
+ '@tailwindcss/oxide-android-arm64@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-darwin-arm64@4.1.11':
optional: true
+ '@tailwindcss/oxide-darwin-arm64@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-darwin-x64@4.1.11':
optional: true
+ '@tailwindcss/oxide-darwin-x64@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-freebsd-x64@4.1.11':
optional: true
+ '@tailwindcss/oxide-freebsd-x64@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11':
optional: true
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.11':
optional: true
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.11':
optional: true
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.11':
optional: true
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-linux-x64-musl@4.1.11':
optional: true
+ '@tailwindcss/oxide-linux-x64-musl@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-wasm32-wasi@4.1.11':
optional: true
+ '@tailwindcss/oxide-wasm32-wasi@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.11':
optional: true
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.12':
+ optional: true
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.11':
optional: true
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.12':
+ optional: true
+
'@tailwindcss/oxide@4.1.11':
dependencies:
detect-libc: 2.0.4
@@ -5083,6 +5253,24 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.11
'@tailwindcss/oxide-win32-x64-msvc': 4.1.11
+ '@tailwindcss/oxide@4.1.12':
+ dependencies:
+ detect-libc: 2.0.4
+ tar: 7.4.3
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.12
+ '@tailwindcss/oxide-darwin-arm64': 4.1.12
+ '@tailwindcss/oxide-darwin-x64': 4.1.12
+ '@tailwindcss/oxide-freebsd-x64': 4.1.12
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.12
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.12
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.12
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.12
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.12
+
'@tailwindcss/postcss@4.1.11':
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -5091,6 +5279,13 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.11
+ '@tailwindcss/vite@4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))':
+ dependencies:
+ '@tailwindcss/node': 4.1.12
+ '@tailwindcss/oxide': 4.1.12
+ tailwindcss: 4.1.12
+ vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)
+
'@tybys/wasm-util@0.10.0':
dependencies:
tslib: 2.8.1
@@ -8274,6 +8469,8 @@ snapshots:
tailwindcss@4.1.11: {}
+ tailwindcss@4.1.12: {}
+
tapable@2.2.2: {}
tar@7.4.3: