如何构建一个近乎无头化的 WordPress 网站

Avatar of Alex Standiford
Alex Standiford

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

我相信传统的 WordPress 主题应该能够像静态网站或无头 Web 应用一样有效地工作。绝大多数 WordPress 网站都是使用传统的 WordPress 主题构建的。其中大多数甚至具有良好的缓存层和依赖项优化,使这些网站运行速度相当快。但作为开发人员,我们已经实现了创建网站更好结果的方法。使用无头 WordPress 使许多网站能够获得更快的加载速度、更好的用户交互以及页面之间无缝的过渡。

问题?维护。让我向您展示另一种可能性!

让我们首先定义“传统”WordPress、“无头”WordPress,然后是“近乎无头”WordPress 的含义。

传统的 WordPress 网站

传统上,WordPress 网站使用 PHP 构建,以渲染页面上呈现的 HTML 标记。每次单击链接时,浏览器都会向服务器发送另一个请求,PHP 会渲染单击的网站的 HTML 标记。

这是大多数网站使用的方法。它最易于维护,技术复杂性最低,并且使用正确的服务器端缓存工具,它可以执行得相当好。问题是,由于它是一个传统网站,它感觉就像一个传统网站。在这种类型的网站中,过渡、效果和其他时尚的现代功能往往更难以构建和维护。

优点

  1. 网站易于维护。
  2. 技术相对简单。
  3. 与 WordPress 插件具有极佳的兼容性。

缺点

  1. 由于社会期望在浏览器中获得类似应用程序的体验,因此您的网站可能会显得有点过时。
  2. 由于该网站没有使用 JavaScript 框架来控制网站的行为,因此 JavaScript 往往有点难以编写和维护。
  3. 传统网站的运行速度往往比无头和近乎无头的选项慢。

无头 WordPress 网站

无头 WordPress 网站使用现代 JavaScript 和某种服务器端 RESTful 服务,例如 WordPress REST API 或 GraphQL。服务器不是在 PHP 中构建和渲染 HTML,而是发送最少的 HTML 和一个大型的 JavaScript 文件,该文件可以处理渲染网站上的任何页面。此方法可以更快地加载页面,并有机会在页面之间创建真正很酷的过渡和其他有趣的事情。

无论您如何解释,大多数无头 WordPress 网站都需要一名开发人员随时准备对网站进行任何重大更改。想要安装表单插件?抱歉,您可能需要开发人员来设置它。想要安装新的 SEO 插件?不,需要开发人员来更改应用程序。想使用那个花哨的区块?太糟糕了——您首先需要一个开发人员。

优点

  1. 网站本身会感觉现代且快速。
  2. 它易于与 WordPress 之外的其他 RESTful 服务集成。
  3. 整个网站都使用 JavaScript 构建,这使得构建复杂的网站变得更容易。

缺点

  1. 您必须重新发明 WordPress 插件为您开箱即用提供的许多功能。
  2. 这种设置难以维护。
  3. 与其他选项相比,托管复杂且可能变得昂贵。

请参阅 “WordPress 和 Jamstack”,以更深入地了解 WordPress 和静态托管之间的差异。

我喜欢无头 WordPress 可以创建的结果。我不喜欢维护。我想要一个 Web 应用,它允许我获得快速的加载速度、页面之间的过渡以及网站的整体类似应用程序的感觉。但我也希望能够自由地使用使 WordPress 如此流行的插件生态系统。我想要一些无头化的东西。近乎无头

我找不到任何符合此描述的内容,因此我构建了一个。从那时起,我已经构建了几个使用此方法的网站,并构建了必要的 JavaScript 库,以便其他人更容易创建自己的近乎无头 WordPress 主题。

介绍近乎无头 WordPress

近乎无头是一种 WordPress 的 Web 开发方法,它为您提供了无头方法带来的许多类似应用程序的优势,以及使用传统 WordPress 主题带来的易于开发的优势。它通过一个小型 JavaScript 应用来实现这一点,该应用将处理路由并像无头应用一样渲染您的网站,但具有回退功能,可以加载具有普通 WordPress 请求的完全相同的页面。您可以选择使用回退方法加载哪些页面,并且可以在 JavaScript 或 PHP 中注入逻辑以确定是否应以这种方式加载页面。

您可以在我构建的 演示站点 上看到它的实际效果,该演示站点展示了这种方法可以实现的功能。

例如,一个实现此方法的网站使用名为 LifterLMS 的学习管理系统在线销售 WordPress 课程。此插件具有内置的电子商务功能,并设置了托管课程内容并将其置于付费墙后的必要界面。此网站使用大量LifterLMS 的内置功能来工作——其中很大一部分是结账购物车。与其重新构建整个页面以在我的应用内工作,我只需将其设置为使用回退方法加载。因此,此页面就像任何旧的 WordPress 主题一样工作,并且因此完全按预期工作——所有这些都无需我重新构建任何内容。

优点

  1. 设置后,这很容易维护。
  2. 托管就像典型的 WordPress 主题一样简单。
  3. 网站感觉与无头网站一样现代且快速。

缺点

  1. 您始终必须考虑两种不同的渲染网站的方法。
  2. 使用这种方法的 JavaScript 库的选择有限。
  3. 此应用与 WordPress 紧密绑定,因此使用第三方 REST API 比无头更困难。

它是如何工作的

为了使某些内容近乎无头,它需要能够执行多项操作,包括

  1. 使用 WordPress 请求加载页面,
  2. 使用 JavaScript 加载页面,
  3. 允许页面相同,无论它们如何呈现,
  4. 提供一种方法来知道何时使用 JavaScript 或 PHP 加载页面,以及
  5. 确保所有路由页面的 100% 平等性,无论它是使用 JavaScript 还是 PHP 呈现。

这允许网站利用渐进增强。由于页面可以使用或不使用 JavaScript 查看,因此您可以根据发出的请求使用最合理的一个版本。是否有可信的机器人正在抓取您的网站?向他们发送非 JavaScript 版本以确保兼容性。是否有结账页面未按预期工作?暂时强制它在没有应用的情况下加载,以后再修复。

为了完成这些项目中的每一个,我发布了一个名为 Nicholas 的开源库,其中包括一个 预制样板

保持 DRY

在构建近乎无头的应用时,我最想克服的最大问题是保持 PHP 和 JavaScript 中页面渲染方式的一致性。我不想在两个不同的地方构建和维护我的标记——我希望尽可能多地使用单个来源的标记。这立即限制了我可以实际使用的 JavaScript 库(对不起,React!)。经过一些研究和大量的实验,我最终使用了 AlpineJS。此库使我的代码保持了合理的 DRY。有些部分绝对必须为每个部分重新编写(例如循环),但大多数重要的标记块可以重复使用。

使用 PHP 渲染的单个帖子模板可能如下所示

<?php
if ( have_posts() ) {
  while ( have_posts() ) {
    the_post();
    if ( is_singular() ) {
      echo nicholas()->templates()->get_template( 'index', 'post', [
        'content' => Nicholas::get_buffer( 'the_content' ),
        'title'   => Nicholas::get_buffer( 'the_title' ),
      ] );
    }
  }
}
?>

使用 Alpine 在 JavaScript 中渲染的相同帖子模板

<template x-for="(post, index) in $store.posts" :key="index">
  <?= nicholas()->templates()->get_template( 'index', 'post' ) ?>
</template>

它们都使用相同的 PHP 模板,因此实际循环内部的所有代码都是 DRY 的

$title   = $template->get_param( 'title', '' );

// Get the title that was passed into this template, fallback to empty string.
$content = $template->get_param( 'content', '' ); // Get the cotent passed into this template, fallback to empty string.

?>
<article x-data="theme.Post(index)">
  <!-- This will use the alpine directive to render the title, or if it's in compatibility mode PHP will fill in the title directly -->
  <h1 x-html="title"><?= $title ?></h1>
  <!-- This will use the Alpine directive to render the post content, or if it's in compatibility mode, PHP will fill in the content directly -->
  <div class="content" x-html="content"><?= $content ?></div>
</article>

相关:这种 Alpine.js 方法在精神上类似于 Jonathan Land 在 “如何在 WordPress 主题中构建 Vue 组件” 中介绍的 Vue.js 方法。

检测页面何时应该以兼容模式运行

“兼容模式”允许您强制任何请求加载*无需*运行网站无头版本的 JavaScript。当页面设置为使用兼容模式加载时,页面将仅使用 PHP 加载,并且应用程序脚本永远不会被加入队列。这允许“问题页面”在无需重写任何内容的情况下运行,而这些页面在应用程序中无法按预期工作。

您可以通过多种不同的方式强制页面以兼容模式运行——有些需要代码,有些则不需要。Nicholas 为任何帖子类型添加了一个切换按钮,可以强制帖子以兼容模式加载。

除此之外,您还可以手动将任何 URL 添加到 Nicholas 设置中,以强制其以兼容模式加载。

这些是一个很好的开始,但我发现我通常可以根据存储在帖子中的块自动检测页面何时需要以兼容模式加载。例如,假设您在您的网站上安装了Ninja Forms,并且您想使用他们提供的验证 JavaScript 而不是重新创建自己的验证。在这种情况下,您必须在任何包含 Ninja Forms 的页面上强制使用兼容模式。您*可以*手动遍历并根据需要添加每个 URL,或者您可以使用查询获取页面上所有包含 Ninja Forms 块的内容。例如:

add_filter( 'nicholas/compatibility_mode_urls', function ( $urls ) {
  // Filter Ninja Forms Blocks
  $filtered_urls = Nicholas::get_urls_for_query( [
    'post_type' => 'any',
    's' => 'wp:ninja-forms/form', // Find Ninja Forms Blocks
  ] );

  return array_merge( $urls, $filtered_urls );
} );

这会自动将任何包含 Ninja Forms 块的页面添加到将使用兼容模式加载的 URL 列表中。这只是使用 WP_Query 参数,因此您可以在这里传递任何您想要的内容来确定应将哪些内容添加到列表中。

扩展应用程序

在幕后,Nicholas 使用一个轻量级的路由器,它可以使用中间件模式进行扩展,这与 Express 应用程序如何处理中间件非常相似。当点击的页面被路由时,系统会运行每个中间件项,最终路由页面。默认情况下,路由器*不做任何事情*;但是,它带有一些预制的中间件组件,允许您根据需要组装路由器。

一个基本的例子如下所示:

// Import WordPress-specific middleware
import {
  updateAdminBar,
  validateAdminPage,
  validateCompatibilityMode
} from 'nicholas-wp/middlewares'

// Import generic middleware
import {
  addRouteActions,
  handleClickMiddleware,
  setupRouter,
  validateMiddleware
} from "nicholas-router";

// Do these actions, in this order, when a page is routed.
addRouteActions(
  // First, validate the URL
  validateMiddleware,
  // Validate this page is not an admin page
  validateAdminPage,
  // Validate this page doesn't require compatibility mode
  validateCompatibilityMode,
  // Then, we Update the Alpine store
  updateStore,
  // Maybe fetch comments, if enabled
  fetchComments,
  // Update the history
  updateHistory,
  // Maybe update the admin bar
  updateAdminBar
)

// Set up the router. This also uses a middleware pattern.
setupRouter(
  // Setup event listener for clicks
  handleClickMiddleware
)

从这里,您可以扩展页面路由时发生的事情。也许您想扫描页面以查找要突出显示的代码,或者您可能想要更改<head>标签的内容以匹配新路由的页面。甚至可以引入一个缓存层。无论您需要做什么,添加所需的 action 都与使用addRouteActionsetupRouter一样简单。

下一步

这是我对实现近乎无头方法使用的一些关键组件的简要概述。如果您有兴趣深入了解,我建议您参加我在 WP Dev Academy 的课程。本课程是有关如何使用现代工具构建近乎无头的 WordPress 网站的分步指南。我还建议您查看我的近乎无头的样板,它可以帮助您开始自己的项目。