Short URLs

The idea isn’t mine:

http://chris.beams.io/posts/epoch/

But I like it – and it would be easy to implement in Hugo.

I thought about just adding an alias directly in the content generator, but I agree with Chris Beams in the post above; it is better with a dedicated field.

So to sum it up:

  • Add a timestamp field that defaults to epoch minutes on content creation time
  • Add some knobs:
    • Turn this timestamp feature on/off
    • Use epoch seconds instead of minutes
    • Add a custom epoch (for personal blogs, this could enable having a post about your birth with timestamp=0)

Any thoughts?

1 Like

I think it’s an idea with some merit.

Some suggestions:

  1. Make it optional
  2. Use seconds
  3. Use Base 36 encoding, not base 10.
    It’s a lot shorter and still works on case insensitive file systems.
  4. Tie it into the hugo new functionality.
  5. Provide a page.ShortLink() function
  6. Add documentation :slight_smile:

Digging this one up, because I’m also looking for a (semi-)automated method for creating shortlinks.

Recently came across this page: permashortlink - IndieWeb
Really liking the idea of the NewBase60.

Multiple implementations exist.

  1. Purely time-based

Use NB60 to turn yyyy-mm-dd into a number and hh: mm: ss into a number. Concat both together.
Now 4xgNaa expands to /2018/11/26/173535/

  1. using identifiers and sequence numbers

You basically use NB60 to turn /year/month/day into a number, then append e.g. a sequence number (in case you have more than 1 post per day) and/or a type identifier (blog, article, note, …).
So tSSSn can programmatically be expanded into /type/year/month/day/number

I see permashortlink is also mentioned here: Multiple permalinks

The existing hash.FNV32a function gets you pretty close.

layouts/partials/make-short-url.html
{{ $html := printf `<!DOCTYPE html>
<html lang="%[1]s">
  <head>
    <title>%[2]s</title>
    <link rel="canonical" href="%[2]s">
    <meta name="robots" content="noindex">
    <meta charset="utf-8">
    <meta http-equiv="refresh" content="0; url=%[2]s">
  </head>
</html>
` .Language.LanguageCode .Permalink }}

{{ $hash := .Path | hash.FNV32a }}
{{ $publishPath := path.Join site.LanguagePrefix $hash "index.html" }}
{{ (resources.FromString $publishPath $html).Publish }}
{{ return ($hash | absLangURL) }}

Then call the partial from your base template or single template or whatever.

{{ $shortURL := partial "make-short-url.html" . }}
<p>Short URL: <a href="{{ $shortURL }}">{{ $shortURL }}</a></p>

Every time the partial is called, it writes an HTML redirect file to disk, something like:

public/4042937710/index.html
<!DOCTYPE html>
<html lang="en-US">
  <head>
    <title>http://example.org/posts/post-1/</title>
    <link rel="canonical" href="http://example.org/posts/post-1/">
    <meta name="robots" content="noindex">
    <meta charset="utf-8">
    <meta http-equiv="refresh" content="0; url=http://example.org/posts/post-1/">
  </head>
</html>

On a monolingual site the permalink looks like this:

http://example.org/4042937710

On a multilingual multi-host site, with defaultContentLanguageInSubdir = true, the permalinks look like this:

http://example.org/en/4042937710
http://example.org/de/4042937710

On a multilingual multi-host site, with defaultContentLanguageInSubdir = false, the permalinks look like this:

http://example.org/4042937710
http://example.org/de/4042937710

The hash is based on a page’s logical path, so the shortened URL is not affected by changes to:

  • The title, date, slug, or url front matter fields
  • The permalinks key in your site configuration

This approach will not work if uglyURLs = true in your site configuration, but I suppose you could modify the partial to accommodate that.

5 Likes

This does seem to work :slight_smile:

But I feel like alias: would somehow be a neater solution for this?

Sure, you could use an archetype to automatically populate the aliases array in front matter, but:

  1. That only works when you create content with the hugo new content command. It won’t work when someone creates a new content file by copying an existing content file.
  2. It is fragile. Anyone can change the value at any time. It is far less likely that someone would change the logical path, and easier to catch in a PR.
  3. The front matter approach is not possible when pulling content in from a module unless the module content already has the aliases defined.