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 React's suite of dependencies + the Slinkity React renderer:

npm i -D react react-dom @slinkity/renderer-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/renderer-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 from the __functions prop as javascript functions. 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 those wondering "what are shortcodes and filters:" they're universal helpers you can access from any page template, no imports necessary. These are especially useful in not-so-JavaScript templates like Nunjucks and markdown. We recommend visiting 11ty.rocks for concrete examples!

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
export default function About({ __functions }) {
const name = 'darth Plagueis the Wise'
return (
<article>
<h2>A tragic tale</h2>
<p>Did YOU ever hear the Tragedy of {__functions.capitalize(name)}?</p>
</article>
)
}

Dynamic permalinks are incredibly 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) {
// note: shortcodes and filters are available
// from __functions. We're using 11ty's built-in
// slugify filter here.
const { __functions, title } = eleventyData
return \`/${__functions.slugify(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 some 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 hydrate front matter prop:

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

export const frontMatter = {
hydrate: true,
}

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>
</>
)
}

To render only client-side without server rendering, try switching from the hydrate prop to renderWithoutSSR. We only recommend this for components that 100% can't be rendered server-side:

frontMatter: {
- hydrate: true / "onClientIdle" / "onClientMedia(...)"
+ renderWithoutSSR: true / "onClientIdle" / "onClientMedia(...)"
}

Like component shortcodes, you're free to use any of our partial hydration modes ("onComponentVisible", "onClientMedia(...)", 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 use a special hydrate.props function from your front matter like so:

// about.jsx
export const frontMatter = {
hydrate: {
mode: true,
// the result of this function
// will be passed to your component as props
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:

  1. We update hydrate: true to hydrate: { mode: true }
  2. We include a hydrate.props function for Slinkity to decide which props our component needs
  3. Slinkity runs this function at build time (not on the client!) to decide which props to generate
  4. 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
)
}
}

Can I call frontMatter.hydrate.props() inside my components?

Section titled "Can I call frontMatter.hydrate.props() inside my components?"

Technically yes, but we wouldn't recommend it. Note that Slinkity calls this function at build-time to figure out which resources to bundle. In other words, it's not meant to re-run in the browser. This is very similar to NextJS' getStaticProps or NuxtJS' data fetchers.

Oh, and for more on hydration options...

Learn the different ways to render components โ†’