概要

Hugoで作成しているブログサイトについて、記載されているリンクについてリンクカードを表示するようにしました。

その方法を紹介します。

環境

  • Hugo v0.158.0
  • hugo-PaperMod v0.8.0

やりたいこと

静的サイトジェネレータであるHugoを利用してブログをやっています。 そのブログの記事について、時々URLリンクを載せることがあります。

使っているテーマはPaperModです。 そのテーマでは、URLリンクを記載した時に、ただそのURLが表示されるだけです。 デザイン的に、これでは微妙です。

そのため、コードを追加してリンクカードのようなものを表示するようにします。

今回紹介する方法は、テーマの相性もありますがPaperMod以外のテーマでも動作すると思います。

リンクカードの表示方法

Hugoでは、コードを追加することで、HTML生成時の処理をカスタマイズできます。 このカスタマイズでは、テーマとの相性があります。 すでにテーマでカスタマイズの処理が記述されている場合、その処理を上書きすることになるため、注意が必要です。

layouts/_default/_markup/render-link.htmlという名前でファイルを配置します。

{{- $u := urls.Parse .Destination -}}
{{- $isExternal := $u.IsAbs -}}
{{- $isInternal := false -}}
{{- $linkedPage := "" -}}

{{- /* 内部リンクかどうかをチェック */ -}}
{{- if not $isExternal -}}
  {{- $linkedPage = .Page.GetPage .Destination -}}
  {{- if $linkedPage -}}
    {{- $isInternal = true -}}
  {{- end -}}
{{- end -}}

{{- /* リンクカードとして表示するかどうかを判定 */ -}}
{{- $isLinkCard := false -}}
{{- if $isExternal -}}
  {{- /* 外部リンクは常にカード表示 */ -}}
  {{- $isLinkCard = true -}}
{{- else if and $isInternal (not (hasPrefix .Text "nocard:")) -}}
  {{- $isLinkCard = true -}}
{{- end -}}

{{- if and $isLinkCard $isInternal -}}
  {{- /* 内部リンクのカード表示 */ -}}
  <div class="link-card internal-link-card">
    <a href="{{ .Destination | safeURL }}" class="link-card-anchor">
      {{- with $linkedPage.Params.images -}}
        {{- if index . 0 -}}
          <div class="link-card-image">
            <img src="{{ index . 0 | absURL }}" alt="{{ $linkedPage.Title }}" loading="lazy">
          </div>
        {{- end -}}
      {{- end -}}
      <div class="link-card-content">
        <div class="link-card-title">{{ $linkedPage.Title }}</div>
        {{- with $linkedPage.Params.description -}}
          <div class="link-card-description">{{ . | plainify | truncate 100 }}</div>
        {{- else -}}
          {{- with $linkedPage.Description -}}
            <div class="link-card-description">{{ . | plainify | truncate 100 }}</div>
          {{- end -}}
        {{- end -}}
        <div class="link-card-meta">
          <span class="link-card-host">{{ site.Title }}</span>
          {{- with $linkedPage.Date -}}
            <span class="link-card-date">{{ .Format "2006.01.02" }}</span>
          {{- end -}}
        </div>
      </div>
    </a>
  </div>
{{- else if and $isLinkCard $isExternal -}}
  {{- /* 外部リンクのカード表示 */ -}}
  <div class="link-card external-link-card">
    <a href="{{ .Destination | safeURL }}" class="link-card-anchor" target="_blank" rel="noopener">
      <div class="link-card-content">
        <div class="link-card-title">{{ .Text | safeHTML }}</div>
        <div class="link-card-url">{{ .Destination | safeURL }}</div>
        <div class="link-card-meta">
          <span class="link-card-host">{{ $u.Hostname }}</span>
          <span class="link-card-external-icon">🔗</span>
        </div>
      </div>
    </a>
  </div>
{{- else -}}
  {{- /* 通常のリンク */ -}}
  <a href="{{ .Destination | safeURL }}" {{ with .Title}} title="{{ . }}"
    {{ end }}{{ if $isExternal }} target="_blank" rel="noopener"
    {{ end }}>{{ .Text | safeHTML }}</a>
{{- end -}}

リンクカードの表示について、サイト内のリンクか、サイト外のリンクかで処理を変更しています。 これは、リンクカードに表示する内容を決定するために行っています。 サイト外のリンクでは、表示する情報をHTML生成の時に取得して表示するということが難しかったので、Markdownでのリンクの記述内容を元に表示しています。 サイト内のリンクでは、記事内容が書かれたMarkdownファイルの内容を参照できるので、そのファイルの内容を元に情報を表示しています。

さらに、リンクカードのデザインを設定するために、assets/css/custom.cssにスタイルを追加します。

/* リンクカードのスタイル */
.link-card {
    margin: 1.5rem 0;
    border: 1px solid #e1e4e8;
    border-radius: 8px;
    overflow: hidden;
    transition: box-shadow 0.2s ease;
}

.link-card:hover {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

.link-card-anchor {
    display: flex;
    text-decoration: none;
    color: inherit;
    min-height: 120px;
}

.link-card-anchor:hover {
    text-decoration: none;
}

/* 内部リンクカード */
.internal-link-card .link-card-image {
    flex-shrink: 0;
    width: 200px;
    overflow: hidden;
    background-color: #f6f8fa;
}

.internal-link-card .link-card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.internal-link-card .link-card-content {
    flex: 1;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

/* 外部リンクカード */
.external-link-card .link-card-content {
    padding: 1rem;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.link-card-title {
    font-size: 1.1rem;
    font-weight: 600;
    line-height: 1.4;
    margin-bottom: 0.5rem;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.link-card-description {
    font-size: 0.9rem;
    color: #586069;
    line-height: 1.5;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    margin-bottom: 0.5rem;
}

.link-card-url {
    font-size: 0.85rem;
    color: #0366d6;
    margin-bottom: 0.5rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.link-card-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.85rem;
    color: #6a737d;
}

.link-card-host {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.link-card-external-icon {
    margin-left: 0.5rem;
}

/* モバイル対応 */
@media (max-width: 640px) {
    .link-card-anchor {
        flex-direction: column;
    }

    .internal-link-card .link-card-image {
        width: 100%;
        height: 180px;
    }

    .link-card-content {
        padding: 0.75rem;
    }
}

/* ダークモード対応 */
.dark .link-card {
    border-color: #30363d;
    background-color: #0d1117;
}

.dark .link-card:hover {
    box-shadow: 0 2px 8px rgba(255, 255, 255, 0.1);
}

.dark .internal-link-card .link-card-image {
    background-color: #161b22;
}

.dark .link-card-description {
    color: #8b949e;
}

.dark .link-card-url {
    color: #58a6ff;
}

.dark .link-card-meta {
    color: #8b949e;
}

CSSのデザインは、あくまでも私の環境に合わせたものです。 お好みに合わせて表示を変更してください。

これで、リンクカードを表示できました。

さいごに

簡単なものですが、Hugoでリンクカードを表示するようにしました。 外部リンクについて、動的に情報を取得して表示するということをやりたかったのですが、それは難しいようです。

もしやるのであれば、JavaScript等を用いて動的に行うという方法があるようです。 その場合は、プロキシのようなことを行う外部サービスがあるので、そのサーバーと通信を行い情報を取得し、それを表示することになります。 外部サービスは基本的に有料サービスとなるため、私はこの方法で実現するのはやめました。

同じようなことを実現したい方の参考になれば嬉しいです。