Skip to content

Commit 11e6de8

Browse files
committed
initial nextjs pages router implementation
1 parent 616c2a7 commit 11e6de8

8 files changed

Lines changed: 641 additions & 0 deletions

File tree

packages/nextjs/README.md

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
# @faustjs/nextjs
2+
3+
Next.js integration for FaustJS template hierarchy (Pages Router support).
4+
5+
## Installation
6+
7+
```bash
8+
npm install @faustjs/nextjs
9+
# or
10+
pnpm add @faustjs/nextjs
11+
# or
12+
yarn add @faustjs/nextjs
13+
```
14+
15+
## Quick Start
16+
17+
### 1. Create Template Files
18+
19+
Create your WordPress templates in `src/wp-templates/`:
20+
21+
```
22+
src/wp-templates/
23+
├── index.js # Template registry/exports
24+
├── home.js # Home/blog template
25+
├── page.js # Page template
26+
├── single.js # Single post template
27+
├── archive.js # Archive template
28+
└── default.js # Default fallback template
29+
```
30+
31+
### 2. Create Templates
32+
33+
```jsx
34+
// src/wp-templates/home.js
35+
export default function HomeTemplate({ templateData }) {
36+
const { seedQuery } = templateData;
37+
const posts = seedQuery.data?.posts?.nodes || [];
38+
39+
return (
40+
<div>
41+
<h1>Blog Home</h1>
42+
{posts.map((post) => (
43+
<article key={post.id}>
44+
<h2>{post.title}</h2>
45+
<div dangerouslySetInnerHTML={{ __html: post.content }} />
46+
</article>
47+
))}
48+
</div>
49+
);
50+
}
51+
```
52+
53+
```jsx
54+
// src/wp-templates/single.js
55+
export default function SingleTemplate({ templateData }) {
56+
const { seedQuery } = templateData;
57+
const post = seedQuery.data?.post;
58+
59+
if (!post) {
60+
return <div>Post not found</div>;
61+
}
62+
63+
return (
64+
<article>
65+
<h1>{post.title}</h1>
66+
<div dangerouslySetInnerHTML={{ __html: post.content }} />
67+
</article>
68+
);
69+
}
70+
```
71+
72+
```jsx
73+
// src/wp-templates/index.js - Template registry with dynamic imports
74+
import dynamic from 'next/dynamic';
75+
76+
const home = dynamic(() => import('./home.js'), {
77+
loading: () => <p>Loading Home Template...</p>,
78+
});
79+
80+
const single = dynamic(() => import('./single.js'), {
81+
loading: () => <p>Loading Single Template...</p>,
82+
});
83+
84+
const page = dynamic(() => import('./page.js'), {
85+
loading: () => <p>Loading Page Template...</p>,
86+
});
87+
88+
const archive = dynamic(() => import('./archive.js'), {
89+
loading: () => <p>Loading Archive Template...</p>,
90+
});
91+
92+
const index = dynamic(() => import('./index-template.js'), {
93+
loading: () => <p>Loading Index Template...</p>,
94+
});
95+
96+
export default { home, single, page, archive, index };
97+
```
98+
99+
### 3. Create Catch-All Route
100+
101+
````jsx
102+
// src/pages/[[...uri]].js
103+
import { uriToTemplate } from '@faustjs/nextjs';
104+
import availableTemplates from '@/wp-templates';
105+
106+
export default function Page(props) {
107+
const { templateData } = props;
108+
109+
const PageTemplate = availableTemplates[templateData.template?.id];
110+
111+
if (!PageTemplate) {
112+
return <div>Template not found</div>;
113+
}
114+
115+
return <PageTemplate {...props} />;
116+
}
117+
118+
export async function getServerSideProps(context) {
119+
const { params } = context;
120+
const uri = Array.isArray(params?.uri)
121+
? '/' + params.uri.join('/') + '/'
122+
: '/';
123+
124+
const templateData = await uriToTemplate({
125+
uri,
126+
availableTemplates: Object.keys(availableTemplates),
127+
wordpressUrl: process.env.WORDPRESS_URL,
128+
});
129+
130+
if (!templateData?.template?.id || templateData?.template?.id === '404 Not Found') {
131+
return { notFound: true };
132+
}
133+
134+
return {
135+
props: {
136+
uri,
137+
templateData: JSON.parse(JSON.stringify(templateData)),
138+
},
139+
};
140+
}
141+
```### 4. Configure Environment
142+
143+
```bash
144+
# .env.local
145+
WORDPRESS_URL=https://your-wordpress-site.com
146+
````
147+
148+
## API Reference
149+
150+
### Core Functions
151+
152+
#### `uriToTemplate(options)`
153+
154+
Resolves a URI to WordPress template data using the template hierarchy.
155+
156+
```js
157+
import { uriToTemplate } from '@faustjs/nextjs';
158+
import availableTemplates from '@/wp-templates';
159+
160+
const templateData = await uriToTemplate({
161+
uri: '/about/',
162+
availableTemplates: Object.keys(availableTemplates),
163+
wordpressUrl: process.env.WORDPRESS_URL,
164+
});
165+
```
166+
167+
#### `getSeedQuery(options)`
168+
169+
Executes the seed query to fetch WordPress content.
170+
171+
```js
172+
import { getSeedQuery } from '@faustjs/nextjs';
173+
174+
const { data, error } = await getSeedQuery({
175+
uri: '/about/',
176+
wordpressUrl: process.env.WORDPRESS_URL,
177+
});
178+
```
179+
180+
### Pages Router Utilities
181+
182+
Since we use `next/dynamic` directly in the template index file, no additional utilities are needed. Simply import your templates and use them directly.
183+
184+
## Configuration
185+
186+
### NextJS Config
187+
188+
```js
189+
// next.config.js
190+
import { createNextJSConfig } from '@faustjs/nextjs';
191+
192+
const nextConfig = createNextJSConfig({
193+
wordpressUrl: process.env.WORDPRESS_URL,
194+
});
195+
196+
export default nextConfig;
197+
```
198+
199+
### Custom GraphQL Client
200+
201+
```js
202+
import { GraphQLClient } from 'graphql-request';
203+
import { uriToTemplate } from '@faustjs/nextjs';
204+
import availableTemplates from '@/wp-templates';
205+
206+
const client = new GraphQLClient('https://example.com/index.php?graphql');
207+
208+
const templateData = await uriToTemplate({
209+
uri: '/about/',
210+
availableTemplates: Object.keys(availableTemplates),
211+
graphqlClient: client,
212+
});
213+
```
214+
215+
## Advanced Usage
216+
217+
### Custom Data Fetching
218+
219+
```js
220+
// src/pages/[[...uri]].js
221+
export async function getServerSideProps(context) {
222+
const { params } = context;
223+
const uri = Array.isArray(params?.uri)
224+
? '/' + params.uri.join('/') + '/'
225+
: '/';
226+
227+
const templateData = await uriToTemplate({
228+
uri,
229+
availableTemplates: Object.keys(availableTemplates),
230+
wordpressUrl: process.env.WORDPRESS_URL,
231+
});
232+
233+
if (!templateData?.template?.id) {
234+
return { notFound: true };
235+
}
236+
237+
// Fetch additional data based on template and WordPress content
238+
let additionalData = {};
239+
240+
if (templateData.template.id === 'single') {
241+
// Fetch related posts for single posts
242+
additionalData.relatedPosts = await fetchRelatedPosts(
243+
templateData.seedQuery.data.nodeByUri.id,
244+
);
245+
} else if (templateData.template.id === 'page') {
246+
// Fetch custom fields for pages
247+
additionalData.customFields = await fetchCustomFields(
248+
templateData.seedQuery.data.nodeByUri.id,
249+
);
250+
}
251+
252+
return {
253+
props: {
254+
uri,
255+
templateData: JSON.parse(JSON.stringify(templateData)),
256+
additionalData,
257+
},
258+
};
259+
}
260+
```
261+
262+
### Template Usage
263+
264+
```js
265+
import availableTemplates from '@/wp-templates';
266+
267+
// Use templates directly - they're already configured with next/dynamic
268+
export default function Page({ templateData }) {
269+
const TemplateComponent = availableTemplates[templateData.template?.id];
270+
271+
if (!TemplateComponent) {
272+
return <div>Template not found</div>;
273+
}
274+
275+
return <TemplateComponent {...props} />;
276+
}
277+
```
278+
279+
### Static Generation
280+
281+
```js
282+
// For ISR (Incremental Static Regeneration)
283+
export async function getStaticProps(context) {
284+
const { params } = context;
285+
const uri = Array.isArray(params?.uri)
286+
? '/' + params.uri.join('/') + '/'
287+
: '/';
288+
289+
const templateData = await uriToTemplate({
290+
uri,
291+
availableTemplates: Object.keys(availableTemplates),
292+
wordpressUrl: process.env.WORDPRESS_URL,
293+
});
294+
295+
if (!templateData?.template?.id) {
296+
return { notFound: true };
297+
}
298+
299+
return {
300+
props: {
301+
uri,
302+
templateData: JSON.parse(JSON.stringify(templateData)),
303+
},
304+
revalidate: 3600, // Revalidate every hour
305+
};
306+
}
307+
308+
// Required for catch-all routes with getStaticProps
309+
export async function getStaticPaths() {
310+
return {
311+
paths: [],
312+
fallback: 'blocking',
313+
};
314+
}
315+
```
316+
317+
## TypeScript Support
318+
319+
The package includes TypeScript definitions. For best experience, create a types file:
320+
321+
```ts
322+
// types/wordpress.ts
323+
import type { NextJSTemplateData } from '@faustjs/nextjs';
324+
325+
export interface TemplateProps {
326+
templateData: NextJSTemplateData;
327+
uri: string;
328+
[key: string]: any;
329+
}
330+
```
331+
332+
## WordPress Setup
333+
334+
1. Install [WPGraphQL](https://www.wpgraphql.com/) plugin
335+
2. Set your GraphQL endpoint in environment variables
336+
3. Ensure your WordPress site is accessible from your Next.js application
337+
338+
## Template Hierarchy
339+
340+
The package follows WordPress template hierarchy rules:
341+
342+
- `index.js` - Default fallback template
343+
- `home.js` - Home page template
344+
- `page.js` - Default page template
345+
- `single.js` - Default single post template
346+
- `archive.js` - Default archive template
347+
- `404.js` - 404 error template
348+
349+
More specific templates take precedence over general ones, following WordPress conventions.
350+
351+
## License
352+
353+
MIT

packages/nextjs/config.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @file Configuration utilities for NextJS integration
3+
*/
4+
5+
/**
6+
* Create a NextJS configuration with WordPress template hierarchy support
7+
* @param {import('./pages/types.js').NextJSConfig} [options={}] - Configuration options
8+
* @returns {Object} NextJS configuration object
9+
*/
10+
export function createNextJSConfig(options = {}) {
11+
const { wordpressUrl, graphqlClient } = options;
12+
13+
return {
14+
// Environment variables for WordPress integration
15+
env: {
16+
WORDPRESS_URL: wordpressUrl || process.env.WORDPRESS_URL,
17+
},
18+
19+
// Template configuration
20+
templateHierarchy: {
21+
graphqlClient,
22+
},
23+
};
24+
}

packages/nextjs/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @file Main entry point for the @faustjs/nextjs package
3+
*/
4+
5+
// Import types for JSDoc usage
6+
import './types.js';
7+
8+
// Export configuration utilities (framework agnostic)
9+
export { createNextJSConfig } from './config.js';

0 commit comments

Comments
 (0)