Making a static webring with Jekyll

This week, I decided to make a webring, which for me meant learning a little ruby to program a jekyll generator plugin. Here’s a little more about what that actually means:

A webring? Isn’t it 2025?

I. HATE. instagram. It is so inconsistent and requires such an awful marketing brain. It makes me sick to spend so much time thinking about how to market myself, even to my friends when I just want to post a little little story. I also can’t stand how much data Meta is harvesting from me and all of my connections, and it’s gross being on the same platform as so many nazis and homophobes. It’s not comfortable feeling, and it doesn’t feel like community at all. A webring is a kind of online collection, with a specific theme, designed for sharing engagement and interaction between a series of websites. The exact mekanism for this is a string of links, with each website linking to the site before and after it in the ring. This adds resiliency in case any one member of the ring goes down, it now is just a link chain and you can go the other way around the ring. I am creating specifically a Queer music webring, using jekyll for static site generation and neocities.org for free hosting.

Why jekyll?

Because it’s what I’m already using for most of my other sites, and I’m fairly comfortable with it, and I was fairly sure I could create a simple site that used no javascript. I wanted to avoid javascript for unltimate accesibilty (No need for a modern browser, no need for a even remotely powerful device! Keep it as simple as possible) and also because I don’t know javascript. I wanted to avoid learning a whole new language for this project. :)

The basics

I created a basic site, and some placeholders for link pages. The idea was to have each page redirect to “example-webring.com/slug/next.html” to get to the next site in the ring, example-webring being our domain and slug being a unique identifier each site will recieve.

We create a file called members.json file in the _data directory in our jekyll project, with this layout

[
  {
    "slug": "unique-identifier",
    "name": "A human readable site name",
    "url": "https://example.com/",
    "description": "A colorful description!",
    "tags":["tags","for","website"]
  },
  {
    "slug": "kitbash",
    "name": "kitbash the fash!",
    "url": "https://kitbash.neocities.org/",
    "description": "The personal website of the admin of this page! wow! It's own personal trash heap, complete with album reviews for fun",
    "tags":["meow"]
  }
]

The tags section is optional, and will be ignored if not populated

Next, we create a plugin in the _plugins folder, named something like “generator.rb” (the name can be anything, it must be a .rb file though). Do you remember how I said I didn’t want to learn a new language for this? Well.

Learning a new language for this

In order to create pages without existing files, jekyll makes use of plugins called “generators.” Jekyll plugins are programmed in ruby and I Do Not Know Ruby. I’ll save us some time and show the complete code here, and then we’ll break it down:

module WebringGenerator
	class RedirectPagePageGenerator < Jekyll::Generator
		safe true
		def generate(site)
			members = site.data["members"] #site.data is a hash we can access wiht a key, here "members" which returns an array

			members.each_with_index { |member, ind|

				nextURL = members.fetch(ind+1, members[0])["url"]
				prevURL = members.fetch(ind-1, members[-1])["url"]

				site.pages << RedirectPage.new(site, member["slug"],"next",nextURL)
				site.pages << RedirectPage.new(site, member["slug"],"prev",prevURL)

			}
		end
	end

	# Subclass of `Jekyll::Page` with custom method definitions.
	class RedirectPage < Jekyll::PageWithoutAFile 
		def initialize(site, slug, filename, link)
			@site = site             # the current site instance.
			@base = site.source      # path to the source directory.
			@dir  = slug         # the directory the page will reside in.

			# All pages have the same filename, so define attributes straight away.
			@basename = filename      # filename without the extension.
			@ext      = '.html'      # the extension.
			@name     = filename + '.html' # basically @basename + @ext.
		

			@data = {
				"redirectLink" => link
			}

        # Look up front matter defaults scoped to type `tags`, if given key
        # doesn't exist in the `data` hash.
        data.default_proc = proc do |_, key|
          site.frontmatter_defaults.find(relative_path, :tags, key)
        end
	  end

		# Placeholders that are used in constructing page URL. (???)
		def url_placeholders
			{
				:path       => @dir,
				:slug   	=> @dir,
				:filename 	=> @dir,
				:basename   => basename,
				:output_ext => output_ext,
			}
		end
	end
end

You’ll notice in some spots we use the keyword “do” to open a section and “end” to close it, (a little like lua) and in other spots use curly braces. This is due to ruby accepting both, I assume one is deprecated, and me copying and pasting from various tutorials in order to get this to work at all.

module WebringGenerator
	class RedirectPagePageGenerator < Jekyll::Generator

We start by declaring the whole module, and then creating the class “RedirectPagePageGenerator” as a subclass of Jekyll::Generator. Don’t ask me why it says ‘Page’ twice, it doesn’t need to.

safe true

I believe this tells jekyll to run this generator even in “safe” mode, though this particular section was copied and the generator ran fine with and without it.

	def generate(site)
			members = site.data["members"] #site.data is a hash we can access wiht a key, here "members" which returns an array

			members.each_with_index { |member, ind|

				nextURL = members.fetch(ind+1, members[0])["url"]
				prevURL = members.fetch(ind-1, members[-1])["url"]

				site.pages << RedirectPage.new(site, member["slug"],"next",nextURL)
				site.pages << RedirectPage.new(site, member["slug"],"prev",prevURL)

			}
		end
	end

Here we assign the members data and loop over it. The .fetch() function will search for the first argument given in the array, and if it doesnt find it, return the second argument instead. So, for example, if we can’t find the previous entry in the array because we’re checking the first entry, we can instead return the last “[-1]” entry instead. I thought this was a rather neat way to do this.

Then we call the RedirectPage.new() function, a constructor for a class we define next, and insert the result into the site.pages, ie. add it to the pages that are put into _site.


	# Subclass of `Jekyll::Page` with custom method definitions.
	class RedirectPage < Jekyll::PageWithoutAFile 
		def initialize(site, slug, filename, link)
			#Some stuff		

			@data = {
				"redirectLink" => link
			}

        # Look up front matter defaults scoped to type `tags`, if given key
        # doesn't exist in the `data` hash.
        data.default_proc = proc do |_, key|
          site.frontmatter_defaults.find(relative_path, :tags, key)
        end
	  end

We then declare the RedirectPage class and its constructor function. We initialize the pages data to access later and assign the page the default layout that is assigned to “tags” in the _config.yml

We edit _config.tml to include:

defaults:
  - scope:
      type: tags # select all tags pages
    values:
      layout: redirects

where “redirects” is our layout.

finally we create a layout

---
---
<!-- _layouts/redirects.html -->

<!doctype html>
<html>
	<head>
		<meta http-equiv="refresh" content="0; url=" />
	</head>

	<body style="background-color:#000;color:#fff">
		<p><a style="color: #fff;" href="">Click here if page does not automatically redirect!</a></p>
	</body>
</html>

We assign some colors, but avoid using a template with uneccesary prettying as that would just take longer to load. We use the meta tag in the head to automatically redirect to whatever redirect variable we have for that page, and also create a link on the page just in case something goes wrong or the redirect doesn’t happen automatically.

That’s about it, but don’t be fooled this was quite a lot of banging my head against my keyboard to understand it, particularly trying to understand an error I kepy getting ‘/var/lib/gems/3.0.0/gems/jekyll-4.3.4/lib/jekyll/page.rb:193:in excerpt': undefined method []' for nil:NilClass (NoMethodError) ' (I believe this was caused by not giving the page any data or proper filename? but it was impossible to search for online and very unhelpful. Thanks, jekyll)

I also created a members page, which iterates over the members data and makes it look pretty. (\’s added to make it actually show liquid variables, wish i could find a better way to escape that where they wouldn’t appear?.)

<h1 id=members>Members: (\{\{site.data.members.size}})</h1>
<hr>

\{\% for member in site.data.members %}
<div>
	<h2> \{\{member.name}} </h2>
	<p>  \{\{member.description}} </p>
	<a href = "\{\{member.url}}" > \{\{member.url\}\} </a>
	\{\% if member.tags %} <!-- If tags exist -->
		<h3> tags: </h3>
		<div class="tagList"> 
			\{\% for tag in member.tags \%\} <!-- Iterate tags -->
			\{\{tag}}\{\% if forloop.last == false %},\{\%endif%}
			\{\% endfor %}
		</div>
	\{\%endif%}
</div>

<hr>
\{\% endfor %}

I’m sure it could be made much prettier but I wanted something kind of simple still, to go with that 90s/early 2000s aesthetic and avoid having to do too much.

All a member of the site has to do is link to their-slug/next.html their-slug/prev.html to reach the sites they are linked with. I had a little stumble when linking from other sites because I didn’t realize the link had to include the “.html” but that only took a little fiddling.

You can view the final result at https://pdx-webring.neocities.org/

These resources were also extremely helpful, particularly Viatrix’s tutorial on webrings that relied on Netlify, which I didn’t want to learn so I had to learn a little ruby instead: https://vivivi.leprd.space/webmastery/webring/ https://github.com/recurser/jekyll-plugins/tree/master, particularly the “Generate_catergories.rb” and “generate_projects.rb” files to study https://aboutfullstack.com/jekyll/jekyll-generate-pages-for-each-tags/, The most helpful for understand what was going on with ruby and the generator. Also less than a year old!

You’ll notice some of these are quite old, I’m just glad theyre still around and took the liberty to make sure each was in the internet archive.

Overall this was a very fun project, and I’m glad I took up it. Understanding the inner workings of jekyll and ruby a little more with definitely be insightful as I keep working with it, and maybe one day I’ll put some javascript on a website :) I’m just excited to start another little corner of the internet where I can love my queer friends, and create a little trouble.