Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/astro/kitchen-sink/.gitignore
Original file line number Diff line number Diff line change
@@ -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
138 changes: 138 additions & 0 deletions examples/astro/kitchen-sink/README.md
Original file line number Diff line number Diff line change
@@ -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/)
33 changes: 33 additions & 0 deletions examples/astro/kitchen-sink/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -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()],
},
});
30 changes: 30 additions & 0 deletions examples/astro/kitchen-sink/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
35 changes: 35 additions & 0 deletions examples/astro/kitchen-sink/src/components/BlogPostItem.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
const { title, date, excerpt, uri, featuredImage } = Astro.props?.post ?? {};
---

<article
class='container max-w-4xl px-10 py-6 mx-auto rounded-lg shadow-sm bg-gray-50 mb-4'>
<time datetime={date} class='text-sm text-gray-600'>
{
new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
})
}
</time>

{
featuredImage && (
<img
src={featuredImage?.node?.sourceUrl}
alt=''
class='w-full h-48 object-cover rounded-t-lg mt-2 mb-4'
/>
)
}

<h2 class='mt-3'>
<a href={uri} class='text-2xl font-bold hover:underline'>
{title}
</a>
</h2>

<div class='mt-2 mb-4' set:html={excerpt} />

<a href={uri} class='hover:underline text-orange-600 mt-4'> Read more </a>
</article>
18 changes: 18 additions & 0 deletions examples/astro/kitchen-sink/src/components/Header.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
import { GET_LAYOUT } from '@/queries/getLayout';
import { print } from 'graphql';

const { data } = await Astro.locals.client?.request(print(GET_LAYOUT));
---

<header class='bg-gray-800 text-white py-4 px-8'>
<div class='flex justify-between items-center max-w-4xl mx-auto'>
<div class='text-3xl font-semibold'>
<a href='/'>{data?.generalSettings?.title}</a>
</div>

<nav class='space-x-6'>
<a href='/' class='text-lg hover:underline'> Home</a>
</nav>
</div>
</header>
23 changes: 23 additions & 0 deletions examples/astro/kitchen-sink/src/components/Layout.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import Header from './Header.astro';
import '../styles/globals.css';

const pageTitle = Astro.props.pageTitle;
---

<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width' />
<link rel='icon' type='image/svg+xml' href='/favicon.svg' />
<meta name='generator' content={Astro.generator} />
<title>{pageTitle}</title>
</head>
<body>
<Header />
<main class='bg-stone-100 text-gray-800 pb-16 pt-8 min-h-screen'>
<slot />
</main>
</body>
</html>
7 changes: 7 additions & 0 deletions examples/astro/kitchen-sink/src/content.config.js
Original file line number Diff line number Diff line change
@@ -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 };
7 changes: 7 additions & 0 deletions examples/astro/kitchen-sink/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
declare namespace App {
interface Locals {
templateData?: import('./lib/templateHierarchy').TemplateData;
isPreview?: boolean;
client?: import('./lib/types').GraphQLClient;
}
}
26 changes: 26 additions & 0 deletions examples/astro/kitchen-sink/src/pages/404.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
import Layout from '@/components/Layout.astro';

// Set the response status to 404
Astro.response.status = 404;
---

<Layout title='Page Not Found'>
<div class='max-w-4xl mx-auto px-8 py-16'>
<div class='text-center mb-12'>
<h1 class='text-6xl font-bold text-gray-900 mb-4'>404</h1>
<h2 class='text-2xl font-semibold text-gray-700 mb-6'>Page Not Found</h2>
<p class='text-lg text-gray-600 mb-8'>
Sorry, the page you're looking for doesn't exist.
</p>
</div>

<div class='text-center'>
<a
href='/'
class='inline-flex items-center px-6 py-3 bg-gray-800 text-white font-medium rounded-lg hover:bg-gray-700 transition-colors duration-200'>
← Go back to the homepage
</a>
</div>
</div>
</Layout>
44 changes: 44 additions & 0 deletions examples/astro/kitchen-sink/src/pages/[...identifier].astro
Original file line number Diff line number Diff line change
@@ -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');
---
Loading
Loading