If you want a lighter version, without eleventy dependency, try pequeno
https://eleventy-react.netlify.app/
Eleventy is a great static site generator, but I don’t like template engines. They introduce a lot of custom syntax, and it’s almost impossibile to convert them to a component-based dev flow.
git clone git@github.com:signalkuppe/eleventy-react.git eleventy-react
cd eleventy-react
npm install
npm run dev
Build you site for deploy
npm run build
just add a data export
to your jsx file
import React from 'react';
import DefaultLayout from '../components/layout/Default';
import P from '../components/primitives/P';
import Span from '../components/primitives/Span';
import H1 from '../components/primitives/H1';
// custom template data
export const data = {
name: 'John Doe',
};
export default function Index({ site, name }) {
// site comes from site.js data file in _data
const { title } = site;
return (
<DefaultLayout>
<H1>{title}</H1>
<P>
This name is from custom template data: <Span bold>{name}</Span>
</P>
</DefaultLayout>
);
}
import React from 'react';
import { slug } from './utils/user';
import DefaultLayout from '../components/layout/Default';
import H1 from '../components/primitives/H1';
/**
* A template that renders a page for each user in the users collection
*/
export const data = {
pagination: {
data: 'users',
size: 1,
alias: 'user',
},
permalink: function (data) {
return slug(data.user);
},
};
export default function User({ site, user }) {
return (
<DefaultLayout>
<H1>{user.name}</H1>
</DefaultLayout>
);
}
Write your content in markdown format, and use .jsx files as layouts. Print html markup with a custom withHtml HOC that uses our primitives styles
_includes/layouts/post.jsx
import React from 'react';
import DefaultLayout from '../../../components/layout/Default';
import Section from '../../../components/primitives/Section';
import Img from '../../../components/primitives/Img';
import Span from '../../../components/primitives/Span';
import VerticalSpace from '../../../components/ui/VerticalSpace';
import withHtml from '../../../components/hoc/withHtml';
import { postSlug } from '../../../components/features/posts/utils';
/**
* A template that renders a page for each post in the post collection (_posts/*.md)
*/
const PostBodySection = withHtml(Section);
export const data = {
permalink: function (data) {
return postSlug(data.page);
},
};
export default function Post(data) {
const { title, content, cover, tags } = data;
return (
<DefaultLayout>
<Img src={cover} alt={title} />
<PostBodySection>{content}</PostBodySection>
{tags?.length && (
<>
<VerticalSpace />
{tags.map((tag, i) => (
<Span key={i} italic>
{tag}
{i < tags.length - 1 && ', '}
</Span>
))}
</>
)}
</DefaultLayout>
);
}
styles are extracted and inserted into the <head>
tag
import React from 'react';
import styled from 'styled-components';
const StyledText = styled.span`
font-weight: ${(props) => {
if (props.bold) {
return 700;
} else {
return 400;
}
}};
`;
export default function Span({ children, ...props }) {
return <StyledText {...props}>{children}</StyledText>;
}
Integration with storybook
Develop your UI in isolation adding stories for your components and run storybook with
npm run storybook
Build storybook inside eleventy’s _site folder
npm run build-storybook
stories are written in .mdx format
basic ui/primitives/features components included to give an idea of the approach
import and use inline svgs in your react components
- @babel/plugin-proposal-optional-chaining
React is not included in the build, so you can use any js approach on the client. I think that adding hydration would go against Eleventy’s philosophy.
Developers like to write code in a component-fashioned way so there’s a helper to deal with client-side js code.
Say you have a component that need some vanilla client-side js logic and maybe an external library, like an accordion.
Just add the <Script>
component in you code like this
import React, { Fragment } from 'react';
import Dl from '../../primitives/Dl';
import Dt from '../../primitives/Dt';
import Dd from '../../primitives/Dd';
import Button from '../../primitives/Button';
import Script, { outputLibDir } from '../Script';
import client from './index.client.js';
export default function Accordion({ items }) {
return (
<>
<Dl reset id="accordion">
{items.map((item, i) => (
<Fragment key={i}>
<Dt>
<Button>{item.title}</Button>
</Dt>
<Dd>{item.description}</Dd>
</Fragment>
))}
</Dl>
<Script
libs={[
{
js: `/${outputLibDir}/fisarmonica/src/fisarmonica.js`,
css: `/${outputLibDir}/fisarmonica/src/fisarmonica.css`,
},
]}
>
{client}
</Script>
</>
);
}
With the libs param you can add as many libraries as you want, adding the required js and css files.
Write you logic in a .client.js file in the component folder, import it and place it as a children of the <Script>
component.
Styles and scripts will be extracted at build time and placed in the right place in the DOM. There is also a Storybook decorator that adds the client-side logic to stories
Be sure to pass the libs installed via npm to the output folder in the .eleventy.js
config file.
// add this client side js lib to our otuput dir
eleventyConfig.addPassthroughCopy({
'node_modules/fisarmonica': `${config.outputLibDir}/fisarmonica`,
});
Sometimes you have to use your theme variables in client-side logic.
A global THEME
variable containing our styled-components settings is exposed to the client.
const THEME = {
colors: {
background: '#282c34',
backgroundDark: '#20232a',
primary: '#61dafb',
white: 'white',
grey: '#32363e',
},
type: {
fontSans: '-apple-system, “Segoe UI”, “Roboto”',
fontMono: 'source-code-pro, Menlo, Monaco, Consolas, monospace',
leading: 1.4,
root: '112.5%',
base: '1rem',
headingsBase: 2,
scale: 1.333,
},
};
- reduce storybook bundle site for production build
- test other eleventy’s features
- use React components in _data files
Requires experimental features in Eleventy, specifically: Custom File Extension Handlers feature from Eleventy