Skip to main content

Component pages

You're free to use React, Vue, Svelte, and more to create page-level templates. Let's learn how!

First, install Preact + the Slinkity Preact renderer:

npm i -D preact @slinkity/preact

Then, add this renderer to a slinkity.config.js at the base of your project:

// .eleventy.js or eleventy.config.js
const slinkity = require('slinkity')
const preact = require('@slinkity/preact')

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(
slinkity.plugin,
slinkity.defineConfig({
renderers: [preact()],
})
)
}

First, install React's suite of dependencies + the Slinkity React renderer:

npm i -D react react-dom @slinkity/react

Then, add this renderer to a slinkity.config.js at the base of your project:

// .eleventy.js or eleventy.config.js
const slinkity = require('slinkity')
const react = require('@slinkity/react')

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(
slinkity.plugin,
slinkity.defineConfig({
renderers: [react()],
})
)
}

Think of component pages like any other template on your 11ty site. For instance, you can add a component-driven /about page alongside your others routes like so:

index.html
blog.md
about.jsx|.vue|.svelte

...And you're ready to start templating. If you're following along at home, you'll want to add some content to this file:

// about.jsx
export default function About() {
return (
<article>
<h2>A tragic tale</h2>
<p>Did YOU ever hear the Tragedy of Darth Plagueis the Wise?</p>
</article>
)
}

Now, you should see a tragic tale on /about.

Before frantically Googling "state variables don't work in Slinkity template," This is intentional! We avoid hydrating your component clientside by default. To opt-in to using useState, vue refs, and the like, jump to our hydration section ๐Ÿ’ง

If you're familiar with 11ty, you've likely worked with front matter before. It allows you to associate "data" with your current template, which can be picked up by layouts, 11ty's collections API, and more. See 11ty's front matter documentation for full details.

For example, let's say you have a simple layout in your project called _includes/base.njk. This layout will:

  1. Inject a given route's title property into the page <title>
  2. Apply the content of that layout between some <body> tags
<!--_includes/base.njk-->
<html lang="en">
<head>
<title>{{ title }}</title>
</head>
<body>
{{ content | safe }}
</body>
</html>

You can apply this layout to your /about page using front matter:

// about.jsx
export const frontmatter = {
title: 'A tragic tale',
layout: 'base.njk',
}

function About() {...}

You've pushed data up into the data cascade using front matter. So how do you pull data back down in your components?

Assuming your page isn't hydrated (see how hydrated props work), all 11ty data is magically available as props.

Say you have a list of incredible, amazing, intelligent Slinkity contributors in a global data file called _data/contributors.json:

[
{ "name": "Ben Myers", "ghProfile": "https://github.com/BenDMyers" },
{ "name": "Anthony Campolo", "ghProfile": "https://github.com/ajcwebdev" },
{ "name": "Thomas Semmler", "ghProfile": "https://github.com/nachtfunke" }
]

Since all _data files are piped into 11ty's data cascade, this is now available to your component page via the contributors prop:

// about.jsx
export default function About({ contributors }) {
return (
<ul>
{contributors.map(({ name, ghProfile }) => (
<li><a href={ghProfile}>{name}</a></li>
))}
</ul>
)
}

To get the most out of these data props, we recommend learning more about the 11ty data cascade. Here's some helpful resources:

All shortcodes and filters are accessible with the useFunctions helper. This includes any shortcode or filter created using the following eleventy config helpers:

// .eleventy.js
module.exports = function(eleventyConfig) {
// Universal filter
eleventyConfig.addFilter("capitalize", function(value) { โ€ฆ });

// Universal shortcode
eleventyConfig.addShortcode("capitalized", function(value) { โ€ฆ });

// Universal paired shortcode
eleventyConfig.addPairedShortcode("capitalized", function(content, value) { โ€ฆ });

// JavaScript Template Function
eleventyConfig.addJavaScriptFunction("capitalize", function(value) { โ€ฆ });
};

For instance, say you registered a capitalize filter like so:

// .eleventy.js
module.exports = function(eleventyConfig) {
eleventyConfig.addFilter('capitalize', function(value) {
const firstLetter = value[0]
const restOfPhrase = value.slice(1)
return firstLetter.toUpperCase() + restOfPhrase
})
}

You can access this filter across your component pages like so:

// about.jsx
import { useFunctions } from '@slinkity/preact/server';

export default function About({ __functions }) {
const { capitalize } = useFunctions();
const name = 'darth Plagueis the Wise'
return (
<article>
<h2>A tragic tale</h2>
<p>Did YOU ever hear the Tragedy of {capitalize(name)}?</p>
</article>
)
}

Dynamic permalinks are useful when generating a URL from 11ty data. You may be used to template strings when using plain 11ty (say, using Nunjucks to output a URL). But with Slinkity, you have the power of JavaScript functions at your disposal ๐Ÿ˜Ž

Section titled "Example - Generate a permalink from a page title"

Say you want to generate a blog post's URL from its title. Since front matter is available from the 11ty data object, you can use a permalink() function like so:

// about.jsx
export const frontmatter = {
title: 'A tragic tale',
permalink(eleventyData, functions) {
// note: shortcodes and filters are available
// from `functions`. We're using 11ty's built-in
// `slugify` filter here.
return \`/${functions.slugify(eleventyData.title)}/\`
},
}

export default function About() {...}

Here's how your site's input / output directories will look, assuming _site is your output and src is your input:

โ”œโ”€โ”€ _site
โ”‚ โ””โ”€โ”€ a-tragic-tale
โ”‚ โ””โ”€โ”€index.html
โ”œโ”€โ”€ src
โ”‚ โ””โ”€โ”€ about.jsx|vue|svelte

Pagination is another common use case for dynamic permalinks. We won't go too in depth on 11ty's pagination options (see their docs for full details), but we'll cover the primary use case: generate routes from an array of data.

Say that:

  1. You have an array of T-shirts to sell on your e-commerce site
  2. You want to generate a unique route to preview each T-shirt

That list of T-shirts may look like this (_data/tshirts.json):

[
{
"name": "Me and the Possum Posse",
"slug": "possum-posse",
"image": "assets/possum-posse.jpg"
},
{
"name": "It possumtimes be like that",
"slug": "possumtimes",
"image": "assets/possumtimes.jpg"
}
]

You can generate routes for each of these T-shirts using the pagination and permalink properties like so:

// tshirt.jsx
export const frontmatter = {
pagination: {
// name of your data
data: 'tshirts',
// number of routes per array element
size: 1,
// variable to access array element values
// from your permalink fn and your component page
alias: 'tshirt'
},
// note the trailing "/" here!
permalink: ({ tshirt }) => \`/${tshirt.slug}/\`
}

export default function Tshirt({ tshirt }) {
return (
<article>
<h1>{tshirt.name}</h1>
<img src={tshirt.image} alt={tshirt.name} />
</article>
)
}

Here's how your site's input / output directories will look, assuming _site is your output and src is your input:

โ”œโ”€โ”€ _site
โ”‚ โ””โ”€โ”€ possum-posse
โ”‚ โ””โ”€โ”€ index.html
โ”‚ โ””โ”€โ”€ possumtimes
โ”‚ โ””โ”€โ”€ index.html
โ”œโ”€โ”€ src
โ”‚ โ””โ”€โ”€ tshirt.jsx|vue|svelte

We've used components as build-time templating languages. Now let's add some JavaScript into the mix ๐Ÿฅ—

You can enable client-side rendering using the island export:

// about.jsx
import { useState } from 'react'

export const island = {
when: 'client:load',
}

export default function About() {
const [count, setCount] = useState(0)

return (
<>
<p>You've had {count} glasses of water ๐Ÿ’ง</p>
<button onClick={() => setCount(count + 1)}>Add one</button>
</>
)
}

Like component shortcodes, you're free to use any of our client: directives (client:visible, client:idle, and more).

Props work a bit differently now that JS is involved. In order to access 11ty data from your component, you'll need to choose which pieces of data you need.

For instance, say you need to access that same global contributors list from earlier. You can add a props function to your island export to access this data and pass the pieces you need to the client:

// about.jsx
export const island = {
when: 'client:load',
props: (eleventyData) => ({
contributors: eleventyData.contributors,
})
}
export default function About({ contributors }) {
return (
<ul>
{contributors.map(({ name, ghProfile }) => (
<li><a href={ghProfile}>{name}</a></li>
))}
</ul>
)
}

A few takeaways here:

  • We include an island.props function for Slinkity to decide which props our component needs
  • Slinkity runs this function at build time (not on the client!) to decide which props to generate
  • These props are accessible from the browser-rendered component

๐Ÿšจ (Important!) Be mindful about your data

Section titled "๐Ÿšจ (Important!) Be mindful about your data"

You may be wondering, "why can't all the eleventyData get passed to my component as props? This seems like an extra step."

Well, it all comes down to the end user's experience. Remember that we're sending your JS-driven component to the browser so pages can be interactive. If we sent all that eleventyData along with it, the user would have to download that huge data blob for every component on your site. ๐Ÿ˜ฎ

So, we added hydrate.props as a way to pick the data that you need, and "filter out" the data that you don't.

11ty's collections object is a prime example of where hydrate.props shines. This object contains references to every page on your site, plus all the data those pages receive. Needless to say, that blob can get pretty big! We suggest you:

// โŒ Don't pass everything
props({ collections }) {
return { collections }
}
// โœ… Map out the pieces you need
props({ collections }) {
return {
blogPostUrls: collections.blogPosts.map(
blogPost => blogPost.page.url
)
}
}

Now that we've discussed page hydration, let's learn how to choose if and when that JS loads.

Learn about client: directives โ†’