diff --git a/core/modules/workspaces/src/WorkspacesLazyBuilders.php b/core/modules/workspaces/src/WorkspacesLazyBuilders.php
new file mode 100644
index 0000000000000000000000000000000000000000..03d1f398d36925b403b85f09259af2ff4800dfab
--- /dev/null
+++ b/core/modules/workspaces/src/WorkspacesLazyBuilders.php
@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\workspaces;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Security\TrustedCallbackInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+
+/**
+ * Defines a service for workspaces #lazy_builder callbacks.
+ *
+ * @internal
+ */
+final class WorkspacesLazyBuilders implements TrustedCallbackInterface {
+
+  use StringTranslationTrait;
+
+  public function __construct(
+    protected readonly WorkspaceManagerInterface $workspaceManager,
+    protected readonly ElementInfoManagerInterface $elementInfo,
+  ) {}
+
+  /**
+   * Lazy builder callback for rendering the workspace toolbar tab.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function renderToolbarTab(): array {
+    $active_workspace = $this->workspaceManager->getActiveWorkspace();
+
+    $build = [
+      '#type' => 'link',
+      '#title' => $active_workspace ? $active_workspace->label() : $this->t('Live'),
+      '#url' => Url::fromRoute('entity.workspace.collection', [], ['query' => \Drupal::destination()->getAsArray()]),
+      '#attributes' => [
+        'title' => t('Switch workspace'),
+        'class' => [
+          'toolbar-item',
+          'toolbar-icon',
+          'toolbar-icon-workspace',
+          'use-ajax',
+        ],
+        'data-dialog-type' => 'dialog',
+        'data-dialog-renderer' => 'off_canvas_top',
+        'data-dialog-options' => Json::encode([
+          'height' => 161,
+          'classes' => [
+            'ui-dialog' => 'workspaces-dialog',
+          ],
+        ]),
+      ],
+      '#attached' => [
+        'library' => ['workspaces/drupal.workspaces.toolbar'],
+      ],
+      '#cache' => [
+        'max-age' => 0,
+      ],
+    ];
+
+    // The renderer has already added element defaults by the time the lazy
+    // builder is run.
+    // @see https://www.drupal.org/project/drupal/issues/2609250
+    $build += $this->elementInfo->getInfo('link');
+    return $build;
+  }
+
+  /**
+   * Render callback for the workspace toolbar tab.
+   */
+  public static function removeTabAttributes(array $element): array {
+    unset($element['tab']['#attributes']);
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function trustedCallbacks(): array {
+    return ['removeTabAttributes', 'renderToolbarTab'];
+  }
+
+}
diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php
index 204b148e2aa3906c5c2534ed6d0226130616d921..4c27e4f64a3fcaf7437992a0ccc7d77b2115ad1d 100644
--- a/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php
+++ b/core/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Tests\workspaces\Functional;
 
+use Drupal\Core\Url;
+use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
 
@@ -20,7 +22,12 @@ class WorkspaceSwitcherTest extends BrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['block', 'workspaces'];
+  protected static $modules = [
+    'block',
+    'dynamic_page_cache',
+    'toolbar',
+    'workspaces',
+  ];
 
   /**
    * {@inheritdoc}
@@ -93,4 +100,20 @@ public function testQueryParameterNegotiator() {
     $this->assertCacheContext('session');
   }
 
+  /**
+   * Tests that the toolbar workspace switcher doesn't disable the page cache.
+   */
+  public function testToolbarSwitcherDynamicPageCache() {
+    $this->drupalLogin($this->drupalCreateUser([
+      'access toolbar',
+      'view any workspace',
+    ]));
+    // Front-page is visited right after login.
+    $this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'MISS');
+    // Reload the page, it should be cached now.
+    $this->drupalGet(Url::fromRoute('<front>'));
+    $this->assertSession()->elementExists('css', '.workspaces-toolbar-tab');
+    $this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'HIT');
+  }
+
 }
diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module
index e9a2b3c17e60b41a7cb85caed6a49e12e42283f7..09c91af77ad1f74a32bb69b457c18be3e1a21775 100644
--- a/core/modules/workspaces/workspaces.module
+++ b/core/modules/workspaces/workspaces.module
@@ -5,7 +5,6 @@
  * Provides full-site preview functionality for content staging.
  */
 
-use Drupal\Component\Serialization\Json;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\EntityFormInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -266,29 +265,20 @@ function workspaces_toolbar() {
   $items['workspace'] += [
     '#type' => 'toolbar_item',
     'tab' => [
-      '#type' => 'link',
-      '#title' => $active_workspace ? $active_workspace->label() : t('Live'),
-      '#url' => Url::fromRoute('entity.workspace.collection', [], ['query' => \Drupal::destination()->getAsArray()]),
-      '#attributes' => [
-        'title' => t('Switch workspace'),
-        'class' => ['use-ajax', 'toolbar-icon', 'toolbar-icon-workspace'],
-        'data-dialog-type' => 'dialog',
-        'data-dialog-renderer' => 'off_canvas_top',
-        'data-dialog-options' => Json::encode([
-          'height' => 161,
-          'classes' => [
-            'ui-dialog' => 'workspaces-dialog',
-          ],
-        ]),
+      '#lazy_builder' => ['workspaces.lazy_builders:renderToolbarTab', []],
+      '#create_placeholder' => TRUE,
+      '#lazy_builder_preview' => [
+        '#type' => 'link',
+        '#title' => $active_workspace ? $active_workspace->label() : t('Live'),
+        '#url' => Url::fromRoute('entity.workspace.collection'),
+        '#attributes' => [
+          'class' => ['toolbar-tray-lazy-placeholder-link'],
+        ],
       ],
-      '#cache' => ['tags' => $active_workspace ? $active_workspace->getCacheTags() : []],
     ],
     '#wrapper_attributes' => [
       'class' => ['workspaces-toolbar-tab'],
     ],
-    '#attached' => [
-      'library' => ['workspaces/drupal.workspaces.toolbar'],
-    ],
     '#weight' => 500,
   ];
 
@@ -298,5 +288,13 @@ function workspaces_toolbar() {
     $items['workspace']['#wrapper_attributes']['class'][] = 'workspaces-toolbar-tab--is-default';
   }
 
+  // \Drupal\toolbar\Element\ToolbarItem::preRenderToolbarItem adds an
+  // #attributes property to each toolbar item's tab child automatically.
+  // Lazy builders don't support an #attributes property so we need to
+  // add another render callback to remove the #attributes property. We start by
+  // adding the defaults, and then we append our own pre render callback.
+  $items['workspace'] += \Drupal::service('plugin.manager.element_info')->getInfo('toolbar_item');
+  $items['workspace']['#pre_render'][] = 'workspaces.lazy_builders:removeTabAttributes';
+
   return $items;
 }
diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml
index 9b5f3b97ce8d3c140eaf7fad28e24bf91bb53fa4..cb7cce900691569cf5b8c1809c25b65cb1734a82 100644
--- a/core/modules/workspaces/workspaces.services.yml
+++ b/core/modules/workspaces/workspaces.services.yml
@@ -59,6 +59,11 @@ services:
     parent: logger.channel_base
     arguments: ['workspaces']
 
+  workspaces.lazy_builders:
+    class: Drupal\workspaces\WorkspacesLazyBuilders
+    arguments: [ '@workspaces.manager', '@plugin.manager.element_info' ]
+  Drupal\workspaces\WorkspacesLazyBuilders: '@workspaces.lazy_builders'
+
   workspaces.entity.query.sql:
     decorates: entity.query.sql
     class: Drupal\workspaces\EntityQuery\QueryFactory