How do I manage breadcrumbs when the node uses multiple vocabularies?
Here's my solution. Keep in mind that I'm very new to Drupal, so this might be a bit over-complicated, but hopefully it gives someone with a similar problem some direction. I will likely refactor this class in upcoming days, but right now I just need a break from all this mess.
function [YOUR-MODULE-NAME]_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &amp;$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {

  // Control Blog Post Breadcrumb Taxonomy Trail
  $blogPostBreadcrumb = new BlogPostBreadcrumb();
  $blogPostBreadcrumb-&gt;alter($breadcrumb);
}



use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Entity\EntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Exception;
use Symfony\Component\HttpFoundation\Request;

/**
 * Class BlogPostBreadcrumb
 *
 * The purpose of this class is to control Blog post breadcrumbs, as described here:
 * 
 * RULE 1:
 * When the user is on the Tag page (Home &gt; Blog &gt; Tag) and clicks on a blog post, the blog post breadcrumbs will
 * display: Home &gt; Blog &gt; [Tag Name] &gt; Blog Post Title
 *
 * RULE 2:
 * When the user is on the Category page (Home &gt; Blog &gt; Category) and clicks on a blog post, the blog post breadcrumbs
 * will display: Home &gt; Blog &gt; [Category Name] &gt; Blog Post Title
 *
 * RULE 3:
 * When the user loads the blog post directly (i.e. Home &gt; Blog, or comes from other sources unrelated to
 * Category or Tag), the blog post breadcrumbs should default to Category:
 * Home &gt; Blog &gt; [Category Name] &gt; Blog Post Title
 *
 */
class BlogPostBreadcrumb
{
  /**
   * @param Breadcrumb $breadcrumb
   */
  public function alter(Breadcrumb &amp;$breadcrumb): void
  {
    try {
      // current node
      $current_node = $this-&gt;currentNode();

      // nothing found, nothing to do
      if (empty($current_node)) {
        return;
      }

      // what is the current content type
      $content_type = $this-&gt;getField($current_node, 'type');

      // apply rule only to blog_post content type, otherwise let's stop the madness right here
      if ($content_type !== 'blog_post') {
        return;
      }

      // determine if the tag can be extracted from referrer. If not, default it to college_tag
      $altered_breadcrumb = $this-&gt;getTaxonomyLabelAndUrlByReferrer()
        ?? $this-&gt;getTaxonomyLabelAndUrlByCollege($current_node);

      // nothing matched - just let it be. Display blog_tags rather than nothing.
      if (empty($altered_breadcrumb)) {
        return;
      }

      // modify existing breadcrumb list by replacing the third link with proper tag
      // (Home &gt; Blog &gt; [college_tag/blog_tag] &gt; Blog Title)
      $breadcrumb-&gt;getLinks()[2]-&gt;setText($altered_breadcrumb['label']);
      $breadcrumb-&gt;getLinks()[2]-&gt;setUrl($altered_breadcrumb['url']);

      // we have to disable caching, otherwise whichever taxonomy opens first, it will get cached!
      // load menu_link_content entity
      $entity =  \Drupal::entityTypeManager()
        -&gt;getStorage('menu_link_content')
        -&gt;load($current_node-&gt;getEntityTypeId());
      // disable caching
      $breadcrumb-&gt;addCacheableDependency($entity);
    } catch (Exception $e) {
      // if any exception is thrown, we don't want to stop the page from loading.
      // Worse case scenario, it will load blog_tags into breadcrumbs.
    }

  }

  /**
   * Get Field from node
   * Helper method for retrieving field value from given node.
   *
   * @param EntityInterface $node
   * @param string $fieldName
   * @return mixed|null
   */
  protected function getField(EntityInterface $node, string $fieldName)
  {
    return $node-&gt;get($fieldName)-&gt;getValue()[0]['target_id'] ?? null;
  }

  /**
   * Load node from current url
   *
   * @return EntityBase|null
   */
  protected function currentNode():? EntityBase
  {
    if ($nid = \Drupal::routeMatch()-&gt;getParameter('node')) {
      return Node::load($nid-&gt;id()) ?? null;
    }
    return null;
  }

  /**
   * Determine Taxonomy Breadcrumb Label and Url by referrer
   * We want to display Breadcrumb category depending from where the user is coming from:
   * Blog College (category) or Blog tag page
   *
   * @return array|null
   */
  protected function getTaxonomyLabelAndUrlByReferrer():? array
  {
    // capture referrer the &quot;Drupal way&quot;
    $route_parameters = $this-&gt;referrer();

    // if type is not a taxonomy term, it's not used to us
    if (empty($route_parameters['taxonomy_term'])) {
      return null;
    }

    // load node (taxonomy term)
    $term = Term::load($route_parameters['taxonomy_term']);

    // get taxonomy type
    $taxonomy = $this-&gt;getField($term, 'vid');

    // check if taxonomy belongs to college_tags or blog_tags
    if (empty($taxonomy) &amp;&amp; !in_array($taxonomy, ['college_tags', 'blog_tags'])) {
      return null;
    }

    // if we can't find the url or the label
    if (empty($term-&gt;url() || empty($term-&gt;label()))) {
      return null;
    }

    return [
      'label' =&gt; $term-&gt;label(),
      'url' =&gt; Url::fromUserInput($term-&gt;url())
    ];
  }

  /**
   * Determine Taxonomy Breadcrumb Label and Url by college
   * This method will be used when referrer page doesn't exist, or the referrer is not Blog Category or Blog Tag page
   *
   * @param EntityInterface $node
   * @return array|null
   */
  protected function getTaxonomyLabelAndUrlByCollege(EntityInterface $node):? array
  {
    // blog can have one college tag (category)
    $taxonomy_id = $this-&gt;getField($node, 'field_college');
    $term = Term::load($taxonomy_id);

    // if we can't find the url or the label
    if (empty($term-&gt;url() || empty($term-&gt;label()))) {
      return null;
    }

    return [
      'label' =&gt; $term-&gt;label(),
      'url' =&gt; Url::fromUserInput($term-&gt;url())
    ];
  }

  /**
   * Referrer node
   * Load referrer node the &quot;drupal way&quot;
   *
   * @return mixed
   */
  protected function referrer()
  {
    // this would be college_tag or blog_tag page
    $previousUrl = \Drupal::request()-&gt;server-&gt;get('HTTP_REFERER');
    // fake the request to previous page, so we can get more info
    $fake_request = Request::create($previousUrl);
    // build url object.
    $url_object = \Drupal::service('path.validator')-&gt;getUrlIfValid($fake_request-&gt;getRequestUri());
    if ($url_object) {
      return $url_object-&gt;getRouteParameters();
    } else {
      return null;
    }
  }
}