{"id":15677712,"url":"https://github.com/seancdavis/twenty-ninety","last_synced_at":"2025-06-12T02:34:32.861Z","repository":{"id":146757605,"uuid":"286718192","full_name":"seancdavis/twenty-ninety","owner":"seancdavis","description":"A production-ready Eleventy starter kit, optimized for performance.","archived":false,"fork":false,"pushed_at":"2020-09-13T11:58:25.000Z","size":1351,"stargazers_count":17,"open_issues_count":3,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-07T01:42:34.554Z","etag":null,"topics":["eleventy","gulp","netlify","postcss","tailwindcss","webpack"],"latest_commit_sha":null,"homepage":"https://www.seancdavis.com/twenty-ninety","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seancdavis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-11T10:45:51.000Z","updated_at":"2025-02-23T18:31:05.000Z","dependencies_parsed_at":"2023-05-21T04:30:45.443Z","dependency_job_id":null,"html_url":"https://github.com/seancdavis/twenty-ninety","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/seancdavis/twenty-ninety","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seancdavis%2Ftwenty-ninety","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seancdavis%2Ftwenty-ninety/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seancdavis%2Ftwenty-ninety/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seancdavis%2Ftwenty-ninety/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seancdavis","download_url":"https://codeload.github.com/seancdavis/twenty-ninety/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seancdavis%2Ftwenty-ninety/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259383604,"owners_count":22849082,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eleventy","gulp","netlify","postcss","tailwindcss","webpack"],"created_at":"2024-10-03T16:10:28.454Z","updated_at":"2025-06-12T02:34:32.817Z","avatar_url":"https://github.com/seancdavis.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Twenty Ninety [![Netlify Status](https://api.netlify.com/api/v1/badges/40e8672c-0101-4d88-8dbc-225257970fb4/deploy-status)](https://app.netlify.com/sites/twenty-ninety/deploys)\n\nTwenty Ninety is a component-based [Eleventy](https://www.11ty.dev/) starter kit. It comes with lots of opinions, all designed to help you build websites faster (and have fun doing it)!\n\n## Getting Started\n\nIf you want to just go for it, you can deploy this project directly to Netlify:\n\n[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/seancdavis/twenty-ninety)\n\nIf you want to start by working on your machine, first clone the repo:\n\n    $ git clone https://github.com/seancdavis/twenty-ninety.git your-project-name\n\nInstall dependencies:\n\n    $ yarn install\n\n(Note: If you prefer NPM over Yarn, you run `npm install` instead, then delete `yarn.lock`.)\n\nThen start the development server:\n\n    $ yarn develop\n\nSee below for a list of features and how to work with them.\n\n## Project Goals\n\nEleventy is a wonderful static site generator. It's quick. It gets out of the way. It lets you work how you want to work. While that's fun and flexible, [it's not efficient](https://cobwwweb.com/increase-developer-efficiency-by-establishing-conventions).\n\nThis project exists to provide a foundation on which we can build websites consistently and efficiently over time.\n\nAs a starter kit, the benefits you derive from this setup exist only in what is provided here, at this time. That's limiting in a way, as you lose the benefit of any progress made here along the way.\n\nTherefore, the longer-term goal with this project is to offer it as a package so you can get the benefits of updates made to the system, without losing the ability to opt-out of the (strong) opinions made here.\n\n## Features\n\nThere is a lot going on above what Eleventy offers out of the box. Here are those features. If you have questions, feel free to [open an issue](https://github.com/seancdavis/twenty-ninety/issues/new) or [start a conversation on Twitter](https://twitter.com/seancdavis29).\n\nHere's a list:\n\n- [Gulp](#gulp)\n- [Eleventy](#eleventy)\n- [CSS](#css)\n- [JavaScript](#javascript)\n- [Netlify](#netlify)\n- [Testing](#testing)\n- [Environment Variables](#environment-variables)\n- [Fonts](#fonts)\n- [Components](#components)\n- [Images](#images)\n- [Markdown](#markdown)\n- [Google Analytics](#google-analytics)\n- [SEO](#seo)\n- [Yarn](#yarn)\n\n### Gulp\n\nEleventy offers a command to process templated files and output HTML code. Most websites require more processing, particularly with assets like CSS and JavaScript. That can make for disparate processes.\n\nIn this project, [Gulp](https://gulpjs.com/) plays the role of managing those processes together so you only have to worry about a few simple commands. The configuration can be found in `gulpfile.js`.\n\nFor convenience, all tasks are exported and can be like so:\n\n    $ yarn gulp [task]\n\nTo see a list of tasks, run:\n\n    $ yarn gulp --tasks\n\nBut, in general, if you're following the conventions for CSS and JS, you _should_ only need to worry about `develop` (starts a dev server) and `build` (builds static files). Both of these commands have been added to `package.json`, so that you can skip the `gulp` portion and just run:\n\n    $ yarn develop\n    $ yarn build\n\n### Eleventy\n\nThe HTML processing uses [Eleventy](https://www.11ty.dev/), which I hope isn't surprising, as this is currently an Eleventy starter kit!\n\nAnything you can do with Eleventy, you can do here, too. But if you use what we have here out of the box, there are a few items to note ...\n\n#### Eleventy Config\n\nEleventy's config goes into `.eleventy.js`. That file typically exports an object representing [the config values that have been adjusted](https://www.11ty.dev/docs/config/). In this case, those items have been abstracted to `eleventy.config.js`, as they are used elsewhere in the project.\n\nNote that some defaults have changed. The two most notable are that the build directory is `dist` and the templating engine for markdown files is nunjucks, rather than liquid.\n\n#### Eleventy Utils\n\nWhen the config file begins to perform many tasks it can quickly get unwieldy. Therefore, this starter offers the benefit of Eleventy utils. Any `.js` file dropped in to the `utils` directory that isn't prefixed with an underscore or suffixed with `.spec.js` will get picked up and run as a plugin by `.eleventy.js`.\n\nEach plugin (utility) requires a **single named export** as `default`. Check out `utils/shortcodes/markdown.js` as an example. It exports the following:\n\n```js\nexports.default = (eleventyConfig) =\u003e {\n  eleventyConfig.addPairedShortcode(\"markdown\", (input) =\u003e exports.renderMarkdown(input))\n}\n```\n\nThat looks just like some code that would typically be dropped into `.eleventy.js`, and that's the point. This provides a means of modularizing the plugins you provide to Eleventy.\n\nThere are a few (cool) custom shortcodes that ship with this project. They are listed near the end of the features.\n\n### CSS\n\nCSS is ready to go by default. It is configured to use [PostCSS](https://postcss.org/) (via a Gulp plugin), and also makes use of [Tailwind CSS](https://tailwindcss.com/).\n\nPostCSS plugins can be added or removed from `postcss.config.js`, and Tailwind configuration can be adjusted in or removed from `tailwind.config.js`.\n\nAny `.css` files in `src/_assets/css` will get picked up by the Gulp task and processed through PostCSS. The default is `main.css`.\n\nThe file is loaded in the `\u003chead\u003e` of the head of the default layout (`src/_includes/layout/head.njk`).\n\n### JavaScript\n\nJavaScript is built using [Webpack](https://webpack.js.org/) (via a Gulp plugin). The webpack config can be found at `webpack.config.js`.\n\nThe Gulp plugin will pick up any `.js` file in `src/_assets/js` and move it to `dist/assets/js/...`.\n\nHowever, it is recommended that with the current Webpack config, you only use a single bundle. It is designed to make the global code available through an `App` object. Notice that if you start up the dev server (`yarn develop`), you can open up your console and run the following:\n\n```js\nconsole.log(App)\n// =\u003e Module {default: {…}, __esModule: true, Symbol(Symbol.toStringTag): \"Module\"}\n\nconsole.log(App.default)\n// =\u003e {}\n```\n\nThat's because that's the default export from `main.js`:\n\n```js\nexport default {}\n```\n\nThe main JavaScript bundle is loaded onto the page from the foot include (`src/_includes/layout/foot.njk`).\n\n#### JavaScript Utilities\n\nThe project is also setup for injecting global utilities. The only one included by default is the _scan links_ utility (`src/_assets/js/utils/scan-links.js`). When the page is read, the link scanner looks through all anchor tags. If it determines the `href` value is to an external URL, it will add `target=\"_blank\"` and `rel=\"noopener\"` attributes to the tag.\n\nIf you'd like to remove this functionality, delete the file and remove the import line from `main.js`. Or, if you want to adjust the attributes that are added, you can edit the `scan-links.js` file.\n\n### Netlify\n\nThis project is built to be deployed with [Netlify](https://netlify.com). There's even a button above to do it with just a couple clicks!\n\nSome base configuration lives in `netlify.toml`, while others you may want to configure through the Netlify UI.\n\nThe project also brings a handful of [build plugins](https://docs.netlify.com/configure-builds/build-plugins/). Many of these are added to improve performance or accessibility in production. There's also a local plugin for running tests prior to running the build. You can see the list of plugins used in `netlify.toml`. Most are well-documented in their README files.\n\n### Testing\n\n[Jest](https://jestjs.io/) is used for testing both server-side and client-side JavaScript code. The convention in this project is to append test files with `.spec.js`.\n\nThe tests can be run with:\n\n    $ yarn test\n\nThere is a local Netlify build plugin that runs these tests automatically before the build on Netlify. You can remove the plugin if you don't want it to run.\n\n### Environment Variables\n\nI prefer [direnv](https://direnv.net/) for managing environment variables, but you can use whatever you'd prefer. A list of environment variables can be found in `.envrc-sample`.\n\n### Fonts\n\nFonts are included by default in `src/_assets/css/global/fonts.css`.\n\nIf you use either local or [Google Fonts](https://fonts.google.com/), the Netlify build plugin `netlify-plugin-subfont` will process the fonts and create subfont versions of those files, increasing performance in production.\n\n### Components\n\nComponents are one of the coolest features of this project. They are included as an Eleventy utility, but there's some magic that happens behind the scenes.\n\nComponents each get their own directory inside `src/_components`. We'll use the button as an example here.\n\nThere must be at least one file in the directory: `[name].template.njk`. (At this time, only `.njk` components are supported.) Therefore, a button component must include a `button.template.njk` file.\n\nIt gets rendered on the page using a `component` shortcode. For example:\n\n```njk\n{% component \"button\", url = \"/\", label = \"Click Me!\", theme = \"blue\" %}\n```\n\nThe first argument tells the shortcode which component to render (`button`), while the rest are sent as variables to the component.\n\nBut sometimes you want to transform those properties (variables) before rendering and don't want to have to do that in a view file. That's where transformers come in. If you create a file at `[name].transformer.js` (e.g. `button.transformer.js`), the properties will get passed through the default export, which should return the transformed components. For example, the button transformer sets a default `theme` property if it is missing. That way we can safely use the `theme` variable in the template.\n\nLastly, if you want to bring custom styles, you can add a `[name].styles.css` file to the directory and it will get picked up automatically by `main.css`. Note that these styles are not automatically scoped.\n\n### Images\n\n`image` is another component (`src/_components/image`). It is designed to work with [Imgix](https://www.imgix.com). It is, at this time, _extremely_ opinionated, even down to the breakpoints. It is undergoing a transformation, as I work with the Imgix team to develop a more flexible solution. You're welcome to dig through the transformer to see how it works. Or you can remove and add your own.\n\n### Markdown\n\nMarkdown is [supported as a templating language with Eleventy](https://www.11ty.dev/docs/languages/markdown/). However, it can't be rendered in other templates. To remedy that problem, this project brings a `markdown` shortcode to render markdown within other template files. For example, in a nunjucks file, you could write the following:\n\n```njk\n\u003cdiv\u003e\n  {% markdown %}\n## This is a heading\n  {% endmarkdown %}\n\u003c/div\u003e\n```\n\nAnd voila! The markdown will be rendered!\n\nThis feature uses [markdown-it](https://github.com/markdown-it/markdown-it) and can be found in `utils/shortcodes/markdown.js`. It also supports syntax highlighting via [Highlight.js](https://highlightjs.org/), bringing in the GitHub theme by default.\n\n### Google Analytics\n\nGoogle Analytics are loaded in a script included by `main.js` (`src/_assets/js/lib/analytics.js`). This makes use of the [analytics library](https://www.npmjs.com/package/analytics). You may swap out Google for other plugins, or take a different approach to loading analytics on your site.\n\nIf you stick with Google, it expects the following conditions to be true with environment variables:\n\n- `ELEVENTY_ENV` to be missing or set to production.\n- `GOOGLE_ANALYTICS_ID` to be set as the ID for the GA account.\n- `ANALYTICS_APP_NAME` to be set as the name of your project (this is arbitrary, but should be adjusted).\n\n### SEO\n\nSEO is another component offered out of the box. It provides a means of writing SEO meta tags to the page based on page-specific data, with sensible defaults.\n\nThe defaults can be set in `src/_data/seo_defaults.json`.\n\nThe component gets loaded in the head (`src/_includes/layout/head.njk`). Notice that it sends the `title`, `image`, and `description`, but optionally allows for overrides by using the `seo` property.\n\nThis means that pages using this head include can override with the following structure:\n\n```md\n---\ntitle: \"Page Title\"\ndescription: \"Page Description\"\nimage: \"/page-image.jpg\"\nseo:\n  title: \"SEO Override Title\"\n  description: \"SEO Override Description\"\n  image: \"/seo-override-image.jpg\"\n  og:\n    title: \"OpenGraph Override Title\"\n    description: \"OpenGraph Override Description\"\n    image: \"/og-override-image.jpg\"\n    type: webpage\n  twitter:\n    title: \"Twitter Override Title\"\n    description: \"Twitter Override Description\"\n    image: \"/twitter-override-image.jpg\"\n    card: summary\n---\n\n...\n```\n\nHere are items of note:\n\n- Twitter first falls back to OpenGraph, then to the overrides, then to the page, then to the global defaults.\n- OpenGraph falls back to the overrides, then to the page, then to the global defaults.\n- It expects images to be local, as it prepends the base url to the image path.\n\n### Yarn v NPM\n\nI prefer [Yarn](https://yarnpkg.com/) over the more traditional NPM as a dependency manager. If you prefer NPM, it's easy enough to switch over. Simply run `npm install` and then delete `yarn.lock`. You should see a `package-lock.json` file appear in the project root, which means you're good to go! There's an NPM alternative to any yarn command mentioned here.\n\n## Contributing\n\nIf you'd like what you see here but have found a bug or would like to add a feature, super! And thank you very much for your support and contribution. To do so, have at it. Fork the repository and create a PR with your proposed change.\n\nIf you have an idea or found a bug, but can't or don't want to work it, feel free to [create an issue](https://github.com/seancdavis/twenty-ninety/issues/new).\n\nIf you'd like to chat about the project, the best way to start the conversation is with [a message on Twitter](https://twitter.com/seancdavis29).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseancdavis%2Ftwenty-ninety","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseancdavis%2Ftwenty-ninety","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseancdavis%2Ftwenty-ninety/lists"}