Reading List

The most recent articles from a list of feeds I subscribe to.

The URLs are new

This blog has had pretty long URLs for legacy reasons. Today I've made some updates to change that.

Redirects and RSS should all work, though some feed readers seem to surface old posts as unread, sorry for any inconvenience.

For context, this used to be my company's website. I only really ever updated the blog section, so I decided to make that the main thing. The old structure lasted just over 8 years and my hope is that the new one will break that record.

URLs are such a cool part of the web, especially if they're set up thoughtfully. Like traintimes.org.uk, where you can add /london/bristol to get times for trains from London to Bristol, and /9:00 to that if you want the ones that leave from 9am.

I don't know of ways to give blog URLs very thoughtful structures, there isn't a lot of structure in this blog anyway. Anne van Kesteren's classic post on how to do blogs has some good pointers, like where an archive should live.

New blog post URLs

On this blog, a post URL used to be something like:

hiddedevries.nl/en/blog/2022-04-02-my-post

It's now:

hidde.blog/my-post

The main changes are:

  • new host name, reducing lots of characters, easier to remember and put on slides
  • single language setup, removing the need for en
  • no more dates in the URL structure

I wasn't super sure about removing dates, so I asked on Twitter. I didn't have a specific reason for including dates before, it was a default of my CMS and I kind of liked it at the time.

Dates matter

A couple of people replied to my question that dates on blog posts are important to readers. Especially for blog posts that share technical advice, as they could go out of date.

Other reasons for including a date are:

But there are drawbacks too:

  • date is usually creation date, this could make posts seem old, while the author may be frequently keeping content up to date, this could be confusing
  • dates are really metadata, they don't belong in the URL
  • it's not as simple and readable

I've gone for simplicity. I do agree dates matter though, especially on tech posts, so I have the publication date in the post content near the title (I do use GitHub's <time-ago> Web Component to display it relatively). I also include dates when I make updates, though I add those at the end rather than near the title (I probably want to improve that).

That's all for today. I've also moved away from the CMS, so if you notice anything is broken, please do slide in my DMs or email my first name at hiddedevries.nl.


Originally posted as The URLs are new on Hidde's blog.

Reply via email

Common accessibility issues that you can fix today

WebAIM have come out with their latest report on which accessibility issues they found in the top million websites that they tested automatically. What is some low hanging fruit you could fix today?

For context, WebAIM, a non profit based out of Utah in the US, have done their ‘WebAIM Million’ project since 2019. They post an extensive analysis every year, looking at trends and improvements/decline in web accessibility over time. I find these posts very insightful and use them to inform my own workshops and outreach. It’s definitely recommended reading!

There are some caveats to be added with surveys based on automated accessibility testing. One is that ‘ease of detectability’ does not correlate with ‘impact on end users’. There are issues that are easy to detect and issues that impact end users most, these are not necessarily the same. Automated tests also cover only a small part of all accessibility, as some things aren’t detectable by machines (yet, or ever). I’m not suggesting this makes the survey less useful or good, but wanted to call it out explicitly. The ACT-Rules Community Group at the W3C works on harmonising test rules for things that are testable.

Ok, let’s look at the top issues and how developers, browsers and CMSes can take away barriers today. Some of these include ideas about what users can do (important caveat: none of this should be user responsibility, website owners should not expect users to use or know about these tools).

Low text contrast

There are cases where text colour and the background colour have a ratio lower than the threshold in WCAG (see also MDN on contrast).

Designers can check contrast manually install plugins (using a tool like Contrast Ratio) or install a plugin like Stark for Figma or Sketch.

Developers can use an automated contrast checker to make sure you avoid low contrast text. Run a checker like the one in Firefox Dev Tools Accessibility Panel or axe in CI/CD, or paste two colours into a manual tool like Contrast Ratio.

Designers and developers alike can use Polypane’s contrast checker that suggests accessible alternatives, which makes it a lot easier to not just find the issue, but also fix it while you’re at it.

Users could use a plugin like Fix Contrast to not be affected: it tweaks colours on the fly so that you don’t have to suffer low contrast text.

There are some discussions on new colour contrast algorithms for WCAG, but this is still under discussion and not ready any time soon.

Missing alternative text

When you create content, describe your images. In your CMS, on Twitter and even in GitHub issues: use that alternative text feature, so that users who can’t see the image can access the content in them. On platforms that don’t support alternative text, like Slack or mobile LinkedIn (!), you can describe images in text. If you choose a CMS or content platform, ensure it can handle alternative text or set it up to do so.

In the resulting HTML, you’ll want something like:

<!-- image with text -->
<img src="website-logo.png" alt="hiddedevries.nl" />

<!-- image with redundant content -->
<img src="hidde.png" alt="" /> 
<p>Photo of Hidde</p>

You can make your decisions on alternative text using the Alt Decision Tree, but basically the gist is that if you were to replace your image with a square, what would you write in the square for it to still be useful? Leaving it empty is an option if that’s the most useful alternative for the image.

Users can use Microsoft Edge, which fills in missing alternatives. AI aren’t very good at intentions or context, but pretty much perfect at recognising text. Next time a news website posts an image of new covid rules with no alternatives (as the main Dutch news website used to do throughout the pandemic), users of Edge can follow along.

Empty links and buttons

When links, buttons or other interactive elements don’t have names, assistive technologies like screenreaders or voice controls have no way to uniquely refer to them. If it is to be interacted with, it needs a name.

Names are derived from text content, like the actual text that’s inside a button, or can be added through an attribute. See Naming things to improve accessibility for more detail or the spec that defines how controls get their names.

Developers need to ensure all controls they build (links, buttons, form fields, etc) have names, ideally that make sense out of context:

<!-- names that make sense out of context -->
<button>Submit form</button>
<button>Publish content</button>
<button>Expand filters</button>
<a href="//wikipedia.org">Wikipedia</a>
<a href="//twitter.com">Hidde on Twitter</a>

<!-- names that are confusing -->
<a href="/">click here</a><!-- avoid -->
<a href="/>read more</a><!-- avoid -->

<!-- names that are missing; avoid or add a name -->
<a href="//twitter.com/"><svg/></a><!-- avoid -->
<a href="//linkedin.com"><img src="" alt="" /></a><!-- avoid -->
<button><img/><button><!-- avoid -->

Browsers can sometimes derive names from nearby text, some do this when there is no text to derive a name from. This is not ideal, can lead to wrong and confusing guesses and be bad end user experience. The developer of this control will know best what the thing does and therefore how it should be named.

Missing form labels

Form labels kind of fall under naming things, as they name form elements specifically.

Developers can fix this by ensuring form fields have a <label> element of which the for attribute points to the id of the input/select/textarea, this also makes that clicking label moves the cursor to the input:

<!-- “for” and “id” are same, this connects them -->
<label for="field">Address</label>
<input id="field" />

They can also add an aria-label attribute to the input to name it that way (but be careful, ARIA labels don’t get translated well).

Missing languages

Proper language indication can get you a Pass on two (!) WCAG Success Criteria (3.1.1 and 3.1.2), and you only need to know one attribute for it.

Developers should ensure the <html> element has a lang attribute with the right language:

<!-- marks this document as 'in English -->
<html lang="en">

Sometimes this is forgotten as the <html> element lives in some template you never touch, but it’s not hard to do, so always double check that this attribute exists and is set to the right language.

If there is content on part the page that is in another language, mark that too, again, with a lang attribute on the relevant DOM node. When you pick a plugin for internationalisation, ensure it sets the langs or makes it easy for you to do so.

CMSes can make sure that lang attributes are set and set correctly. Browsers can guess languages, but they aren’t always good at this, specifically when it comes to distinguishing dialects: they often matter a lot to people, less so to machines.

Wrapping up

These are some tips based on the most common issues that WebAIM Million 2022 identified. Let’s all put in the work to make sure we, our colleagues, our clients and our users avoid these issues. Like, actually. We need WCAG auditors to focus on higher hanging fruit, by fixing the lower hanging stuff for good.

If this is all new to you, hi, thanks for reading! I hope this helps and feel free to get in touch with questions. If you already knew all of this, please keep spreading the word, so that in next year’s survey, we’ll see steady improvements for the web at large.

Want to learn more about fixing low hanging fruit accessibility issues? In response to the same survey, Christian Heilmann blogged about how to fix accessibility issues using your browser


The post Common accessibility issues that you can fix today was first posted on hiddedevries.nl blog | Reply via email

Common accessibility issues that you can fix today

This week, WebAIM published their latest “WebAIM Million” report. It details accessibility issues they found when they tested the web's top million websites automatically. What is some low hanging fruit you could fix today?

For context, WebAIM, a non profit based out of Utah in the US, have done their ‘WebAIM Million’ project since 2019. They post an extensive analysis every year, looking at trends and improvements/decline in web accessibility over time. I find these posts very insightful and use them to inform my own workshops and outreach. It’s definitely recommended reading!

There are some caveats to be added with surveys based on automated accessibility testing. One is that ‘ease of detectability’ does not correlate with ‘impact on end users’. There are issues that are easy to detect and issues that impact end users most, these are not necessarily the same. Automated tests also cover only a small part of all accessibility, as some things aren’t detectable by machines (yet, or ever). I’m not suggesting this makes the survey less useful or good, but wanted to call it out explicitly. The ACT-Rules Community Group at the W3C works on harmonising test rules for things that are testable.

Ok, let’s look at the top issues and how developers, browsers and CMSes can take away barriers today. Some of these include ideas about what users can do (important caveat: none of this should be user responsibility, website owners should not expect users to use or know about these tools).

Low text contrast

There are cases where text colour and the background colour have a ratio lower than the threshold in WCAG (see also MDN on contrast).

Designers can check contrast manually install plugins (using a tool like Contrast Ratio) or install a plugin like Stark for Figma or Sketch.

Developers can use an automated contrast checker to make sure you avoid low contrast text. Run a checker like the one in Firefox Dev Tools Accessibility Panel or axe in CI/CD, or paste two colours into a manual tool like Contrast Ratio.

Designers and developers alike can use Polypane’s contrast checker that suggests accessible alternatives, which makes it a lot easier to not just find the issue, but also fix it while you’re at it.

Users could use a plugin like Fix Contrast to not be affected: it tweaks colours on the fly so that you don’t have to suffer low contrast text.

There are some discussions on new colour contrast algorithms for WCAG, but this is still under discussion and not ready any time soon.

Missing alternative text

When you create content, describe your images. In your CMS, on Twitter and even in GitHub issues: use that alternative text feature, so that users who can’t see the image can access the content in them. On platforms that don’t support alternative text, like Slack or mobile LinkedIn (!), you can describe images in text. If you choose a CMS or content platform, ensure it can handle alternative text or set it up to do so.

In the resulting HTML, you’ll want something like:

<!-- image with text -->
<img src="website-logo.png" alt="hiddedevries.nl" />

<!-- image with redundant content -->
<img src="hidde.png" alt="" /> 
<p>Photo of Hidde</p>

You can make your decisions on alternative text using the Alt Decision Tree, but basically the gist is that if you were to replace your image with a square, what would you write in the square for it to still be useful? Leaving it empty is an option if that’s the most useful alternative for the image. Like in the example above, there is a photo with a paragraph underneath that describes it. In this case, writing “Hidde” or “Photo of Hidde” in the alt attribute is redundant, it would be best to use an empty alt (but do leave the attribute in there, or some browsers will use the image URL as an alternative).

Users can use Microsoft Edge, which fills in missing alternatives. AI aren’t very good at intentions or context, but pretty much perfect at recognising text. Next time a news website posts an image of new covid rules with no alternatives (as the main Dutch news website used to do throughout the pandemic), users of Edge can follow along.

Empty links and buttons

When links, buttons or other interactive elements don’t have names, assistive technologies like screenreaders or voice controls have no way to uniquely refer to them. If it is to be interacted with, it needs a name.

Imagine the difference between a screenreader saying “here's 4 buttons: button, button, button and button”, versus “here's 4 buttons: publish content, delete content, change to draft, upload image”. In the first instance, you'd need to press them before you know what they do, in the second, it is clear what you're in for, with no further context needed.

Names are derived from text content, like the actual text that’s inside a button, or can be added through an attribute. See Naming things to improve accessibility for more detail or the spec that defines how controls get their names.

Developers need to ensure all controls they build (links, buttons, form fields, etc) have names, ideally that make sense out of context:

<!-- names that make sense out of context -->
<button>Submit form</button>
<button>Publish content</button>
<button>Expand filters</button>
<a href="//wikipedia.org">Wikipedia</a>
<a href="//twitter.com">Hidde on Twitter</a>

<!-- names that are confusing -->
<a href="/">click here</a><!-- avoid -->
<a href="/">read more</a><!-- avoid -->

<!-- names that are missing; avoid or add a name -->
<a href="//twitter.com/"><svg/></a><!-- avoid -->
<a href="//linkedin.com"><img src="" alt="" /></a><!-- avoid -->
<button><img/><button><!-- avoid -->

Browsers can sometimes derive names from nearby text, some do this when there is no text to derive a name from. This is not ideal, can lead to wrong and confusing guesses and be bad end user experience. The developer of this control will know best what the thing does and therefore how it should be named.

Missing form labels

Form labels kind of fall under naming things, as they name form elements specifically.

Developers can fix this by ensuring form fields have a <label> element of which the for attribute points to the id of the input/select/textarea , this also makes that clicking label moves the cursor to the input:

<!-- “for” and “id” are same, this connects them -->
<label for="field">Address</label>
<input id="field" />

They can also add an aria-label attribute to the input to name it that way (but be careful, ARIA labels don’t get translated well).

Missing languages

Proper language indication can get you a Pass on two (!) WCAG Success Criteria (3.1.1 and 3.1.2), and you only need to know one attribute for it.

Developers should ensure the <html> element has a lang attribute with the right language:

<!-- marks this document as 'in English -->
<html lang="en">

Sometimes this is forgotten as the <html> element lives in some template you never touch, but it’s not hard to do, so always double check that this attribute exists and is set to the right language.

If there is content on part the page that is in another language, mark that too, again, with a lang attribute on the relevant DOM node. When you pick a plugin for internationalisation, ensure it sets the lang s or makes it easy for you to do so.

CMSes can make sure that lang attributes are set and set correctly. Browsers can guess languages, but they aren’t always good at this, specifically when it comes to distinguishing dialects: they often matter a lot to people, less so to machines.

Wrapping up

These are some tips based on the most common issues that WebAIM Million 2022 identified. Let’s all put in the work to make sure we, our colleagues, our clients and our users avoid these issues. Like, actually. We need WCAG auditors to focus on higher hanging fruit, by fixing the lower hanging stuff for good.

If this is all new to you, hi, thanks for reading! I hope this helps and feel free to get in touch with questions. If you already knew all of this, please keep spreading the word, so that in next year’s survey, we’ll see steady improvements for the web at large.

Want to learn more about fixing low hanging fruit accessibility issues? In response to the same survey, Christian Heilmann blogged about how to fix accessibility issues using your browser


Originally posted as Common accessibility issues that you can fix today on Hidde's blog.

Reply via email

Photoblogging with Sanity and Eleventy

After two years of being mostly inside, I developed an increasing desire to travel. I’m also a millennial developer in the age of social media, so I felt like I needed a way to share where I was going. In this post, I will show how I built a personal photo blog. My tools of choice: Sanity and Eleventy.

Disclaimer: I recently started working at Sanity, so I am biased. Of course, other authoring tools and content platforms exist too; use what works for you etc.

page with header where is hidde and a lot of square photos where.hiddedevries.nl

A wishlist

There are a lot of ways to share photos online, but what would I do if the proverbial sky was the limit?

Control

Something I find particularly important on websites, especially when they have my personal content, is that I have control. Control over where the content lives is important, I want to be able to move my stuff to elsewhere. I want to decide what URLs look like. I also want control over how my content is presented. The very first social network I was on in my teens, allowed for custom CSS and people’s profiles had the wildest layouts, for good and for worse.

Works from a phone

Travel updates are quite place and time specific. I usually find time to write updates while I’m still traveling, so the ability to compose on the go would be a must have. This could have different shapes and forms, of course. For a photo blog, composing photos always happens on the go, obviously, camera phones and cameras are made for portability. But it would be nice to be able to publish photo content on the go.

Can put location data to use

These days, most (phone) cameras can save location data into the image file: latitude, longitude and level. This is fairly privacy sensitive, so I would not always want to share it. On a photo blog though, for photos I picked, it seemed like a useful thing to add, especially since this would be data under my control.

Is a product instead of me

I didn’t want to be the product, I wanted to pay for storing my content, or at least store my data in a place that charges customers for storage.

Lives on an URL I control

To decrease reliance on specific systems, I wanted to set this personal archive up with URLs that I control. This makes it possible for me to move my data to a different URL, without breaking The Web.

Has simple syndication

To ensure that people have an easy way to find new content, I also wanted to implement RSS, or Really Simply Syndication. It points RSS readers to my content, so that they can show it in their timeline.

Building blocks

Ok, so with that wishlist in mind, I started out building myself a photoblog using Sanity, Eleventy and Netlify. Except for Eleventy, these are commercial tools, but they have free tier quota that a personal photo blog will be unlikely to exceed. In any case, they meet the “I’m not the product” item on the wishlist.

Sanity

Let’s start with the Sanity part. As mentioned, I recently joined the Sanity team, and this project is my first deep dive into the product. Or products, really.

Sanity has multiple products that together form an ecosystem that can power your content creation and delivery process.

The first is an authoring tool, called Sanity Studio, that presents a UI that mirrors your content structure. If you have a recipe website, it can have fields for ingredients, method and vegan-friendly, if you are a concert venue, you may want fields for artists, genres and photography. You can have fields that are strings of text, dates or images, to name a few.

There is also a place to store content, called Content Lake, to which you can send data, using the Studio or using anything else that can do HTTP, like curl. This is where content is saved in the structure you define, and without storing any HTML. The Studio saves your stuff in real time, which also comes in handy if you work on content with multiple people simultaneously.

And then there is a query language called GROQ, that you can use to request precisely the content you need in your website. It’s a little bit like GraphQL, for those familiar with that, Sanity supports that too (yay choice!). In my case, if I wanted to query photos, I could ask for them all, or request photos taken in a specific location, with a specific aspect ratio or within a specific colour range. I can then specify which data I want from that selection, such as the description or alternative text.

So, Sanity has a set of tools that each deal with a specific part of your content processes, covering the creation, storing and fetching of content. What it doesn’t offer is a front-end, this is by design.

Eleventy

For building front-end projects, I’m a huge fan of Eleventy, a Node tool that can create static websites. I love it for being unassuming and unopiniated. It is great for progressive enhancement, too, as it doesn’t ship with JavaScript in the runtime if you don’t ask it to. You feed it data, write templates and it will create HTML for you that you can host somewhere.

Eleventy makes it easy to set up an RSS feed with the RSS plugin, set up URLs however you like and output HTML that you have full control over, so that’s a few of my wishlist boxes checked.

Netlify

Which brings me to hosting. When Eleventy has built a folder of static HTML files and assets, we’ll need to put that folder somewhere to see it on the web. We’ll use Netlify for that, as it can conveniently integrate with GitHub as well as Sanity, i.e. do its thing when there are changes in code or content.

Process

With those tools, I built my personal photoblog. Below, I will go into some of the stages I went through from start to finish. I used a starter project, defined a content structure, ensured I could add photos, wrote a query to fetch my data, built a front-end to display that data and then set up automated deployments.

Starter projects

I got started using the Sanity / Eleventy starter project. These starter projects ‘magically’ set up everything you need for a Sanity project: the authoring tool, the data storage, a GitHub repo and some minimal starter code that shows how to query and then use the data you get returned. The starter project tool sets all this up for you and does the necessary plumbing to leave you with working development and production environments.

three screenshots, first two show logged in accounts, the third is not yet logged in The three steps of this starter: connect Sanity, connect to GitHub and connect to Netlify. With these permissions, the starter script sets things up on behalf of you.

Magic and code, not everyone loves it. I might opt to set up each part manually, but it was nice to see the steps in action and have a working environment in minutes.

Content structure

With the setup out of the way, my next task was to figure out how to structure my content. After trying some different structures and ideas, I ended up with just two content types, Photo and Category. For categories, I wanted to save a name. For photos, I wanted to save an image including colour and location data, a text alternative, a title, a date and a Category.

In Sanity, content structure is defined in code, this is part of what my main image type lools like:

{
  name: 'mainImage',
  type: 'image',
  title: 'Image',
  options: {
    hotspot: true,
    metadata: [
      'location',
      'palette'
    ]
  },
  fields: [
    {
      name: 'alt',
      type: 'string',
      title: 'Alternative text',
      description: 'What\'s on this picture?',
      validation: Rule => Rule.error('You have to fill out the alternative text.').required(),
      options: {
        isHighlighted: true
      }
    },
  ],

It has an image field with “hot spot”, so authors can say which part of the photo should be prioritised in case of cropping, location and palette, and it has a field for alternative text which is compulsory.

For images, Sanity strips out most metadata by default. This is a nice privacy feature, but in my case, I wanted location data. so I turned that on manually. I also opted for Sanity to extract colours from uploaded images using the opt-in palette feature. For more information, see Sanity’s Image Metadata documentation.

Adding photos

The authoring tool is in your repo, too. You can run it on a local server, host it with Sanity or host it with any web hosting. It is “just” a Single Page App that does HTTP requests.

In my case, my wishlist said I wanted to do some of this on my phone, so I deployed my Sanity Studio on a URL, in my case on Netlify. I say “I deployed”, but the first Netlify deployment of my Studio was actually set up by the starter project. The Studio works on phones, because it has a responsive UI and not a lot of fluff that requires screen real estate. Sanity’s image upload component is a regular file upload, that, on my phone, was able to take input from my photo library as well as from the camera directly.

My go to way to host a blog or photo content would be to put the content in a git repository, probably using some combination of Markdown for content and Yaml for metadata. For editing on the go, it is actually nice to have a CMS, not content in git, so that I don’t need to find a way to run or trigger add, commit and push commands from my cli-less mobile OS.

Data collection

As mentioned earlier, Sanity supports his query language called GROQ. There is a CSS-Tricks post on how to use GROQ on your data, but I’ll go through my query for content too.

GROQ queries can have these three parts:

  • the dataset with all your documents
  • a filter (which subset of documents you want)
  • a projection (the shape of the data from that subset)

Let’s look at an example that combines the first two parts. First, it requests all content in my dataset (*) , then it filters for data of the “post” type that has a slug and has a “published” date before now (so anything that’s not set to be published in the future):

*[_type == "post" && defined(slug) && publishedAt < now()]

Then, from that data, we can define a specific set that we need for the photo blog:

{
  _id,
  publishedAt,
  title,
  slug,
  mainImage {
    ..., 
    asset->
  }
}

This requests an object of a specific shape for each post that is returned. The _id is an internal property, publishedAt and title are the publish date and a title, the slug is a URL friendly string and mainImage has a pointer to where the image lives.

From the mainImage, we’re specifically requesting two things with the projection ({}):

  1. First, we “spread” all the properties of the mainImage object with ..., a bit like the spread syntax in JavaScript.
  2. Then, we also join in the reference found in asset->.

The thing is, my posts don’t contain the image data, they contain references to the image data. So if I had asked for asset, I would only get access to an ID that I could use to find the referenced asset. The good news is that I don’t actually need to find it myself, as with asset->, we’re getting the data of the referenced asset and use it right there. In other words, the properties that the image asset object had are returned in place.

A “reference” is an indexed pointer to another document that you can query. In this case, the relationship is between a post and an image. This works two ways. If you’re listing images, for each image, you can request the post it relates to. If you’re listing posts, you can request the image that it relates to.

Front-end

For the front-end, I’ve gone with a page that lists all photos and a simple header and footer. There are also individual pages for each photo.

Image URL fun

Sanity has a lot of features that you can access by manipulating your image’s URL. If you know the image URL, you can add parameters to request:

  • specific crops; I use this for square images on my index page)
  • formats; I use “auto formats” so that the CDN returns webp’s to browsers that support them, yay performance
  • manipulated versions of your images, eg with different resolutions and sharpnesses

Photo-based header colours

On the single photo page, the background of the header has the colour of the most prominent colour in the photo. I was able to build this, because Sanity can return a palette object with a bunch of colour info, including the most prominent and a color that contrasts with it. In my template, I use the HEX value from this palette property to overwrite the CSS variable that the header’s color and background-color are set with.

In the (Nunjucks) template for a single photo, I have this HTML snippet:

<style>
body {
  --header-background-color: {{ palette.darkVibrant.background; 
  --header-color: {{ palette.darkVibrant.title }};
}
</style>

For the header colour, my site’s main stylesheet has a custom property, as CSS’s built-in variables are called. With this snippet, I set it again. Custom properties in CSS take part in the cascade, CSS’s system to figure out which style to use when style rules compete. In this case, the definition in my main stylesheet and the one in this template compete, and the latter wins.

Blurry previews

Photos can take time to load, so I wanted to display something while you’re waiting. I’ve never been a fan of spinners, or, unpopular opinion, skeletons, but I found Sanity can return a ‘blurhash’. This is a string that looks like this:

It can be converted into a very blurry and small image that is specifically useful to show while waiting for the full image.

This is mostly useful for users on slower connections.

Deployment

Own URL

My personal site is hiddedevries.nl and I want to use where.hiddedevries.nl for this project. I set up my nameservers so that they point to Netlify, where I’m hosting the website. The site now mostly meets the “own your URL” requirement, and if I get unhappy with my current setup, I can move my data elsewhere.

One aspect where I don’t own my URLs though, is image assets. When I requested images with GROQ earlier, I got a URL of Sanity’s CDN. This has end user benefits, including that it can serve assets from the data centre that is closest to you, caching and, less of an issue with HTTP/2, allows for more more concurrent downloads by having a separate hostname. For full control and with the same benefits, you might want this on your own separate hostname too. I’m told this is currently only possible in Sanity’s enterprise plan.

Hooks

Our generated files from Eleventy are on Netlify and need to be deployed to Netlify. When is a good time? Probably when either the code, on GitHub, or the content, in Sanity, change. We can keep Netlify posted of changes in either using webhooks:

  • GitHub can tell Netlify about changes, these are set in the Netlify settings
  • Sanity has GROQ-powered webhooks: given a GROQ query on your content, whenever the queried set of data changes, it can perform a HTTP request to a URL (and yes, that can be a Netlify build hook’s URL)

Whenever either of these happen, Netlify will run my build process. The build process comes down to:

  • it grabs my latest data from Sanity (the response to my GROQ query, including photo URLs, metadata like titles, text alternatives, location and colour palette)
  • it runs Eleventy to combine the data with my photoblog HTML/CSS
  • it places the folder that Eleventy outputs on the server

Wrapping up

In this post, I’ve shown how I built a personal photo blog, managing content with Sanity, generating a static site with Eleventy and hosting that site on Netlify. That’s a lot of concerns, but I feel like they are separated in a sensible way, where each piece could be replaced by some other piece if needed.

I didn’t manage to check all of the boxes: uploading photos with location data in iOS and Android seems to be impossible, they are stripped upon uploading, probably for privacy (may be continued in another blog post). To have fun with location data, I will have to upload photos from desktop.

This was a fun learning experience, thanks for reading it, I hope it was helpful! You can look at the website at where.hiddedevries.nl. The code is on hidde/where-is-hidde, the schem for a post is in post.js.


The post Photoblogging with Sanity and Eleventy was first posted on hiddedevries.nl blog | Reply via email

Photo blogging with Sanity and Eleventy

After two years of being mostly inside, I developed an increasing desire to travel. I’m also a millennial developer in the age of social media, so I felt like I needed a way to share where I was going. In this post, I will show how I built a personal photo blog. My tools of choice: Sanity and Eleventy.

Disclaimer: I recently started working at Sanity, so I am biased. Of course, other authoring tools and content platforms exist too; use what works for you etc.

page with header where is hidde and a lot of square photos where.hiddedevries.nl

A wishlist

There are a lot of ways to share photos online, but what would I do if the proverbial sky was the limit? Here’s some things I wanted for my photo blog.

Control

Something I find particularly important on websites, especially when they have my personal content, is that I have control. Control over where the content would live was important, I wanted to be able to move my stuff to elsewhere. I wanted to decide what URLs looked like. I also wanted control over how my content was presented.

Works from a phone

Travel updates are particularly place and time specific. I usually find time to write updates while I’m still traveling, so the ability to compose on the go would be a must have. This could have different shapes and forms. The photo-taking would always be on the go, of course, phones and cameras are very portable. But I also wanted to publish photo content from a mobile device. In my case that meant not using git for content, but a CMS (!).

Can put location data to use

These days, most (phone) cameras can save location data into the image file: latitude, longitude and level. This is fairly privacy sensitive, so I would not always want to share it. On a photo blog though, for photos I picked, it seemed like a useful thing to add, especially since this would be data under my control.

Is a product instead of me

I didn’t want to be the product, I wanted to pay for storing my content, or at least store my data in a place that charges customers for storage.

Lives on a URL I control

To decrease reliance on specific systems, I desired to set this personal archive up with URLs that I control. This makes it possible for me to move my data to a different URL, without breaking The Web.

Has simple syndication

To ensure that people have an easy way to find new content, I also wanted to implement RSS, or Really Simply Syndication. It points RSS readers to my content, so that they can show it in their timeline.

Building blocks

Ok, so with that wishlist in mind, I started out building myself a photoblog using Sanity, Eleventy and Netlify. Except for Eleventy, these are commercial tools, but they have free tier quota that a personal photo blog will be unlikely to exceed. In any case, they meet the “I’m not the product” item on the wishlist.

Sanity

Let’s start with the Sanity part. As mentioned, I recently joined the Sanity team, and this project is my first deep dive into the product. Or products, really.

Sanity has multiple products that together form an ecosystem that can power your content creation and delivery process.

The first is an authoring tool, called Sanity Studio, that presents a UI that mirrors your content structure. If you have a recipe website, it can have fields for “ingredients”, “method” and “vegan-friendly”, if you are a concert venue, you may want fields for artists, genres and photography. You can have fields that are strings of text, dates or images, to name a few.

There is also a place to store content, called Content Lake, to which you can send data, using the Studio or using anything else that can do HTTP, like curl. This is where content is saved in the structure you define, and without storing any HTML. The Studio saves your stuff in real time, which also comes in handy if you work on content with multiple people simultaneously.

And then there is a query language called GROQ, that you can use to request precisely the content you need in your website (or any JSON file, also outside Sanity). It’s a little bit like GraphQL, for those familiar with that, Sanity supports that too (yay choice!). In my case, if I wanted to query photos, I could ask for them all, or request photos taken in a specific location or with a specific aspect ratio. I can then specify which data I want from that selection, such as the description or alternative text.

So, Sanity has a set of tools that each deal with a specific part of your content processes, covering the creation, storing and fetching of content. What it doesn’t offer is a front-end, this is by design.

Eleventy

For building front-end projects, I’m a huge fan of Eleventy, a Node tool that can create static websites. I love it for being unassuming and unopiniated. It is great for progressive enhancement, too, as it doesn’t ship with JavaScript in the runtime if you don’t ask it to. You feed it data, write templates and it will create HTML for you that you can host somewhere.

Eleventy makes it easy to set up an RSS feed with the RSS plugin, set up URLs however you like and output HTML that you have full control over, so that’s a few of my wishlist boxes checked.

Netlify

Which brings me to hosting. When Eleventy has built a folder of static HTML files and assets, we’ll need to put that folder somewhere to see it on the web. We’ll use Netlify for that, as it can conveniently integrate with GitHub as well as Sanity, i.e. do its thing when there are changes in code or content.

Process

With those tools, I built my personal photoblog. Below, I will go into some of the stages I went through from start to finish. I used a starter project, defined a content structure, ensured I could add photos, wrote a query to fetch my data, built a front-end to display that data and then set up automated deployments.

Starter projects

I got started using the Sanity / Eleventy starter project. These starter projects ‘magically’ set up everything you need for a Sanity project: the authoring tool, the data storage, a GitHub repo and some minimal starter code that shows how to query and then use the data you get returned. The starter project tool sets all this up for you and does the necessary plumbing to leave you with working development and production environments.

three screenshots, first two show logged in accounts, the third is not yet logged in The three steps of this starter: connect Sanity, connect to GitHub and connect to Netlify. With these permissions, the starter script sets things up on behalf of you.

Magic and code, not everyone loves it. I might opt to set up each part manually, but it was nice to see the steps in action and have a working environment in minutes.

Content structure

With the setup out of the way, my next task was to figure out how to structure my content. After trying some different structures and ideas, I ended up with just two content types, Photo and Category. For categories, I wanted to save a name. For photos, I wanted to save an image including colour and location data, a text alternative, a title, a date and a Category.

In Sanity, content structure is defined in code, this is part of what my main image type lools like:

{
  name: 'mainImage',
  type: 'image',
  title: 'Image',
  options: {
    hotspot: true,
    metadata: [
      'location',
      'palette'
    ]
  },
  fields: [
    {
      name: 'alt',
      type: 'string',
      title: 'Alternative text',
      description: 'What\'s on this picture?',
      validation: Rule => Rule.error('You forgot to describe this image').required(),
      options: {
        isHighlighted: true
      }
    },
  ],

It has an image field with “hot spot”, so authors can say which part of the photo should be prioritised in case of cropping and it has a compulsory field for alternative text.

For images, Sanity strips out most metadata by default. In my case, I actually wanted to share location data, so I turned that on manually, adding location to the content structure. I also opted for Sanity to extract colours from uploaded images using the opt-in palette feature. For more information, see Sanity’s Image Metadata documentation.

Adding photos

The authoring tool is in your repo, too. You can run it on a local server, host it with Sanity or host it with any web hosting. It is “just” a Single Page App that does HTTP requests.

post editor on desktop view and mobile view plus screenshot of outputted JSON Editing a post on desktop and mobile

In my case, my wishlist said I wanted to do some of this on my phone, so I deployed my Sanity Studio on a URL, in my case on Netlify. I say “I deployed”, but the first Netlify deployment of my Studio was actually set up by the starter project. The Studio works on phones, because it has a responsive UI and not a lot of fluff that requires screen real estate. Sanity’s image upload component is a regular file upload, that, on my phone, was able to take input from my photo library as well as from the camera directly.

A common, Jamstacky way to host a blog or photo content would be to put the content in a git repository, probably using some combination of Markdown for content and Yaml for metadata. I do that for my Books, and it works ok for me. But for editing on the go, it is actually nice to have a CMS, not content in git. It saves from the trouble of finding a way to run or trigger add, commit and push commands from my cli-less mobile OS.

Data collection

As mentioned earlier, Sanity supports this query language called GROQ. There is a CSS-Tricks post on how to use GROQ on your data, but I’ll go through my query for content too.

GROQ queries can have these three parts:

  • the dataset with all your documents
  • a filter (which subset of documents you want)
  • a projection (the shape of the data from that subset)

Let’s look at an example that combines the first two parts. First, it requests all content in my dataset (*) , then it filters for data of the “post” type that has a slug and has a “published” date before now (so anything that’s not set to be published in the future):

*[_type == "post" && defined(slug) && publishedAt < now()]

Then, from that data, we can define a specific set that we need for the photo blog:

{
  _id,
  publishedAt,
  title,
  slug,
  mainImage {
    ..., 
    asset->
  }
}

This requests an object of a specific shape for each post that is returned. The _id is an internal property, publishedAt and title are the publish date and a title, the slug is a URL friendly string and mainImage has a pointer to where the image lives.

From the mainImage, we’re specifically requesting two things with the projection ({}):

  1. First, we “spread” all the properties of the mainImage object with ..., a bit like the spread syntax in JavaScript.
  2. Then, we also join in the reference found in asset->.

The thing is, my posts don’t contain the image data, they contain references to the image data. So if I had asked for asset, I would only get access to an ID that I could use to find the referenced asset. The good news is that I don’t actually need to find it myself, as with asset->, we’re getting the data of the referenced asset and use it right there. In other words, the properties that the image asset object had are returned in place.

A “reference” is an indexed pointer to another document that you can query. In this case, the relationship is between a post and an image. This works two ways. If you’re listing images, for each image, you can request the post it relates to. If you’re listing posts, you can request the image that it relates to.

Front-end

For the front-end, I’ve gone with a page that lists all photos and a simple header and footer. There are also individual pages for each photo.

Image URL fun

Sanity has a lot of features that you can access by manipulating your image’s URL. If you know the image URL, you can add parameters to request:

  • specific crops; I use this for square images on my index page
  • formats; I use “auto formats” so that the CDN returns webp’s to browsers that support them, yay performance
  • manipulated versions of your images, eg with different resolutions and sharpnesses

Photo-based header colours

On the single photo page, the background of the header has the colour of the most prominent colour in the photo. I was able to build this, because Sanity can return a palette object with a bunch of colour info, including the most prominent and a color that contrasts with it. In my template, I use the HEX value from this palette property to overwrite the CSS variable that the header’s color and background-color are set with.

In the (Nunjucks) template for a single photo, I have this HTML snippet:

<style>
body {
  --header-background-color: {{ palette.darkVibrant.background }}; 
  --header-color: {{ palette.darkVibrant.title }};
}
</style>

For the header colour, my site’s main stylesheet has a custom property, as CSS’s built-in variables are called. With this snippet, I overwrite it. Custom properties in CSS take part in the cascade, CSS’s system to figure out which style to use when style rules compete. In this case, the definition in my main stylesheet and the one in this template compete, and the latter wins.

Blurry previews

Photos can take time to load, so I pondered displaying something while you’re waiting. I’ve never been a fan of spinners, or, unpopular opinion, skeletons, but I found Sanity can return a ‘blurhash’. This is a string that looks like this:

d79Z$I-o4:IoxaofR*WC00Io?GxtM{Rkt7s:~VxaNGRk

It can be converted into a very blurry and small image that is specifically useful to show while waiting for the full image. This is mostly useful for users on slower connections. I have left this out from the final site.

Deployment

Own URL

My personal site is hiddedevries.nl and I want to use where.hiddedevries.nl for this project. I set up my nameservers so that they point to Netlify, where I’m hosting the website. The site now mostly meets the “own your URL” requirement, and if I get unhappy with my current setup, I can move my data elsewhere.

One aspect where I don’t own my URLs though, is image assets. When I requested images with GROQ earlier, I got a URL of Sanity’s CDN. This has end user benefits, including that it can serve assets from the data centre that is closest to you, caching and, less of an issue with HTTP/2, allows for more more concurrent downloads by having a separate hostname. For full control and with the same benefits, you might want this on your own separate hostname too. I’m told this is currently only possible in Sanity’s enterprise plan. If it’s your thing, you could totally build some sort of proxy yourself, too.

Hooks

Our generated files from Eleventy are on Netlify and need to be deployed to Netlify. When is a good time? Probably when either the code, on GitHub, or the content, in Sanity, change. We can keep Netlify posted of changes in either using webhooks:

  • GitHub can tell Netlify about changes, these are set in the Netlify settings
  • Sanity has GROQ-powered webhooks: given a GROQ query on your content, whenever the queried set of data changes, it can perform a HTTP request to a URL (and yes, that can be a Netlify build hook’s URL)

Whenever either of these happen, Netlify will run my build process. The build process comes down to:

  • it grabs my latest data from Sanity (the response to my GROQ query, including photo URLs, metadata like titles, text alternatives, location and colour palette)
  • it runs Eleventy to combine the data with my photoblog HTML/CSS
  • it places the folder that Eleventy outputs on the server

Wrapping up

In this post, I’ve shown how I built a personal photo blog, managing content with Sanity, generating a static site with Eleventy and hosting that site on Netlify. That’s a lot of concerns, but I feel like they are separated in a sensible way, where each piece could be replaced by some other piece if needed.

I didn’t manage to check all of the boxes: uploading photos with location data in iOS and Android seems to be impossible, they are stripped upon uploading, probably for privacy (may be continued in another blog post). To have fun with location data, I will have to upload photos from desktop.

This was a fun learning experience, thanks for reading it, I hope it was helpful! You can look at the website at where.hiddedevries.nl. The code is on hidde/where-is-hidde, the schema for a post is in post.js.

Thanks Knut and Kilian for feedback on earlier versions.


The post Photo blogging with Sanity and Eleventy was first posted on hiddedevries.nl blog | Reply via email