Salin dan Bagikan
Hugo Custom Output Formats: JSON, AMP, dan Custom Outputs - Panduan lengkap custom output formats di Hugo. Pelajari cara membuat JSON API, AMP pages, dan custom …

Hugo Custom Output Formats: JSON, AMP, dan Custom Outputs

Hugo Custom Output Formats: JSON, AMP, dan Custom Outputs

Hugo’s output format system adalah fitur powerful yang memungkinkan Anda generate multiple output formats dari content yang sama. Ini membuka kemungkinan untuk membuat JSON APIs, AMP pages, RSS feeds, dan berbagai custom outputs. Panduan ini akan membahas cara memanfaatkan output format system untuk berbagai use cases.

Standard Hugo output adalah HTML, namun banyak aplikasi membutuhkan output formats yang berbeda. Dengan Hugo’s output format system, Anda dapat define custom outputs dan Hugo akan generate semua formats secara otomatis saat build.

Memahami Output Format System

Default Output Formats

Hugo menyediakan beberapa built-in output formats:

HTML adalah format default untuk regular pages. JSON adalah format untuk APIs dan data exchange. RSS adalah format untuk RSS feeds. AMP adalah format untuk Accelerated Mobile Pages.

Mendefinisikan Output Formats

# hugo.toml
[outputs]
  home = ["HTML", "JSON", "RSS"]
  page = ["HTML", "JSON"]
  section = ["HTML", "JSON", "RSS"]
  taxonomy = ["HTML", "JSON"]
  RSS = ["RSS"]

[mediaTypes]
  [mediaTypes.json]
    suffix = "json"
    type = "application/json"
    
  [mediaTypes.rss]
    suffix = "xml"
    type = "application/rss+xml"
    
  [mediaTypes.amp]
    suffix = "amp.html"
    type = "text/html"

[outputFormats]
  [outputFormats.json]
    mediaType = "mediaTypes.json"
    baseName = "index"
    isPlainText = true
    notAlternative = true
    
  [outputFormats.rss]
    mediaType = "mediaTypes.rss"
    baseName = "index"
    isPlainText = true
    
  [outputFormats.amp]
    mediaType = "mediaTypes.amp"
    baseName = "index"
    path = "amp"
    isPlainText = false

Membuat JSON API

JSON untuk Homepage

{{/* layouts/_default/index.json */}}
{{- $.Scratch.Add "data" (dict 
    "title" .Site.Title
    "description" .Site.Params.description
    "url" .Permalink
    "lastUpdated" .Lastmod
) -}}

{{- $.Scratch.Add "posts" slice -}}
{{- range first 10 (where .Site.RegularPages "Section" "posts") -}}
    {{- $.Scratch.Add "posts" (dict
        "title" .Title
        "slug" .Slug
        "date" .Date
        "description" .Description
        "url" .Permalink
        "categories" .Params.categories
        "tags" .Params.tags
    ) -}}
{{- end -}}

{{- $.Scratch.SetInMap "data" "posts" ($.Scratch.Get "posts") -}}
{{- printf "%s" (jsonify ($.Scratch.Get "data")) | resources.FromString "index.json" | minify -}}

JSON untuk Single Post

{{/* layouts/_default/single.json */}}
{{- $post := dict
    "title" .Title
    "slug" .Slug
    "date" .Date
    "lastModified" .Lastmod
    "description" .Description
    "content" (.Content | plainify)
    "htmlContent" .Content
    "url" .Permalink
    "featuredImage" .Params.image
    "categories" .Params.categories
    "tags" .Params.tags
    "author" .Params.author
    "readingTime" (div (wordCount .Content) 200 | math.Ceil)
-}}
{{- printf "%s" (jsonify $post) -}}
{{/* layouts/_default/search.json */}}
{{- $.Scratch.Add "results" slice -}}
{{- range where .Site.RegularPages "Section" "posts" -}}
    {{- $.Scratch.Add "results" (dict
        "id" .File.UniqueID
        "title" .Title
        "slug" .Slug
        "section" .Section
        "date" .Date
        "description" .Description
        "content" (.Content | plainify | lower)
        "url" .Permalink
        "categories" .Params.categories
        "tags" .Params.tags
    ) -}}
{{- end -}}
{{- printf "%s" (jsonify (dict "total" ($.Scratch.Get "results" | len) "posts" ($.Scratch.Get "results"))) -}}

JSON Feed untuk Blog

{{/* layouts/_default/feed.json */}}
{{- $title := .Site.Title -}}
{{- $description := .Site.Params.description -}}
{{- $feedUrl := .Permalink -}}
{{- $author := dict 
    "name" .Site.Params.author
    "email" .Site.Params.authorEmail
    "url" .Site.BaseURL
-}}

{{- $.Scratch.Add "items" slice -}}
{{- range first 20 (where .Site.RegularPages "Section" "posts") -}}
    {{- $.Scratch.Add "items" (dict
        "id" .Permalink
        "url" .Permalink
        "title" .Title
        "contentHtml" .Content
        "contentText" (.Content | plainify)
        "summary" .Description
        "image" .Params.image
        "datePublished" .Date
        "dateModified" .Lastmod
        "author" $author
    ) -}}
{{- end -}}

{{- $feed := dict
    "version" "https://jsonfeed.org/version/1"
    "title" $title
    "home_page_url" .Site.BaseURL
    "feed_url" $feedUrl
    "description" $description
    "author" $author
    "items" ($.Scratch.Get "items")
-}}
{{- printf "%s" (jsonify $feed) -}}

Membuat AMP Pages

Base Template untuk AMP

{{/* layouts/_default/baseof.amp */}}
<!doctype html>
<html  lang="{{ .Site.LanguageCode }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
    <title>{{ .Title }} | {{ .Site.Title }}</title>
    
    <link rel="canonical" href="{{ .Permalink }}">
    
    {{- with .Description -}}
        <meta name="description" content="{{ . }}">
    {{- end -}}
    
    <script async src="https://cdn.ampproject.org/v0.js"></script>
    
    <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
    
    <style amp-custom>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h1, h2, h3 {
            color: #1a1a1a;
            line-height: 1.2;
        }
        img {
            max-width: 100%;
            height: auto;
        }
        code {
            background: #f4f4f4;
            padding: 2px 6px;
            border-radius: 3px;
            font-size: 0.9em;
        }
        pre {
            background: #f4f4f4;
            padding: 15px;
            overflow-x: auto;
            border-radius: 5px;
        }
        .meta {
            color: #666;
            font-size: 0.9em;
        }
    </style>
    
    {{- template "_internal/opengraph.html" . -}}
</head>
<body>
    {{ block "main" . }}{{ end }}
</body>
</html>

Single Post AMP Template

{{/* layouts/_default/single.amp */}}
{{- define "main" -}}
<article>
    <header>
        <h1>{{ .Title }}</h1>
        <p class="meta">
            {{ with .Params.author }}{{ . }}{{ end }}
            
            <time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2 January 2006" }}</time>
        </p>
    </header>
    
    {{- with .Params.image -}}
        <amp-img 
            src="{{ . }}" 
            width="800" 
            height="400" 
            layout="responsive"
            alt="{{ $.Title }}">
        </amp-img>
    {{- end -}}
    
    <div class="content">
        {{ .Content }}
    </div>
    
    <footer>
        <p class="meta">
            {{ with .Params.categories }}
                Categories: {{ delimit . ", " }}
            {{ end }}
        </p>
    </footer>
</article>
{{- end -}}

Custom Output Format untuk Sitemap

Extended Sitemap

{{/* layouts/_default/sitemap.xml */}}
{{- printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" | safeHTML -}}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
    {{- range .Site.RegularPages -}}
    <url>
        <loc>{{ .Permalink }}</loc>
        <lastmod>{{ .Lastmod.Format "2006-01-02" }}</lastmod>
        <changefreq>{{ with .Params.changeFreq }}{{ . }}{{ else }}weekly{{ end }}</changefreq>
        <priority>{{ with .Params.priority }}{{ . }}{{ else }}{{ cond .IsHome 1.0 0.5 }}{{ end }}</priority>
        {{- with .Params.image }}
        <image:image>
            <image:loc>{{ . }}</image:loc>
            <image:title>{{ $.Title }}</image:title>
        </image:image>
        {{- end -}}
    </url>
    {{- end -}}
</urlset>

News Sitemap

{{/* layouts/_default/news-sitemap.xml */}}
{{- printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" | safeHTML -}}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
    {{- range where .Site.RegularPages "Section" "posts" -}}
    {{- if ge .Date.AddDate 0 0 -48 -}}
    <url>
        <loc>{{ .Permalink }}</loc>
        <news:news>
            <news:publication>
                <news:name>{{ .Site.Title }}</news:name>
                <news:language>{{ .Site.LanguageCode }}</news:language>
            </news:publication>
            <news:publication_date>{{ .Date.Format "2006-01-02T15:04:05+00:00" }}</news:publication_date>
            <news:title>{{ .Title }}</news:title>
        </news:news>
    </url>
    {{- end -}}
    {{- end -}}
</urlset>

Video Sitemap

{{/* layouts/_default/video-sitemap.xml */}}
{{- printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" | safeHTML -}}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
    {{- range where .Site.RegularPages "Section" "videos" -}}
    <url>
        <loc>{{ .Permalink }}</loc>
        <video:video>
            <video:thumbnail_loc>{{ .Params.thumbnail }}</video:thumbnail_loc>
            <video:title>{{ .Title }}</video:title>
            <video:description>{{ .Description }}</video:description>
            <video:player_loc autoplay="false">{{ .Params.videoUrl }}</video:player_loc>
            <video:duration>{{ .Params.duration }}</video:duration>
            <video:publication_date>{{ .Date.Format "2006-01-02T15:04:05+00:00" }}</video:publication_date>
            <video:tag>{{ delimit .Params.tags "," }}</video:tag>
            <video:category>{{ .Params.category }}</video:category>
        </video:video>
    </url>
    {{- end -}}
</urlset>

Multiple Output Formats untuk Same Content

Configure Multiple Outputs

# hugo.toml
[outputs]
  home = ["HTML", "JSON", "AMP"]
  page = ["HTML", "JSON", "AMP"]

[mediaTypes]
  [mediaTypes.json]
    suffix = "json"
    type = "application/json"
  [mediaTypes.amp]
    suffix = "amp.html"
    type = "text/html"

[outputFormats]
  [outputFormats.json]
    mediaType = "mediaTypes.json"
    baseName = "index"
    isPlainText = true
    notAlternative = true
  [outputFormats.amp]
    mediaType = "mediaTypes.amp"
    baseName = "index"
    path = "amp"
    isPlainText = false
{{/* layouts/_default/baseof.html */}}
<head>
    {{- if .IsHome -}}
        <link rel="alternate" type="application/json" href="{{ .OutputFormats.Get "JSON" .Permalink }}">
    {{- end -}}
    
    {{- range .OutputFormats -}}
        {{- if ne .Name "HTML" -}}
            <link rel="alternate" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ .Name }}">
        {{- end -}}
    {{- end -}}
</head>

Best Practices

Performance Considerations

Generate multiple outputs menambah build time. Pertimbangkan untuk:

  • Use conditional builds dengan hugo --outputFormats
  • Cache expensive computations
  • Exclude dari production builds jika tidak diperlukan

Template Organization

layouts/
  _default/
    baseof.html      # HTML
    baseof.amp        # AMP
    baseof.json       # JSON API
    single.html       # HTML post
    single.amp        # AMP post
    single.json       # JSON post
  _internal/
    rss.xml          # Built-in RSS

Validation

Validate outputs untuk memastikan correctness:

# Validate JSON
cat public/index.json | python -m json.tool > /dev/null && echo "Valid JSON"

# Validate XML
xmllint --noout public/sitemap.xml && echo "Valid XML"

Kesimpulan

Hugo’s output format system adalah fitur powerful yang memungkinkan Anda generate multiple formats dari content yang sama. Dengan memahami dan memanfaatkan fitur ini, Anda dapat membuat APIs, AMP pages, dan custom outputs sesuai kebutuhan proyek.

Artikel Terkait

Link Postingan : https://www.tirinfo.com/hugo-custom-output-formats/

Hendra WIjaya
Tirinfo
6 minutes.
4 February 2026