Lewati ke konten
Kembali ke Blog

Hugo Shortcodes Lengkap: Membuat Components Reusable

Β· Β· 12 menit baca

Shortcodes adalah fitur powerful di Hugo untuk membuat components reusable dalam content Markdown. Mereka memungkinkan Anda menyisipkan complex HTML/JSX dengan syntax sederhana di Markdown.

Apa itu Shortcodes?

Definisi

Shortcodes adalah snippets template yang bisa dipanggil dari content files untuk menambahkan fungsionalitas atau styling kompleks tanpa menulis HTML langsung di Markdown.

Mengapa Menggunakan Shortcodes?

  1. DRY Principle: Tidak mengulang kode yang sama
  2. Consistency: Format konsisten di seluruh site
  3. Simplicity: Content writers bisa menambahkan complex components
  4. Maintainability: Update satu file, efek global
  5. Security: Escape content secara otomatis

Built-in Shortcodes Hugo

1. figure

Menampilkan gambar dengan caption:

{{ < figure src="/images/photo.jpg" title="Photo Title" caption="Photo description" > }}

2. gist

Embed GitHub Gist:

{{ < gist username gist-id > }}

3. highlight

Syntax highlighting dengan options:

{{ < highlight go "linenos=table,hl_lines=2 3" > }}
package main
func main() {
    fmt.Println("Hello")
}
{{ < /highlight > }}

4. instagram

Embed Instagram post:

{{ < instagram post-id > }}

5. param

Mengakses site parameters:

Site author: {{ < param author > }}

6. ref dan relref

Internal linking dengan validasi:

[Link ke post]({{ < ref "blog/my-post.md" > }})
[Relative link]({{ < relref "my-post.md" > }})

7. tweet

Embed Twitter/X tweet:

{{ < tweet user="username" id="123456789" > }}

8. vimeo dan youtube

Embed video:

{{ < vimeo video-id > }}
{{ < youtube video-id > }}

Membuat Custom Shortcodes

Struktur Folder

layouts/
└── shortcodes/
    β”œβ”€β”€ alert.html        # Single shortcode
    β”œβ”€β”€ figure.html       # Override built-in
    β”œβ”€β”€ button.html
    β”œβ”€β”€ gallery.html
    β”œβ”€β”€ code-block.html
    └── youtube-custom.html

1. Alert Shortcode

File: layouts/shortcodes/alert.html

{{ $type := .Get "type" | default "info" }}
{{ $title := .Get "title" | default "" }}

{{ $colors := dict "info" "bg-blue-50 border-blue-200 text-blue-800" "warning" "bg-yellow-50 border-yellow-200 text-yellow-800" "success" "bg-green-50 border-green-200 text-green-800" "error" "bg-red-50 border-red-200 text-red-800" "tip" "bg-purple-50 border-purple-200 text-purple-800" }}

<div class="rounded-lg border-l-4 p-4 my-6 {{ index $colors $type }}"> {{ with $title }} <h4 class="font-semibold mb-2">{{ . }}</h4> {{ end }} <div class="text-sm"> {{ .Inner | markdownify }} </div> </div>

Penggunaan:

 < alert type="warning" title="Perhatian" > 
Pastikan untuk backup data sebelum melanjutkan.
 < /alert > 

< alert type="tip" > Gunakan Hugo Extended untuk fitur image processing. < /alert >

2. Button Shortcode

File: layouts/shortcodes/button.html

{{ $url := .Get "url" | default "#" }}
{{ $text := .Get "text" | default "Click me" }}
{{ $style := .Get "style" | default "primary" }}
{{ $size := .Get "size" | default "md" }}
{{ $external := .Get "external" | default false }}

{{ $styles := dict "primary" "bg-blue-600 text-white hover:bg-blue-700" "secondary" "bg-gray-200 text-gray-800 hover:bg-gray-300" "outline" "border-2 border-blue-600 text-blue-600 hover:bg-blue-50" "danger" "bg-red-600 text-white hover:bg-red-700" }}

{{ $sizes := dict "sm" "px-3 py-1.5 text-sm" "md" "px-4 py-2 text-base" "lg" "px-6 py-3 text-lg" }}

<a href="{{ $url }}" class="inline-flex items-center rounded-lg font-medium transition-colors {{ index $styles $style }} {{ index $sizes $size }}" {{ if $external }}target="_blank" rel="noopener noreferrer"{{ end }}> {{ $text }} {{ if $external }} <svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path> </svg> {{ end }} </a>

Penggunaan:

{{ < button url="https://gohugo.io" text="Visit Hugo Docs" style="primary" size="lg" external="true" > }}

{{ < button url="/contact" text="Contact Us" style="outline" > }}

3. Gallery Shortcode

File: layouts/shortcodes/gallery.html

{{ $folder := .Get "folder" | default "images/gallery" }}
{{ $match := .Get "match" | default "*" }}

<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 my-8"> {{ $images := .Page.Resources.Match (printf "%s/%s" $folder $match) }} {{ range $images }} <div class="relative aspect-square overflow-hidden rounded-lg group"> {{ $resized := .Resize "400x400" }} <img src="{{ $resized.RelPermalink }}" alt="{{ .Name }}" class="w-full h-full object-cover transition-transform group-hover:scale-110" loading="lazy"> <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-opacity"></div> </div> {{ end }} </div>

Penggunaan:

{{ < gallery folder="images/trip" match="*.jpg" > }}

4. Code Block dengan File

File: layouts/shortcodes/code-file.html

{{ $file := .Get "file" }}
{{ $lang := .Get "lang" | default "text" }}
{{ $title := .Get "title" | default $file }}

{{ $path := printf "%s" $file }} {{ $content := readFile $path }}

<div class="code-block my-6"> <div class="flex items-center justify-between px-4 py-2 bg-gray-800 rounded-t-lg"> <span class="text-gray-300 text-sm font-mono">{{ $title }}</span> <button onclick="navigator.clipboard.writeText(this.nextElementSibling.textContent)" class="text-gray-400 hover:text-white text-sm"> Copy </button> </div> {{ highlight $content $lang "" }} </div>

Penggunaan:

{{ < code-file file="config.toml" lang="toml" title="hugo.toml" > }}

{{ < code-file file="layouts/_default/baseof.html" lang="html" > }}

5. YouTube Custom

File: layouts/shortcodes/youtube-custom.html

{{ $id := .Get "id" }}
{{ $title := .Get "title" | default "YouTube Video" }}
{{ $start := .Get "start" | default 0 }}
{{ $autoplay := .Get "autoplay" | default false }}

<div class="video-container my-8"> <div class="relative w-full" style="padding-bottom: 56.25%;"> <iframe class="absolute inset-0 w-full h-full rounded-lg" src="https://www.youtube.com/embed/{{ $id }}?start={{ $start }}{{ if $autoplay }}&autoplay=1{{ end }}" title="{{ $title }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen> </iframe> </div> {{ with $title }} <p class="text-center text-sm text-gray-600 mt-2">{{ . }}</p> {{ end }} </div>

Penggunaan:

{{ < youtube-custom id="dQw4w9WgXcQ" title="Hugo Tutorial" start="30" > }}

6. Card Component

File: layouts/shortcodes/card.html

{{ $title := .Get "title" }}
{{ $image := .Get "image" }}
{{ $link := .Get "link" | default "#" }}
{{ $style := .Get "style" | default "default" }}

{{ $styles := dict "default" "bg-white" "dark" "bg-gray-800 text-white" "primary" "bg-blue-50 border-blue-200" }}

<a href="{{ $link }}" class="block rounded-xl shadow-md overflow-hidden hover:shadow-xl transition-shadow {{ index $styles $style }}"> {{ with $image }} <img src="{{ . }}" alt="{{ $title }}" class="w-full h-48 object-cover"> {{ end }} <div class="p-6"> <h3 class="text-xl font-semibold mb-2">{{ $title }}</h3> <div class="text-gray-600"> {{ .Inner | markdownify }} </div> </div> </a>

Penggunaan:

{{ < card title="Hugo Tutorial" image="/images/hugo.jpg" link="/tutorials/hugo" > }}
Learn how to build fast static sites with Hugo.
{{ < /card > }}

Parameter Handling

Positional vs Named Parameters

Positional (bisa semua):

<!-- shortcode: {{ < myshortcode param1 param2 > }} -->
{{ $first := .Get 0 }}
{{ $second := .Get 1 }}

Named (lebih readable):

<!-- shortcode: {{ < myshortcode key="value" > }} -->
{{ $value := .Get "key" }}

Mixed:

<!-- {{ < myshortcode "value1" key="value2" > }} -->
{{ $first := .Get 0 }}
{{ $second := .Get "key" }}

Default Values

{{ $title := .Get "title" | default "Untitled" }}
{{ $show := .Get "show" | default true }}
{{ $count := .Get "count" | default 5 }}

Boolean Parameters

{{ $external := false }}
{{ if eq (.Get "external") "true" }}
  {{ $external = true }}
{{ end }}

Inner Content

Simple Shortcodes

{{ < shortcode param="value" > }}

Paired Shortcodes

{{ < shortcode > }}
Inner content here with **markdown** support.
{{ < /shortcode > }}

Template:

<div class="box">
  {{ .Inner | markdownify }}
</div>

Conditional Inner Content

{{ with .Inner }}
  <div class="content">
    {{ . | markdownify }}
  </div>
{{ else }}
  <p class="placeholder">No content provided</p>
{{ end }}

Advanced Shortcodes

1. Tabs Component

File: layouts/shortcodes/tabs.html

<div class="tabs my-6" x-data="{ activeTab: 0 }">
  <div class="flex border-b border-gray-200">
    {{ $tabs := split (.Inner) "<!-- tab -->" }}
    {{ range $index, $tab := after 1 $tabs }}
      {{ $parts := split $tab "<!-- /tab -->" }}
      {{ $title := index (split (index $parts 0) " ") 0 }}
      <button
        @click="activeTab = {{ $index }}"
        :class="{ 'border-blue-500 text-blue-600': activeTab === {{ $index }} }"
        class="px-4 py-2 font-medium text-gray-500 hover:text-gray-700 border-b-2 border-transparent">
        {{ $title }}
      </button>
    {{ end }}
  </div>

<div class="py-4"> {{ range $index, $tab := after 1 $tabs }} {{ $parts := split $tab "<!-- /tab -->" }} {{ $content := index $parts 0 }} <div x-show="activeTab === {{ $index }}" class="prose"> {{ $content | markdownify }} </div> {{ end }} </div> </div>

Penggunaan:

{{ < tabs > }}
<!-- tab macOS -->
### Installation on macOS
```bash
brew install hugo


Installation on Windows

choco install hugo-extended


Installation on Linux

sudo apt install hugo

{{ < /tabs > }}


### 2. Details/Summary (Accordion)

File: layouts/shortcodes/details.html


{{ $summary := .Get &quot;summary&quot; | default &quot;Details&quot; }}
{{ $open := .Get &quot;open&quot; | default false }}

&lt;details class=&quot;my-4 p-4 bg-gray-50 rounded-lg&quot; {{ if $open }}open{{ end }}&gt;
  &lt;summary class=&quot;font-semibold cursor-pointer&quot;&gt;{{ $summary }}&lt;/summary&gt;
  &lt;div class=&quot;mt-4 prose&quot;&gt;
    {{ .Inner | markdownify }}
  &lt;/div&gt;
&lt;/details&gt;
</code></pre>
<p><strong>Penggunaan:</strong></p>
<pre><code class="language-markdown">{{ &lt; details summary=&quot;Click to expand&quot; open=&quot;true&quot; &gt; }}
Hidden content that can be toggled.
{{ &lt; /details &gt; }}
</code></pre>
<h3>3. Book Rating</h3>
<p><strong>File: <code>layouts/shortcodes/book.html</code></strong></p>
<pre><code class="language-html">{{ $title := .Get &quot;title&quot; }}
{{ $author := .Get &quot;author&quot; }}
{{ $rating := .Get &quot;rating&quot; | default 0 }}
{{ $cover := .Get &quot;cover&quot; }}
{{ $link := .Get &quot;link&quot; }}

&lt;div class=&quot;flex gap-4 my-6 p-4 bg-gray-50 rounded-lg&quot;&gt;
  {{ with $cover }}
  &lt;img src=&quot;{{ . }}&quot; alt=&quot;{{ $title }}&quot; class=&quot;w-24 h-36 object-cover rounded shadow&quot;&gt;
  {{ end }}

  &lt;div class=&quot;flex-1&quot;&gt;
    &lt;h3 class=&quot;font-bold text-lg&quot;&gt;{{ $title }}&lt;/h3&gt;
    &lt;p class=&quot;text-gray-600&quot;&gt;{{ $author }}&lt;/p&gt;

    &lt;div class=&quot;flex items-center gap-1 mt-2&quot;&gt;
      {{ range seq 5 }}
        {{ if le . $rating }}
        &lt;svg class=&quot;w-5 h-5 text-yellow-400 fill-current&quot; viewBox=&quot;0 0 20 20&quot;&gt;
          &lt;path d=&quot;M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z&quot;&gt;&lt;/path&gt;
        &lt;/svg&gt;
        {{ else }}
        &lt;svg class=&quot;w-5 h-5 text-gray-300 fill-current&quot; viewBox=&quot;0 0 20 20&quot;&gt;
          &lt;path d=&quot;M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z&quot;&gt;&lt;/path&gt;
        &lt;/svg&gt;
        {{ end }}
      {{ end }}
    &lt;/div&gt;

    {{ with $link }}
    &lt;a href=&quot;{{ . }}&quot; class=&quot;text-blue-600 hover:underline mt-2 inline-block&quot;&gt;View on Amazon β†’&lt;/a&gt;
    {{ end }}
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Penggunaan:</strong></p>
<pre><code class="language-markdown">{{ &lt; book title=&quot;Atomic Habits&quot; author=&quot;James Clear&quot; rating=&quot;5&quot; cover=&quot;/images/atomic-habits.jpg&quot; link=&quot;https://amazon.com/...&quot; &gt; }}
</code></pre>
<h2>Shortcode Security</h2>
<h3>XSS Prevention</h3>
<pre><code class="language-html">&lt;!-- Selalu escape output --&gt;
{{ $title := .Get &quot;title&quot; | htmlEscape }}

&lt;!-- Atau gunakan safeHTML dengan hati-hati --&gt;
{{ $html := .Get &quot;html&quot; | safeHTML }}
</code></pre>
<h3>Markdownify vs HTML</h3>
<pre><code class="language-html">&lt;!-- Use markdownify untuk inner content --&gt;
{{ .Inner | markdownify }}

&lt;!-- Use .RenderString untuk custom markdown --&gt;
{{ .RenderString .Inner }}
</code></pre>
<h2>Best Practices</h2>
<h3>1. Naming Conventions</h3>
<ul>
<li><strong>Descriptive names</strong>: <code>youtube-custom</code> lebih baik dari <code>yt</code></li>
<li><strong>Consistent naming</strong>: <code>alert</code>, <code>button</code>, <code>card</code></li>
<li><strong>Avoid conflicts</strong>: Jangan override built-in kecuali perlu</li>
</ul>
<h3>2. Documentation</h3>
<p>Tambahkan docs di shortcode file:</p>
<pre><code class="language-html">&lt;!--
Usage:
{{ &lt; alert type=&quot;warning&quot; title=&quot;Title&quot; &gt; }}
Content here
{{ &lt; /alert &gt; }}

Parameters:
- type: info|warning|success|error|tip (default: info)
- title: string (optional)
--&gt;
</code></pre>
<h3>3. Default Values</h3>
<p>Selalu berikan default untuk required parameters:</p>
<pre><code class="language-html">{{ $title := .Get &quot;title&quot; | default &quot;Untitled&quot; }}
{{ $style := .Get &quot;style&quot; | default &quot;default&quot; }}
</code></pre>
<h3>4. Validation</h3>
<p>Check parameters sebelum digunakan:</p>
<pre><code class="language-html">{{ if .Get &quot;title&quot; }}
  &lt;!-- Render dengan title --&gt;
{{ else }}
  {{ errorf &quot;Shortcode 'card': missing required parameter 'title'&quot; }}
{{ end }}
</code></pre>
<h2>Kesimpulan</h2>
<p>Shortcodes adalah fitur powerful Hugo untuk:</p>
<p>βœ… <strong>Reusability</strong>: Components sekali buat, pakai berulang kali<br />
βœ… <strong>Consistency</strong>: Format uniform di seluruh site<br />
βœ… <strong>Simplicity</strong>: Markdown tetap bersih dan readable<br />
βœ… <strong>Flexibility</strong>: Kustomisasi tanpa batas<br />
βœ… <strong>Maintainability</strong>: Update global dengan edit satu file</p>
<p>Gunakan shortcodes untuk:<br />
- Complex layouts<br />
- Embeds (YouTube, CodePen, dll)<br />
- Styled components (alerts, buttons, cards)<br />
- Content variations (tabs, accordions)</p>
<h2>Artikel Terkait</h2>
<ul>
<li><a href="/panduan-lengkap-hugo-tailwind-css-2026/">Panduan Lengkap Membuat Website dengan Hugo dan Tailwind CSS 2026</a></li>
<li><a href="/cara-membuat-theme-hugo-tailwind/">Cara Membuat Theme Hugo dari Nol dengan Tailwind CSS</a></li>
<li><a href="/best-practices-content-hugo-seo/">Best Practices Structuring Content Hugo untuk SEO Maksimal</a></li>
<li><a href="/hugo-shortcodes-lengkap/">Hugo Shortcodes Lengkap: Membuat Components Reusable</a></li>
<li><a href="/optimasi-seo-hugo/">Optimasi SEO Hugo: Schema Markup dan Open Graph</a></li>
</ul>

Ditulis oleh

Hendra Wijaya

Tinggalkan Komentar

Email tidak akan ditampilkan.