├── .env.local.sample ├── .github ├── CODEOWNERS ├── assets │ └── faustjs-logo.svg └── workflows │ ├── blueprint-export-diff.yml │ └── build.yml ├── .gitignore ├── .nvmrc ├── DEVELOPMENT.md ├── README.md ├── acm-blueprint.zip ├── atlas-blueprint.zip ├── components ├── EntryHeader.js ├── FeaturedImage.js ├── Footer.js ├── Header.js ├── PostListItem.js ├── footer.js └── header.js ├── faust.config.js ├── fragments └── PostListFragment.js ├── next.config.js ├── package.json ├── pages ├── [...wordpressNode].js ├── _app.js ├── api │ └── faust │ │ └── [[...route]].js ├── example.js ├── index.js ├── preview.js └── sitemap.xml.js ├── possibleTypes.json ├── queries ├── MenuQueries.js └── SiteSettingsQuery.js ├── screenshots ├── category-page.png ├── front-page.png ├── single-page.png └── single-post.png ├── styles ├── archive.module.css ├── entry-header.module.css ├── featured-image.module.css ├── footer.module.css ├── front-page.module.css ├── globals.css ├── header.module.css └── post-list-item.module.css └── wp-templates ├── archive.js ├── front-page.js ├── index.js ├── page.js └── single.js /.env.local.sample: -------------------------------------------------------------------------------- 1 | # Your WordPress site URL 2 | NEXT_PUBLIC_WORDPRESS_URL=https://faustexample.wpengine.com 3 | 4 | # Plugin secret found in WordPress Settings->Headless 5 | # FAUST_SECRET_KEY=YOUR_PLUGIN_SECRET 6 | 7 | # The URL of your site, used by Faust for sitemap generation, however you can name this whatever you want. 8 | NEXT_PUBLIC_SITE_URL=http://localhost:3000 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wpengine/headless-open-source 2 | 3 | # jira:[18721] is where issues related to this repository should be ticketed 4 | -------------------------------------------------------------------------------- /.github/assets/faustjs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/blueprint-export-diff.yml: -------------------------------------------------------------------------------- 1 | name: Blueprint Export Diff 2 | on: pull_request 3 | jobs: 4 | blueprint_export_diff: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | with: 9 | fetch-depth: 0 10 | - uses: actions/setup-node@v2 11 | with: 12 | node-version: "16" 13 | - name: Check for changes 14 | id: check-for-export-changes 15 | run: echo ::set-output name=check::$(git diff --name-only origin/main --exit-code -- acm-blueprint.zip) 16 | - name: Setup 17 | if: steps.check-for-export-changes.outputs.check != 0 18 | run: | 19 | wget https://github.com/wpengine/faust-scaffold/raw/main/acm-blueprint.zip -O /tmp/acm-blueprint-base.zip 20 | unzip -j /tmp/acm-blueprint-base.zip -d /tmp/acm-blueprint-base 21 | unzip -j ./acm-blueprint.zip -d /tmp/acm-blueprint-compare 22 | - name: Create Diff 23 | if: steps.check-for-export-changes.outputs.check != 0 24 | run: | 25 | diff -ry --suppress-common-lines /tmp/acm-blueprint-base/ /tmp/acm-blueprint-compare/ || true > /tmp/diff-output.txt 26 | cat /tmp/diff-output.txt 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: pull_request 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node: ["18", "20"] 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: ${{ matrix.node }} 14 | - run: npm install 15 | - run: cp .env.local.sample .env.local 16 | - run: npm run build 17 | continue-on-error: FALSE 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | package-lock.json 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/jod 2 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## Updating the ACM Blueprint Export 4 | 5 | ### Importing 6 | 7 | 1. Create a fresh site in Local or use the FakerPress plugin to wipe your current WordPress database to a fresh install. 8 | 2. Install and activate Atlas Content Modeler. 9 | 3. From the WP CLI, run `wp acm blueprint import `. You can use the GitHub RAW URL from the repo: https://github.com/wpengine/faust-scaffold/raw/main/acm-blueprint.zip 10 | 11 | This will import the ACM Blueprint export into your WordPress database. Make any modifications as necessary. 12 | 13 | ### Exporting 14 | 15 | 1. Before exporting, make sure you have deleted any of the initial content that gets created upon a WordPress install (e.g. "Sample Page", "Hello World", any comments, etc.) 16 | 2. From the WP CLI, run `wp acm blueprint export --open --wp-options=category_base,permalink_structure,nav_menu_options,theme_mods_twentytwentytwo --post-types=nav_menu_item,post,page,testimonial,project`. This will export the site into the appropriate ACM Blueprint .zip, and also open the location where the .zip was saved. It will also export the Navigational Menus and the CPTs from the ACM models. 17 | 3. Replace the existing `acm-blueprint.zip` in the repo with the newly exported `acm-blueprint.zip` and make a PR with the changes. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Faust.js Logo](./.github/assets/faustjs-logo.svg) Faust.js Starter Kit 2 | 3 | This repository contains a starter kit to get you up and running quickly on [WP Engine's Headless Platform](https://wpengine.com/headless-wordpress/) with a WordPress site skeleton for more advanced developers. 4 | 5 | ## Getting Started 🚀 6 | 7 | To get started on WP Engine's Platform please follow the docs here [https://developers.wpengine.com/docs/atlas/getting-started/create-app/](https://developers.wpengine.com/docs/atlas/getting-started/create-app/) 8 | 9 | ## Project Structure 10 | 11 | ```bash 12 | ├── components/ 13 | ├── fragments/ 14 | ├── pages/ 15 | ├── queries/ 16 | ├── styles/ 17 | ├── wp-templates/ 18 | │ ├── archive.js # For your category/archive templates 19 | │ ├── front-page.js # Front page 20 | │ ├── index.js # Mapping for available templates 21 | │ ├── page.js # Single page 22 | │ └── single.js # Single post or singular 23 | ├── DEVELOPMENT.md 24 | ├── faust.config.js 25 | ├── next.config.js 26 | ├── package.json 27 | ├── possibleTypes.json 28 | └── README.md 29 | └── screenshots 30 | ``` 31 | 32 | ## Available Commands 33 | 34 | | Command | Script | Description | 35 | | ------------- | ----------------------------- | -------------------------------- | 36 | | `dev` | `faust dev` | Start the development server | 37 | | `build` | `faust build` | Build the project for production | 38 | | `generate` | `faust generatePossibleTypes` | Generate GraphQL possible types | 39 | | `start` | `faust start` | Start the production server | 40 | | `format` | `prettier . --write` | Format code with Prettier | 41 | | `test:format` | `prettier . --check` | Check code formatting | 42 | 43 | ## Screenshots 44 | 45 |
46 | View Screenshots 47 | 48 | ![Front Page](screenshots/front-page.png) 49 | 50 | ![Category Page](screenshots/category-page.png) 51 | 52 | ![Single Page](screenshots/single-page.png) 53 | 54 | ![Single Post](screenshots/single-post.png) 55 | 56 |
57 | 58 | ## Our Community 🩵 59 | 60 | At WP Engine, we have a strong community built around headless WordPress to support you with your journey. 61 | 62 | - [Discord Headless Community Channel](https://faustjs.org/discord) 63 | - [Fortnightly Headless Community Call](https://discord.gg/headless-wordpress-836253505944813629?event=1371472220592930857) 64 | - [WP Engine's Headless Platform developer community](https://wpengine.com/builders/headless) 65 | - [WP Engine`s Builders YouTube Channel](https://www.youtube.com/@WPEngineBuilders) 66 | - [WP Engine's Headless Platform](https://wpengine.com/headless-wordpress/) 67 | - [WP Engines Headless Platform Docs](https://developers.wpengine.com/docs/atlas/overview/) 68 | 69 | ## Plugin Ecosystem 🪄 70 | 71 | - [Faust.js](https://faustjs.org) 72 | - [WPGraphQL](https://www.wpgraphql.com) 73 | - [WPGraphQL Content Blocks](https://github.com/wpengine/wp-graphql-content-blocks) 74 | - [WPGraphQL IDE](https://github.com/wp-graphql/wpgraphql-ide) 75 | - [HWP Toolkit](https://github.com/wpengine/hwptoolkit) 76 | 77 | ## Documentation 🔎 78 | 79 | > [!NOTE] 80 | > We are continuously adding new docs for [Faustjs.org](https://faustjs.org/docs) 81 | 82 | - [Faust.js Documentation](https://faustjs.org/docs/) 83 | - [Headless Platform Documentation](https://wpengine.com/headless-wordpress/) 84 | - [WPGraphQL Documentation](https://developers.wpengine.com/docs/atlas/overview/) 85 | 86 | 87 | ## Contributions 88 | 89 | ## Contributor License Agreement 90 | 91 | All external contributors to WP Engine products must have a signed Contributor License Agreement (CLA) in place before the contribution may be accepted into any WP Engine codebase. 92 | 93 | 1. [Submit your name and email](https://wpeng.in/cla/) 94 | 2. 📝 Sign the CLA emailed to you 95 | 3. 📥 Receive copy of signed CLA 96 | 97 | ❤️ Thank you for helping us fulfill our legal obligations in order to continue empowering builders through headless WordPress. 98 | -------------------------------------------------------------------------------- /acm-blueprint.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wpengine/faust-scaffold/c180598cec8cb8f04984a92974ead88bfc103656/acm-blueprint.zip -------------------------------------------------------------------------------- /atlas-blueprint.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wpengine/faust-scaffold/c180598cec8cb8f04984a92974ead88bfc103656/atlas-blueprint.zip -------------------------------------------------------------------------------- /components/EntryHeader.js: -------------------------------------------------------------------------------- 1 | import style from "../styles/entry-header.module.css"; 2 | 3 | export default function EntryHeader({ title, date, author }) { 4 | return ( 5 |
6 | {title &&

{title}

} 7 | 8 | {date && author && ( 9 |
10 | By {author} on 11 |
12 | )} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/FeaturedImage.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Image from "next/image"; 3 | import styles from "../styles/featured-image.module.css"; 4 | 5 | export function FeaturedImage({ 6 | post, 7 | classNames = "h-48 my-9 relative", 8 | uri = false, 9 | title = "", 10 | }) { 11 | if (!post.featuredImage?.node?.sourceUrl) { 12 | return ""; 13 | } 14 | 15 | return ( 16 |
17 | {typeof uri === "string" && uri.trim() !== "" ? ( 18 | 19 | {post.featuredImage.node.altText 26 | 27 | ) : ( 28 | {post.featuredImage.node.altText 35 | )} 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | import styles from "../styles/footer.module.css"; 2 | 3 | export default function Footer() { 4 | return ( 5 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | import Link from "next/link"; 3 | import style from "../styles/header.module.css"; 4 | 5 | export default function Header({ siteTitle, siteDescription, menuItems }) { 6 | return ( 7 |
8 |
9 | 10 |

{siteTitle}

11 |

{siteDescription}

12 | 13 | 14 | 23 |
24 |
25 | ); 26 | } 27 | 28 | Header.fragments = { 29 | entry: gql` 30 | fragment HeaderFragment on RootQuery { 31 | generalSettings { 32 | title 33 | description 34 | } 35 | primaryMenuItems: menuItems(where: { location: PRIMARY }) { 36 | nodes { 37 | id 38 | uri 39 | path 40 | label 41 | parentId 42 | cssClasses 43 | menu { 44 | node { 45 | name 46 | } 47 | } 48 | } 49 | } 50 | } 51 | `, 52 | }; 53 | -------------------------------------------------------------------------------- /components/PostListItem.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FeaturedImage } from "./FeaturedImage"; 3 | import styles from "../styles/post-list-item.module.css"; 4 | 5 | export default function PostListItem({ post }) { 6 | const { title, excerpt, uri, date } = post; 7 | 8 | return ( 9 |
10 | 17 | 18 |

19 | 20 | {title} 21 | 22 |

23 | 24 | {post.author && post.author.node && ( 25 |
26 | by {post.author.node.name} 27 |
28 | )} 29 | 30 | 36 | 37 |
41 | 42 | 43 | Read more 44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import styles from "../styles/footer.module.css"; 2 | 3 | export default function Footer() { 4 | return ( 5 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | import Link from "next/link"; 3 | import style from "../styles/header.module.css"; 4 | 5 | export default function Header({ siteTitle, siteDescription, menuItems }) { 6 | return ( 7 |
8 |
9 | 10 |

{siteTitle}

11 |

{siteDescription}

12 | 13 | 14 | 23 |
24 |
25 | ); 26 | } 27 | 28 | Header.fragments = { 29 | entry: gql` 30 | fragment HeaderFragment on RootQuery { 31 | generalSettings { 32 | title 33 | description 34 | } 35 | primaryMenuItems: menuItems(where: { location: PRIMARY }) { 36 | nodes { 37 | id 38 | uri 39 | path 40 | label 41 | parentId 42 | cssClasses 43 | menu { 44 | node { 45 | name 46 | } 47 | } 48 | } 49 | } 50 | } 51 | `, 52 | }; 53 | -------------------------------------------------------------------------------- /faust.config.js: -------------------------------------------------------------------------------- 1 | import { setConfig } from "@faustwp/core"; 2 | import templates from "./wp-templates"; 3 | import possibleTypes from "./possibleTypes.json"; 4 | 5 | /** 6 | * @type {import('@faustwp/core').FaustConfig} 7 | **/ 8 | export default setConfig({ 9 | templates, 10 | experimentalPlugins: [], 11 | possibleTypes, 12 | }); 13 | -------------------------------------------------------------------------------- /fragments/PostListFragment.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | 3 | export const POST_LIST_FRAGMENT = gql` 4 | fragment PostListFragment on Post { 5 | id 6 | title 7 | uri 8 | excerpt 9 | date 10 | featuredImage { 11 | node { 12 | sourceUrl 13 | altText 14 | } 15 | } 16 | author { 17 | node { 18 | name 19 | avatar { 20 | url 21 | } 22 | } 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { withFaust } = require("@faustwp/core"); 2 | 3 | /** 4 | * @type {import('next').NextConfig} 5 | **/ 6 | module.exports = withFaust({ 7 | images: { 8 | domains: ["faustexample.wpengine.com"], 9 | }, 10 | trailingSlash: true, 11 | }); 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@faustwp/faust-scaffold", 3 | "private": true, 4 | "scripts": { 5 | "dev": "faust dev", 6 | "build": "faust build", 7 | "generate": "faust generatePossibleTypes", 8 | "start": "faust start", 9 | "format": "prettier . --write", 10 | "test:format": "prettier . --check" 11 | }, 12 | "dependencies": { 13 | "@apollo/client": "^3.13.8", 14 | "@faustwp/cli": "^3.2.3", 15 | "@faustwp/core": "^3.2.3", 16 | "graphql": "^16.10.0", 17 | "next": "^15.3.1", 18 | "prettier": "^3.5.3", 19 | "react": "^19.1.0", 20 | "react-dom": "^19.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pages/[...wordpressNode].js: -------------------------------------------------------------------------------- 1 | import { getWordPressProps, WordPressTemplate } from "@faustwp/core"; 2 | 3 | export default function Page(props) { 4 | return ; 5 | } 6 | 7 | export function getStaticProps(ctx) { 8 | return getWordPressProps({ ctx }); 9 | } 10 | 11 | export async function getStaticPaths() { 12 | return { 13 | paths: [], 14 | fallback: "blocking", 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import "../faust.config"; 2 | import React from "react"; 3 | import { useRouter } from "next/router"; 4 | import { FaustProvider } from "@faustwp/core"; 5 | import "../styles/globals.css"; 6 | 7 | export default function MyApp({ Component, pageProps }) { 8 | const router = useRouter(); 9 | 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /pages/api/faust/[[...route]].js: -------------------------------------------------------------------------------- 1 | import "../../../faust.config"; 2 | import { apiRouter } from "@faustwp/core"; 3 | 4 | export default apiRouter; 5 | -------------------------------------------------------------------------------- /pages/example.js: -------------------------------------------------------------------------------- 1 | import { gql, useQuery } from "@apollo/client"; 2 | import Head from "next/head"; 3 | import Header from "../components/Header"; 4 | import EntryHeader from "../components/EntryHeader"; 5 | import Footer from "../components/Footer"; 6 | import { getNextStaticProps } from "@faustwp/core"; 7 | 8 | /** 9 | * Next.js file based page example with Faust helpers. 10 | */ 11 | export default function Page() { 12 | const { data } = useQuery(Page.query); 13 | 14 | const { title: siteTitle, description: siteDescription } = 15 | data.generalSettings; 16 | const menuItems = data.primaryMenuItems.nodes; 17 | 18 | return ( 19 | <> 20 | 21 | {siteTitle} 22 | 23 | 24 |
29 | 30 |
31 | 32 |

Next.js pages are still supported!

33 |
34 | 35 |