├── .circleci └── config.yml ├── .editorconfig ├── .env ├── .env.production ├── .eslintrc ├── .gitignore ├── .wp-env.json ├── .wp-env └── .htaccess ├── LICENSE ├── README.md ├── codegen.yml ├── components ├── Blocks │ ├── ClassicEditorBlock │ │ └── ClassicEditorBlock.tsx │ ├── Heading │ │ └── Heading.tsx │ ├── ImageBlock │ │ └── ImageBlock.tsx │ ├── List │ │ └── List.tsx │ ├── Paragraph │ │ └── Paragraph.tsx │ ├── Quote │ │ ├── Quote.module.css │ │ ├── Quote.test.tsx │ │ └── Quote.tsx │ ├── Table │ │ └── Table.tsx │ ├── UnsupportedBlock │ │ ├── UnsupportedBlock.module.css │ │ └── UnsupportedBlock.tsx │ └── index.tsx ├── Card │ ├── Card.module.css │ └── Card.tsx ├── ClientOnly │ └── ClientOnly.tsx ├── Image │ ├── Image.test.tsx │ └── Image.tsx ├── Loading │ ├── Loading.module.css │ └── Loading.tsx ├── Page │ └── Page.tsx ├── PostContent │ └── PostContent.tsx ├── PostList │ └── PostList.tsx ├── SearchForm │ ├── SearchForm.module.css │ └── SearchForm.tsx ├── SiteFooter │ └── SiteFooter.tsx └── SiteHeader │ └── SiteHeader.tsx ├── graphql ├── apollo-link.ts ├── apollo-provider.tsx ├── apollo.ts ├── fragments │ ├── ContentBlock.graphql │ ├── ContentNode.graphql │ ├── ContentType.graphql │ ├── MediaItem.graphql │ └── PageInfo.graphql └── queries │ ├── AllContentTypes.graphql │ ├── ContentNodeBySlug.graphql │ ├── ContentNodePreviewById.graphql │ ├── ContentNodesBySearchTerm.graphql │ ├── ContentTypeByName.graphql │ └── MediaItems.graphql ├── jest.config.js ├── jest.setup.js ├── lib ├── blocks.test.ts ├── blocks.ts ├── hooks │ └── useInternalLinkRouting.ts ├── links.test.ts ├── links.ts ├── log.ts └── redis │ ├── client.ts │ └── index.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── [...slug].tsx ├── _app.tsx ├── api │ ├── books.ts │ ├── healthcheck.ts │ └── robots.ts ├── index.tsx ├── latest │ └── [content_type].tsx ├── media │ ├── index.module.css │ └── index.tsx ├── preview │ └── [token] │ │ └── [id].tsx └── search │ └── index.tsx ├── public └── favicon.ico ├── styles └── new.css ├── tsconfig.json └── vip.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 2.1 4 | 5 | orbs: 6 | node: circleci/node@5.0.0 7 | 8 | workflows: 9 | lint: 10 | jobs: 11 | - node/run: 12 | name: "lint" 13 | npm-run: lint 14 | matrix-tests: 15 | jobs: 16 | - node/test: 17 | name: "unit tests" 18 | matrix: 19 | parameters: 20 | version: 21 | - "18.9" 22 | - "16.17" 23 | - "14.20" 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_GRAPHQL_ENDPOINT="http://localhost:8888/graphql" 2 | NEXT_PUBLIC_SERVER_URL="http://localhost:3000" 3 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_GRAPHQL_ENDPOINT="https://wp-content-hub.go-vip.net/graphql" 2 | NEXT_PUBLIC_SERVER_URL="https://node-content-hub.go-vip.net" 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:editorconfig/all", 10 | "plugin:jest/recommended", 11 | "next", 12 | "next/core-web-vitals" 13 | ], 14 | "plugins": [ 15 | "editorconfig", 16 | "jest" 17 | ], 18 | "rules": { 19 | "editorconfig/indent": [ 20 | "error", 21 | { 22 | "SwitchCase": 1 23 | } 24 | ], 25 | "@typescript-eslint/ban-ts-comment": [ 26 | "error", 27 | { 28 | "ts-expect-error": "allow-with-description" 29 | } 30 | ], 31 | "@typescript-eslint/no-unused-vars": [ 32 | "error", 33 | { 34 | "argsIgnorePattern": "^_", 35 | "varsIgnorePattern": "^_" 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # codegen 37 | /graphql/generated 38 | -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "WP_HOME": "http://localhost:3000" 4 | }, 5 | "plugins": [ 6 | "Automattic/vip-decoupled-bundle" 7 | ], 8 | "mappings": { 9 | ".htaccess": ".wp-env/.htaccess" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.wp-env/.htaccess: -------------------------------------------------------------------------------- 1 | # Allow rewrites for GraphQL endpoint 2 | 3 | 4 | RewriteEngine On 5 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 6 | RewriteBase / 7 | RewriteRule ^index\.php$ - [L] 8 | RewriteCond %{REQUEST_FILENAME} !-f 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule . /index.php [L] 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2021 Automattic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js boilerplate for decoupled / headless WordPress applications 2 | 3 | This is WordPress VIP's [Next.js][nextjs] boilerplate for decoupled WordPress. It is not required for Node.js applications on VIP, but it helps solve many common use cases for decoupled / headless WordPress applications. If you choose to use it, VIP's [decoupled plugin bundle][bundle] must be installed and activated on the WordPress backend. The notes below describe its behavior when run on [WordPress VIP's platform][wpvip]. 4 | 5 | > ⚠️ This project is under active development. If you are a VIP customer, please let us know if you'd like to use this boilerplate and we can provide additional guidance. Issues and PRs are welcome. 💖 6 | 7 | ## Features 8 | 9 | + [Next.js][nextjs] 13 10 | + Fetch data with [Apollo][apollo] and [WPGraphQL][wpgraphql] 11 | + Seamless previewing 12 | + Easily map Gutenberg blocks to React components and incorporate your design system 13 | + Automatic [code generation][code-generation] from GraphQL queries 14 | + Optional TypeScript support 15 | 16 | ## Getting started 17 | 18 | ### Install dependencies 19 | 20 | ```sh 21 | npm install 22 | ``` 23 | 24 | ### Local configuration 25 | 26 | The application is preconfigured to run with a local install of WordPress: 27 | 28 | 1. Install `wp-env` [via these instructions][wpenv]. 29 | 2. In the root directory of this repository, start and configure WordPress via: 30 | 31 | ```sh 32 | wp-env start 33 | wp-env run cli "wp rewrite structure '/%year%/%monthnum%/%postname%/'" 34 | ``` 35 | 36 | Note that plain permalinks [are not supported](#permalink-setup) by the boilerplate project. 37 | 38 | 3. Next, run the Next.js development server with: 39 | 40 | ```sh 41 | npm run dev 42 | ``` 43 | 44 | You should now be able to access: 45 | 46 | + Next.js front-end via [http://localhost:3000][local-nextjs] 47 | + WordPress backend via [http://localhost:8888/wp-admin][local-wordpress] with the [default credentials][wpenv-credentials]: 48 | 49 | + Username: `admin` 50 | + Password: `password` 51 | 52 | ### Remote configuration 53 | 54 | Update the following environment variables defined in the `.env` file: 55 | 56 | + `NEXT_PUBLIC_GRAPHQL_ENDPOINT`: The full URL, including protocol, of your WPGraphQL endpoint. You can find it in the WordPress Admin Dashboard > Settings > VIP Decoupled. 57 | + `NEXT_PUBLIC_SERVER_URL`: The full URL, including protocol, of this Next.js site. This allows things like sitemaps and link routing to be configured correctly. 58 | 59 | If you have additional environment variables, you can add them here. 60 | 61 | Working remote environment settings are available in `.env.production` to test against a live VIP WordPress backend. 62 | 63 | Note that plain permalinks are not by the boilerplate project. Read the [**Permalink Setup** section below](#permalink-setup) for supported permalink configurations. 64 | 65 | You should also review `vip.config.js` for additional configuration options. 66 | 67 | ### Development server 68 | 69 | Start a development server, with hot-reloading, at [http://localhost:3000][local-nextjs]. 70 | 71 | ```sh 72 | npm run dev 73 | ``` 74 | 75 | ### Production build 76 | 77 | ```sh 78 | npm run build 79 | npm start 80 | ``` 81 | 82 | These are the exact same commands that will be executed when your application runs on WordPress VIP. Testing your production build locally is a good step to take when troubleshooting issues in your production site. 83 | 84 | Note that the `build` directory has been added to the `.gitignore` file. This avoids a steady buildup of build artifacts in the git repository, but means that your local build will not be pushed to WordPress VIP. Instead, we'll run the build automatically whenever you push up code changes. 85 | 86 | ## Previewing 87 | 88 | Previewing unpublished posts or updates to published posts works out of the box. Simply click the “Preview” button in WordPress and you’ll be redirected to a one-time-use preview link on the Next.js site. You can share your preview link with others; as long as they are logged in to WordPress in the same browser and have permissions to access that content, they will be able to preview it as well. 89 | 90 | ## Permalink setup 91 | 92 | [Plain permalinks][permalinks-plain] are not recommended or supported by the boilerplate project. Any other pretty permalink structure that includes the `%postname%` slug in the URL are supported, like: 93 | 94 | - **Day and name**: `/%year%/%monthnum%/%day%/%postname%/` 95 | - **Month and name**: `/%year%/%monthnum%/%postname%/` 96 | - **Post name**: `/%postname%/` 97 | 98 | Pretty permalinks [can be enabled via the WordPress backend][permalinks-setup]. 99 | 100 | ## Gutenberg / block support 101 | 102 | When you query for content (posts, pages, and custom post types), you'll receive the post content as blocks. If the content was written with WordPress's [block editor][gutenberg] (Gutenberg), these blocks will correspond directly with the blocks you see in the editor. The block data you will receive roughly matches the output of WordPress’s [`parse_blocks` function][parse-blocks], with some enhancements. To learn more, you can follow how block data is parsed and resolved in [our extension of WPGraphQL][content-blocks]. 103 | 104 | Receiving the content as blocks allow you to easily create customizations defining the related component for each block type. This boilerplate provides a mapping for basic components like headings, paragraphs, lists, and tables (see [`'@/components/Blocks/index.tsx'`](https://github.com/Automattic/vip-go-nextjs-skeleton/blob/trunk/components/Blocks/index.tsx)). We supply components for a few basic block types in order to demonstrate this approach, but you will undoubtedly need to write additional components. 105 | 106 | Here is a simple example of how to override the default block mapping to support all of the default and custom blocks that you use in your WordPress instance: 107 | 108 | ```js 109 | import PostContent from '@/lib/components'; 110 | import MyCustomHeader from 'my-design-system'; 111 | 112 | export default function Post( props: Props ) { 113 | return ( 114 |
115 |

{props.title}

116 | 122 |
123 | ); 124 | } 125 | ``` 126 | 127 | If you used WordPress's [classic editor][classic-editor], you will receive a single block representing the HTML content of the entire post. A `ClassicEditorBlock` component is provided to render these blocks. 128 | 129 | ### Unsupported blocks 130 | 131 | When running the development server, in order to help you identify blocks that have not yet been mapped to a React component, this boilerplate will display an "Unsupported block" notice. This notice is suppressed in production and the block is simply ignored. 132 | 133 | ## Internal link routing 134 | 135 | When writing code that links to another page in your Next.js application, you should use Next.js's [`Link` component][nextjs-link] so that the request is routed client-side without a full-round trip to the server. 136 | 137 | However, when user-authored blocks contain links, the `innerHTML` is handled by React and you don't have an opportunity to use the `Link` component. To address this, our boilerplate [listens for link clicks][link-listener] and will route them client-side if the link destination is determined to be internal. You can configure which hostnames are considered internal in [`lib/config`][lib-config]. 138 | 139 | ## Data fetching 140 | 141 | Next.js is optimized to create performant pages that are statically generated at build time ([`getStaticProps`][nextjs-gsp]) or server-side-rendered at request time ([`getServerSideProps`][nextjs-gssp]). This results in HTML that is cacheable at the edge and immediately crawlable by search engines—both critically important factors in the performance and success of your site. 142 | 143 | This boilerplate uses [Apollo][apollo] to query for data using GraphQL, and it is configured and ready to use. Note that Apollo hooks (e.g., `useQuery`) are not compatible with `getStaticProps` or `getServerSideProps`. 144 | 145 | ### Client-side data fetching 146 | 147 | Many Apollo implementations, including Next.js’s official examples, implement a complex, isomorphic approach that bootstraps and hydrates the data from the server-side render into an in-memory cache, where it can be used for client-side requests. We have intentionally avoided this approach because it introduces a large performance penalty and increases the risk that performance degrades even more over time. 148 | 149 | Before adding client-side data fetching, examine your typical user flows in detail and consider whether it truly benefits your application and its users. Skipping this complicated step simplifies your configuration, decreases page weight, and usually increases overall performance. If you absolutely need to perform client-side data fetching, an `ApolloProvider` is exported and ready to use in [`graphql-provider`][apollo-provider]. Note that data from the server-side render will not be hydrated into the store. 150 | 151 | ### Code generation 152 | 153 | Our boilerplate has a code generation step that examines the GraphQL queries in `./graphql/queries/`, introspects your GraphQL schema, and generates TypeScript code that can be used to load data via Apollo. See [`LatestContent`][latest-content] for an example of using generated code with `getStaticProps` and [`Post`][post] for an example with `getServerSideProps`. 154 | 155 | Having declared types across the entire scope of data fetching chain—queries, responses, and React component props—is incredibly powerful and provides confidence as you build your site. Code generation runs automatically on all GraphQL queries in `./graphql/queries/` whenever you start the development server or build the application. If you need to run it manually, you can use: 156 | 157 | ```sh 158 | npm run codegen 159 | ``` 160 | 161 | In development, if you make changes or additions to your queries, you will need to restart the development server to see those changes reflected. 162 | 163 | ## Caching 164 | 165 | Responses from Next.js are cached by [VIP's page cache][page-cache] for five minutes by default, [overriding the default behavior of Next.js][cache-config] to help avoid invalidation issues. 166 | 167 | As `POST` requests, GraphQL queries are not cached. However, when using static or server-side data loading—which is strongly recommended—these queries are effectively cached by the page cache. 168 | 169 | ### Redis 170 | 171 | If you have Redis deployed alongside your application hosted on VIP Go, you can cache API responses and other data there. An example preconfigured to work on VIP is provided at `./pages/api/books.ts`. 172 | 173 | ## Next.js middleware and edge runtime 174 | 175 | Next.js offers a way to implement [middleware][middleware], which can be a great way to separate logic from presentation. Additionally, this middleware can target an ["edge runtime"][edge-runtime] on some platforms (for example, Vercel’s "edge functions"). Middleware is supported on VIP, but will not run at the edge. Instead, middleware runs on your origin servers with the rest of your application. 176 | 177 | This project uses middleware (`.pages/_middleware.ts`) to implement the [healthcheck endpoint][healthcheck] required by WordPress VIP's platform. Middleware is a great way to solve for use cases that would otherwise require a [custom server][nextjs-custom-server]. 178 | 179 | ## Serverless functions 180 | 181 | "Serverless" functions that live in `/pages/api/` and target Node.js (via JavaScript or TypeScript) are supported on VIP. Just like middleware, they will run on your origin servers. 182 | 183 | ## Sitemap and syndication (RSS) feeds 184 | 185 | WordPress has provided a default sitemap since version 5.5, and there are many plugins that provide this functionality as well. Rather than recreate sitemaps in Next.js, we recommend that you point to the sitemaps produced by WordPress. If the permalinks generated by WordPress point to your decoupled front-end (and they should!), this will work seamlessly. Google and other search engines will happily crawl [sitemaps on other domains][sitemap-google], provided you point them there. The `robots.txt` provided by this boilerplate does just that (see `./pages/api/robots.ts`). 186 | 187 | Feeds can be linked by passing a `feedLink` prop to the `Page` component. Again, we recommend pointing to the feeds that are already being generated by WordPress. 188 | 189 | ## TypeScript 190 | 191 | This boilerplate is written in [TypeScript][typescript]. Next.js has [built-in support for TypeScript][nextjs-ts] and processes it automatically in both development and production. If you're already proficient in TypeScript, see [`tsConfig.json`][ts-config] for details. 192 | 193 | You don’t need to use TypeScript to use this boilerplate: our `tsConfig.json` is lenient and allows you to write code in either TypeScript or JavaScript. 194 | 195 | ## Linting 196 | 197 | Next.js provides an [ESLint integration][nextjs-eslint], which means you don't need to separately install `eslint` packages. This boilerplate provides an [ESLint config][eslint-config] based on the default Next.js ESLint rules, which can be integrated with most code editors. To run linting manually, use: 198 | 199 | ```sh 200 | npm run lint 201 | ``` 202 | 203 | Many linting issues can be fixed automatically with: 204 | 205 | ```sh 206 | npm run lint:fix 207 | ``` 208 | 209 | ## Tests 210 | 211 | There is support for tests using [Jest][jest]. Some basic unit tests are provided for boilerplate code and carry a `.test.ts` extension. Run tests using: 212 | 213 | ```sh 214 | npm test 215 | ``` 216 | 217 | ## URL imports 218 | 219 | URL imports allow you to import packages or images directly from URLs instead of from local disk. At this time, the feature is not stable, introduces security risk, and is not recommended. 220 | 221 | ## Image optimization 222 | 223 | The Next.js `Image` component, [next/image][nextjs-image], is an extension of the HTML `` element, evolved for the modern web. It includes a variety of built-in performance optimizations. Next.js will automatically determine the width and height of your image based on the imported file. 224 | 225 | For the API images, the `srcSet` property is automatically defined by the `deviceSizes` and `imageSizes` properties added to the `next.config.js` file. If you need to manually set the `srcSet` for a particular image, you should use the `` HTML tag instead. 226 | 227 | ## Breaking changes from earlier Next.js versions 228 | 229 | - Webpack 4 support has been removed. See the [Webpack 5 upgrade documentation][webpack5] for more information. 230 | - The `target` option has been deprecated. If you are currently using the `target` option set to `serverless`, please read the [documentation on how to leverage the new output][output-file-tracing]. 231 | - Next.js `Image` component changed its wrapping element. See the [documentation][image-optimization] for more information. 232 | - The minimum Node.js version has been bumped from `12.0.0` to `12.22.0` which is the first version of Node.js with native ES Modules support. 233 | 234 | 235 | [apollo]: https://www.apollographql.com 236 | [apollo-provider]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/graphql/apollo-provider.tsx 237 | [block-attributes]: https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/ 238 | [bundle]: https://github.com/Automattic/vip-decoupled-bundle 239 | [cache-config]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/next.config.js#L34-L51 240 | [classic-editor]: https://wordpress.com/support/classic-editor-guide/ 241 | [code-generation]: https://www.graphql-code-generator.com 242 | [content-blocks]: https://github.com/Automattic/vip-decoupled-bundle/blob/trunk/blocks/blocks.php 243 | [edge-runtime]: https://nextjs.org/docs/api-reference/edge-runtime 244 | [eslint-config]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/.eslintrc 245 | [feed-redirect]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/next.config.js#L95-L100 246 | [gutenberg]: https://developer.wordpress.org/block-editor/ 247 | [healthcheck]: https://docs.wpvip.com/technical-references/vip-platform/node-js/#h-requirement-1-exposing-a-health-route 248 | [image-optimization]: https://nextjs.org/docs/basic-features/image-optimization#styling 249 | [jest]: https://jestjs.io 250 | [latest-content]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/pages/latest/%5Bcontent_type%5D.tsx 251 | [lib-config]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/lib/config.ts 252 | [link-listener]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/lib/hooks/useInternalLinkRouting.ts 253 | [local-nextjs]: http://localhost:3000 254 | [local-wordpress]: http://localhost:8888/wp-admin 255 | [middleware]: https://nextjs.org/docs/middleware 256 | [nextjs]: https://nextjs.org 257 | [nextjs-custom-server]: https://nextjs.org/docs/advanced-features/custom-server 258 | [nextjs-eslint]: https://nextjs.org/docs/basic-features/eslint 259 | [nextjs-gsp]: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation 260 | [nextjs-gssp]: https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering 261 | [nextjs-image]: https://nextjs.org/docs/api-reference/next/image 262 | [nextjs-link]: https://nextjs.org/docs/api-reference/next/link 263 | [nextjs-ts]: https://nextjs.org/docs/basic-features/typescript 264 | [output-file-tracing]: https://nextjs.org/docs/advanced-features/output-file-tracing 265 | [page-cache]: https://docs.wpvip.com/technical-references/caching/page-cache/ 266 | [parse-blocks]: https://github.com/WordPress/wordpress-develop/blob/5.8.1/src/wp-includes/blocks.php#L879-L891 267 | [post]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/pages/%5B...slug%5D.tsx 268 | [post-content]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/components/PostContent/PostContent.tsx 269 | [sitemap-google]: https://developers.google.com/search/docs/advanced/robots/robots_txt#sitemap 270 | [ts-config]: https://github.com/Automattic/vip-go-nextjs-skeleton/blob/725c0695ad603d2ecc8b56ff1c9f1cad95f5fe98/tsconfig.json 271 | [typescript]: https://www.typescriptlang.org 272 | [webpack5]: https://nextjs.org/docs/messages/webpack5 273 | [wpenv]: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/ 274 | [wpenv-credentials]: https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/#starting-the-environment 275 | [wpgraphql]: https://www.wpgraphql.com 276 | [wpvip]: https://wpvip.com 277 | [permalinks-plain]: https://wordpress.org/support/article/using-permalinks/#plain-permalinks 278 | [permalinks-setup]: https://wordpress.org/support/article/using-permalinks/#choosing-your-permalink-structure-1 279 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "${NEXT_PUBLIC_GRAPHQL_ENDPOINT}" 3 | documents: "graphql/**/*.graphql" 4 | generates: 5 | graphql/generated/index.tsx: 6 | plugins: 7 | - "typescript" 8 | - "typescript-operations" 9 | # https://www.graphql-code-generator.com/docs/plugins/typescript-react-apollo 10 | - "typescript-react-apollo" 11 | graphql/generated/fragmentMatcher.ts: 12 | plugins: 13 | - "fragment-matcher" 14 | -------------------------------------------------------------------------------- /components/Blocks/ClassicEditorBlock/ClassicEditorBlock.tsx: -------------------------------------------------------------------------------- 1 | import { BlockProps } from '../index'; 2 | 3 | export default function ClassicEditorBlock ( { block: { innerHTML } }: BlockProps ) { 4 | return
; 5 | } 6 | -------------------------------------------------------------------------------- /components/Blocks/Heading/Heading.tsx: -------------------------------------------------------------------------------- 1 | import { BlockProps } from '../index'; 2 | 3 | export default function Heading ( { block: { innerHTML } }: BlockProps ) { 4 | return

; 5 | } 6 | -------------------------------------------------------------------------------- /components/Blocks/ImageBlock/ImageBlock.tsx: -------------------------------------------------------------------------------- 1 | import { BlockProps } from '../index'; 2 | import Image from '@/components/Image/Image'; 3 | 4 | type Props = BlockProps & { 5 | alt: string, 6 | src: string, 7 | }; 8 | 9 | export default function ImageBlock ( props : Props ) { 10 | const { block: _omit, ...imageProps } = props; 11 | 12 | return {props.alt} 13 | } 14 | -------------------------------------------------------------------------------- /components/Blocks/List/List.tsx: -------------------------------------------------------------------------------- 1 | import { BlockProps } from '../index'; 2 | 3 | type Props = BlockProps & { 4 | ordered?: boolean, 5 | reversed?: boolean, 6 | start?: number, 7 | }; 8 | 9 | export default function List ( { block: { innerHTML }, ...props }: Props ) { 10 | if ( props.ordered ) { 11 | return ( 12 |
    17 | ); 18 | } 19 | 20 | return ( 21 |