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
25 changes: 25 additions & 0 deletions examples/sveltekit/template-hierarchy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
node_modules

# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build

# OS
.DS_Store
Thumbs.db

# Env
.env
.env.*
!.env.example
!.env.test

# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

package-lock.json
88 changes: 88 additions & 0 deletions examples/sveltekit/template-hierarchy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# FaustJS SvelteKit Template Hierarchy Example

This example demonstrates how to use FaustJS with SvelteKit to create a headless WordPress application with automatic template hierarchy support.

## What is Template Hierarchy?

WordPress template hierarchy determines which template file is used to display different types of content (posts, pages, archives, etc.). This example shows how to implement similar functionality in a SvelteKit application using FaustJS.

## Features

- ✅ Automatic template selection based on WordPress content type
- ✅ Support for custom post types and archives
- ✅ WordPress-style template hierarchy (single.svelte, archive.svelte, index.svelte)
- ✅ GraphQL data fetching with URQL
- ✅ Server-side rendering (SSR)

## Getting Started

### Prerequisites

- Node.js v16.0.0 or newer
- A WordPress site with the [FaustJS plugin](https://wordpress.org/plugins/faustwp/) installed
- WPGraphQL plugin installed on your WordPress site

### Installation

1. Clone this repository or copy this example
2. Install dependencies:

```bash
npm install
```

3. Configure your WordPress URL in the `.env` file:

```bash
WORDPRESS_URL=https://your-wordpress-site.com
```

### Development

Start the development server:

```bash
npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open
```

The application will automatically:

- Fetch content from your WordPress site
- Determine the appropriate template based on the URL
- Render the content using the matching Svelte template

### Template Structure

Templates are located in `src/wp-templates/`:

- `index.svelte` - Default template (homepage, fallback)
- `single.svelte` - Single post/page template
- `archive.svelte` - Archive pages (categories, tags, custom post types)

### Building

To create a production version of your app:

```bash
npm run build
```

You can preview the production build with `npm run preview`.

## How It Works

1. The `[...uri]/+page.server.js` route catches all URLs
2. Uses `uriToTemplate()` from `@faustjs/sveltekit` to:
- Query WordPress for content at the given URI
- Determine the appropriate template type
- Fetch the necessary data
3. Renders the content using the matching Svelte template

## Learn More

- [FaustJS Documentation](https://faustjs.org/docs/)
- [SvelteKit Documentation](https://kit.svelte.dev/docs)
- [WordPress Template Hierarchy](https://developer.wordpress.org/themes/basics/template-hierarchy/)
31 changes: 31 additions & 0 deletions examples/sveltekit/template-hierarchy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@faustjs/sveltekit-template-hierarchy-example",
"private": true,
"version": "0.1.0",
"license": "0BSD",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@faustjs/sveltekit": "workspace:*",
"@faustjs/template-hierarchy": "workspace:*",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"vite": "^6.2.6"
},
"dependencies": {
"@urql/core": "^5.1.1",
"@urql/exchange-persisted": "^4.3.1",
"deepmerge": "^4.3.1",
"graphql": "^16.11.0"
}
}
12 changes: 12 additions & 0 deletions examples/sveltekit/template-hierarchy/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
14 changes: 14 additions & 0 deletions examples/sveltekit/template-hierarchy/src/hooks.server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { dev } from '$app/environment';

export const handle = async ({ event, resolve }) => {
if (
dev &&
event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json'
) {
return new Response(undefined, { status: 404 });
}

return resolve(event, {
filterSerializedResponseHeaders: () => true, // basically get all headers
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script>
const { children } = $props();
</script>

<main>
{@render children()}
</main>

<style>
main {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 800px;
margin: 0 auto;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const load = async (event) => {
const { data } = event;

const template = await import(`$wp/${data.templateData.template.id}.svelte`);

return {
...data,
template: template.default,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
createDefaultClient,
setGraphQLClient,
uriToTemplate,
} from '@faustjs/sveltekit';
import { WORDPRESS_URL } from '$env/static/private';

export const load = async (event) => {
const {
params: { uri },
fetch,
} = event;

const workingUri = uri || '/';

const client = createDefaultClient(WORDPRESS_URL);
setGraphQLClient(client);

const templateData = await uriToTemplate({
fetch,
uri: workingUri,
graphqlClient: client,
});

return {
uri: workingUri,
templateData,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
const { data } = $props();
</script>

<data.template data={data.graphqlData} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { json } from '@sveltejs/kit';
const TEMPLATE_PATH = 'wp-templates';

export const GET = async ({ url }) => {
const uri = url.searchParams.get('uri');

if (!uri) {
return new Response('Missing URI', { status: 400 });
}

const files = await readdir(join('src', TEMPLATE_PATH));

const templates = [];

for (const file of files) {
if (file.startsWith('+')) {
continue;
}

const slug = file.replace('.svelte', '');

templates.push({
id: slug,
path: join('/', TEMPLATE_PATH, slug),
});
}

return json(templates);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script>
// Example WordPress archive template for SvelteKit
// In a real implementation, you would receive archive data as props
// from the parent route's load function

// Example archive data (in real app this would come from props or store)
let archiveType = 'Category';
let archiveName = 'Technology';
let posts = [
{
id: 1,
title: 'Archive Post 1',
excerpt: 'This would be a post from the archive...',
date: 'March 15, 2024'
},
{
id: 2,
title: 'Archive Post 2',
excerpt: 'This would be another post from the archive...',
date: 'March 10, 2024'
}
];
</script>

<svelte:head>
<title>Archive Template - WordPress Archive</title>
</svelte:head>

<h1>WordPress Archive Template</h1>
<p>
This template would render WordPress archive pages (category, tag, date,
etc.).
</p>

<div class="content-area">
<h2>Archive: {archiveType} - {archiveName}</h2>
<p>Showing posts from this {archiveType.toLowerCase()}...</p>

{#each posts as post (post.id)}
<div class="post-preview">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<small>Posted on {post.date}</small>
</div>
{/each}
</div>

<style>
h1 {
color: #333;
border-bottom: 2px solid #0066cc;
padding-bottom: 10px;
margin-bottom: 20px;
}

.content-area {
background: #e8f4f8;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}

.post-preview {
background: #f0f8ff;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
border-left: 4px solid #0066cc;
}

.post-preview h3 {
margin-top: 0;
color: #0066cc;
}

.post-preview small {
color: #666;
font-style: italic;
}

h2 {
color: #333;
margin-bottom: 15px;
}

p {
line-height: 1.6;
color: #555;
}
</style>
Loading
Loading