[
        {
          "id": "docs-commands-plugins",
          "title": "Plugins Command",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "command-line-usage",
          "tags": "",
          "url": "/docs/commands/plugins",
          "content": "The plugins command allows you to display all custom or gem-based plugins you have loaded in the site along with other related infrastructure such as source manifests, generators, and builders.\n\nRun bridgetown plugins list and you&#8217;ll get a printout in your terminal that looks something like this:\n\nRegistered Plugins: 5\n                    bridgetown-sample-plugin (~&gt; 1.0)\n                    bridgetown-seo-tag (~&gt; 3.0)\n                    bridgetown-feed (~&gt; 1.0)\n                    plugins/builders/tags.rb\n                    plugins/builders/newsletter_digest.rb\n  Source Manifests: ---\n            Origin: SamplePlugin\n        Components: /path/to/bridgetown-sample-plugin/components\n           Content: /path/to/bridgetown-sample-plugin/content\n           Layouts: /path/to/bridgetown-sample-plugin/layouts\n                    ---\n          Builders: 3\n                    SamplePlugin::Builder\n                    NewsletterDigest\n                    TagsBuilder\n        Converters: 3\n                    Bridgetown::Converters::Markdown\n                    Bridgetown::Converters::SmartyPants\n                    Bridgetown::Converters::Identity\n        Generators: 4\n                    NewsletterDigest\n                    Bridgetown::PrototypeGenerator\n                    Bridgetown::Paginate::PaginationGenerator\n                    BridgetownFeed::Generator\n\n\nYou can read more about builders, generators, etc. in the Plugins documentation.\n\nCopying Files out of Plugin Source Folders\n\nBridgetown gem-based plugins/themes which provide source manifests may add content to your site such as layouts, resources, static files, and components from folders in the gem.\n\nIf you ever need to override some of that content, you can use the plugins cd command. The syntax is as follows:\n\nbridgetown plugins cd &lt;origin&gt;/&lt;dir&gt;\n\n\nwhere &lt;origin&gt; is one of the source manifest origins (like the SamplePlugin example above), and &lt;dir&gt; is one of the folder names (like Content or Layouts).\n\nThe command drops you in a new temporary shell where you can access the files, and when you&#8217;re done, type exit to return to your site. In addition, you&#8217;re given the BRIDGETOWN_SITE environment variable as a way to reference your site from the temporary shell.\n\nFor example, if you wanted to copy all the layouts from a gem-based plugin into your own site layouts folder, run:\n\nbridgetown plugins cd AwesomePlugin/Layouts\n\ncp -r * $BRIDGETOWN_SITE/src/_layouts\nexit"
        },
        {
          "id": "docs-components-liquid",
          "title": "Liquid Components",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "components",
          "tags": "",
          "url": "/docs/components/liquid",
          "content": "Historically, templates in Bridgetown websites were typically powered by the Liquid template engine. You can still use Liquid in layouts and HTML pages as well as inside of content such as Markdown text with a configuration change.\n\nA component is a reusable piece of template logic (sometimes referred to as a “partial”) that can be included in any part of the site, and a full suite of components can comprise what is often called a “design system”.\n\nLiquid components can be combined with front-end component strategies using web components or other JavaScript libraries/frameworks for a hybrid static/dynamic approach.\n\nTable of Contents\n\n  Usage\n  The with Tag\n  Rendering Liquid Components from Ruby-based Templates\n  History of the Include Tag\n\n\nUsage\n\nIncluding a component within a content document or design template is done via a render tag or rendercontent tag. Here’s an example:\n\nHere is some **Markdown** text. Sign up for my newsletter!\n\n{% render \"forms/newsletter\" %}\n\n_Thank you!_\n\n\nThis would attempt to load the component defined in src/_components/forms/newsletter.liquid and render that into the document.\n\nHere is a more complex example using a block and variables:\n\nReally interesting content…\n\n{% rendercontent \"sections/aside\", heading: \"Some Additional Context\", type: \"important\", authors: resource.data.additional_authors %}\n  Read what some of our panelists have to say about the matter.\n\n  And **that's all folks**.\n{% endrendercontent %}\n\n### Wrapping Up\n\nAnd in summary…\n\n\nThis would load the component in src/_components/sections/aside.liquid, which might look something like this:\n\n{%- assign typeclass = \"sidebar-default\" %}\n{%- if type == \"important\" %}\n{%- assign typeclass = \"sidebar-important\" %}\n{%- endif %}\n&lt;aside class=\"sidebar {{ typeclass }}\"&gt;\n  &lt;h3&gt;{{ heading }}&lt;/h3&gt;\n  {{ content }}\n  &lt;p class=\"authors\"&gt;{{ authors | array_to_sentence_string }}&lt;/p&gt;\n&lt;/aside&gt;\n\n\nYou can use components provided by others via plugins, or you can write your own components. You can also nest components within components. Here’s an example layout:\n\n{% rendercontent \"shared/page_layout\" %}\n  {% rendercontent \"shared/box\" %}\n    {% render \"shared/back_to_button\", label: \"Components List\", url: \"/components/\" %}\n    {% render \"shared/header_subpage\", title: resource.data.title %}\n\n    &lt;div class=\"content\"&gt;\n      {% render \"component_preview/metadata\", component: resource.data.component %}\n      {% render \"component_preview/variables\", component: resource.data.component %}\n    &lt;/div&gt;\n  {% endrendercontent %}\n  {% render \"component_preview/preview_area\", resource: resource.data %}\n{% endrendercontent %}\n\n\nThe with Tag\n\nInstead of passing variable data to a block-style component inline with the rendercomponent definition, you can also use the with tag. This is great for components which combine a bunch of content regions into a single markup composition.\n\nHere’s an example of how you might author a navbar component using with. First we’ll define the component itself:\n\n&lt;nav class=\"navbar\"&gt;\n  &lt;div class=\"navbar-logo\"&gt;\n    {{ logo }}\n  &lt;/div&gt;\n\n  &lt;div class=\"navbar-start\"&gt;\n    {{ items_start }}\n  &lt;/div&gt;\n\n  &lt;div class=\"navbar-end\"&gt;\n    {{ items_end }}\n  &lt;/div&gt;\n&lt;/nav&gt;\n\n\nNow we can render that component and fill in the logo, items_start, and items_end regions:\n\n{% rendercontent \"navbar\" %}\n  {% with logo %}\n    &lt;a class=\"navbar-item\" href=\"/\"&gt;\n      Awesome Site\n    &lt;/a&gt;\n  {% endwith %}\n\n  {% with items_start %}\n    &lt;a class=\"navbar-item\" href=\"/\"&gt;Home&lt;/a&gt;\n    &lt;a class=\"navbar-item\" href=\"/about\"&gt;About&lt;/a&gt;\n    &lt;a class=\"navbar-item\" href=\"/posts\"&gt;Posts&lt;/a&gt;\n  {% endwith %}\n\n  {% with items_end %}\n    &lt;div class=\"navbar-item search-item\"&gt;\n      {% render \"bridgetown_quick_search/search\", placeholder: \"Search\", input_class: \"input\" %}\n    &lt;/div&gt;\n    &lt;a class=\"navbar-item is-hidden-desktop-only\" href=\"https://{{ metadata.mastodon }}\" target=\"_blank\" rel=\"noopener\"&gt;\n      &lt;span class=\"icon\"&gt;&lt;i class=\"fa fa-mastodon is-size-6\"&gt;&lt;/i&gt;&lt;/span&gt;\n      &lt;span class=\"is-hidden-tablet\"&gt;Mastodon&lt;/span&gt;\n    &lt;/a&gt;\n  {% endwith %}\n{% endrendercontent %}\n\n\nNormally content inside of with tags is not processed as Markdown (unlike the default behavior of rendercontent). However, you can add a :markdown suffix to tell with to treat it as Markdown. Example:\n\n{% rendercontent \"article\" %}\n  {% with title:markdown %}\n    ## Article Title\n  {% endwith %}\n\n  Some _nifty_ content here.\n{% endrendercontent %}\n\n\nRendering Liquid Components from Ruby-based Templates\n\nYou can use the liquid_render helper from Ruby-based templates to render Liquid components.\n\n&lt;%= liquid_render \"test_component\", param: \"Liquid FTW!\" %&gt;\n\n\nIf you pass a block to liquid_render, it will utilize the rendercontent Liquid tag and the block contents will be captured and made available via the content variable.\n\nHistory of the Include Tag\n\nAs part of Bridgetown’s past Jekyll heritage, you may be familiar with the include tag as a means of loading partials into templates and passing variables/parameters. This tag was removed in Bridgetown 1.0. The render tag offers greater room for performance optimizations and requires explicit declaration of available variables rather than relying on global variables—in other words, within a component file, you can’t access page or site, etc., unless you specifically pass page or site in as a variable. Example:\n\n{% render \"navbar\", site: site %}\n\n\nIn many cases, you may not need to pass such large objects and can be more choosy in how you use variables. For example, maybe you can use site.metadata or resource.relative_url:\n\n{% render \"navbar\", metadata: site.metadata, current_url: resource.relative_url %}\n\n\nTips for migrating to render:\n\n\n  Files must not contain hyphens (-). Use underscores instead (_). So my_widget, not my-widget.\n  You don’t include extensions in the path. It automatically defaults to either .html or .liquid (preferred). So my_widget, not my_widget.html\n  As mentioned, any variables you use will have to be passed in explicitly. No variables in the scope of a page or layout are available by default in a component.\n  The rendercontent block tag automatically converts anything you put inside of it from Markdown to HTML. So even in an HTML layout/page, if you have Markdown text inside the block, it will be converted."
        },
        {
          "id": "docs-components-lit",
          "title": "Lit Web Components",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "components",
          "tags": "",
          "url": "/docs/components/lit",
          "content": "The Web Component spec is an open web standard, an integral part of the fabric of the web and supported by all modern, evergreen browsers. A web component starts life as a custom element in HTML markup which contains one or more dashes, for example &lt;my-button&gt; or &lt;card-heading-title&gt;. You then direct the browser to load that custom element as a dedicated JavaScript object through use of the window.customElements.define method. A web component also comes with a suite of features such as Shadow DOM, Slots, and CSS Shadow Parts to give the component a degree of autonomy and clean separation from the styling and markup concerns of the parent document.\n\nWhile you can author a web component without using libraries or frameworks of any kind, the lightweight Lit library makes web component development fast &amp; straightforward, offering a fabulous DX (Developer Experience) through features like reactive rendering, attribute reflection as properties (including JSON data!), and template literal syntax.\n\nThrough the use of Bridgetown’s Lit Renderer plugin, you can “bake” HTML &amp; CSS from the Lit component into your static or server-rendered output via Declarative Shadow DOM, which is then “re-hydrated” on the client side. You can take advantage of APIs to render up-to-date content in real-time in the browser after possibly-stale static content has first loaded.\n\nTable of Contents\n\n  Installing Lit Renderer\n  Take Lit for a Spin\n  Lit Helper Options\n  Sidecar CSS Files\n  In Combination with Ruby Components\n  Technical and Performance Considerations\n\n\nInstalling Lit Renderer\n\nRun the Bundled Configuration automation:\n\nbin/bridgetown configure lit\n\n\nOr pass it along to the new command:\n\nbridgetown new mysite -t erb -c lit\n\n\nThis will install both the Lit library itself plus the Lit Renderer plugin.\n\nTake Lit for a Spin\n\nAs part of the installation, an example component was provided in src/_components/happy-days.lit.js. It looks like this:\n\nimport { LitElement, html, css } from \"lit\"\n\nexport class HappyDaysElement extends LitElement {\n  static styles = css`\n    :host {\n      display: block;\n      border: 2px dashed gray;\n      padding: 20px;\n      max-width: 300px;\n    }\n  `\n\n  static properties = {\n    hello: { type: String }\n  }\n\n  render() {\n    return html`\n      &lt;p&gt;Hello ${this.hello}! ${Date.now()}&lt;/p&gt;\n    `;\n  }\n}\n\ncustomElements.define(\"happy-days\", HappyDaysElement)\n\n\nThe component establishes some initial styles (:host is the way you apply CSS directly to the custom element itself) and configures a hello reactive property (which gets initialized with whatever is contained within the hello HTML attribute). It then renders out a paragraph tag within its shadow DOM containing the hello text and a current timestamp.\n\nYou can use this component in any Ruby template (Liquid not supported). For example, in an .erb template or page:\n\n&lt;%= lit :happy_days, hello: \"there\" %&gt;\n\n\nThe helper will know how to convert the tag name and attribute keywords to HTML output via Lit’s SSR process. It will look something like this:\n\n&lt;hydrate-root&gt;\n  &lt;happy-days defer-hydration hello=\"there\"&gt;\n    &lt;template shadowroot=\"open\"&gt;\n      &lt;style&gt;\n        :host {\n          display: block;\n          border: 2px dashed gray;\n          padding: 20px;\n          max-width: 300px;\n        }\n      &lt;/style&gt;\n      &lt;p&gt;Hello there! 1654106939801&lt;/p&gt;\n  &lt;/happy-days&gt;\n&lt;/hydrate-root&gt;\n\n\nThe &lt;hydrate-root&gt; custom element is provided by Bridgetown’s Lit plugin and establishes the “island” of the the Lit component tree for re-hydration upon page load. Within the &lt;happy-days&gt; component, the &lt;template shadowroot=\"open\"&gt; tag contains the rendered content as part of the declarative shadow DOM spec.\n\nOnce you start up your Bridgetown site and visit the page, you should see a box containing “Hello there!” and a timestamp when the page was first rendered.\n\nYou can reload the page several times and see that the timestamp doesn’t change, because Lit’s SSR + Hydration support knows not to re-render the component. However, if you change the hello attribute, you’ll get a re-render and thus see a new timestamp. How cool is that?!\n\nLit Helper Options\n\nThe lit helper works in any Ruby template language and let’s you pass data down to the Lit SSR build process. Any value that’s not already a string will be converted to JSON (via Ruby’s to_json). You can use a symbol or string for the tag name and underscores are automatically converted to dashes.\n\n&lt;%= lit :page_header, title: resource.data.title %&gt;\n\n\n(Remember, all custom elements always must have at least one dash within the HTML.)\n\nIf you pass a block to lit, it will add that additional HTML into the Lit template output:\n\n&lt;%= lit :ui_sidebar do %&gt;\n  &lt;h2 slot=\"title\"&gt;Nice Sidebar&lt;/h2&gt;\n&lt;% end %&gt;\n\n\nYou can also pass page/resource front matter and other data along via the data keyword, which then can be used in the block. In addition, if a tag name isn’t present, you can add it yourself in within the block.\n\n&lt;%= lit data: resource.data do %&gt;\n  &lt;page-header&gt;\n    &lt;h1&gt;${data.title}&lt;/h1&gt;\n  &lt;/page-header&gt;\n&lt;% end %&gt;\n\n\nWhen the component is hydrated, it will utilize the same data that was passed at build time and avoid a client-side re-render. However, from that point forward you’re free to mutate component attribute/properties to trigger re-renders as normal. Check out Lit’s firstUpdated method as a good place to start.\n\nYou also have the option of choosing a different entry point (aka your JS file that contains or imports one or more Lit components). The default is ./config/lit-components-entry.js, but you can specify any other file you wish (the path should be relative to your project root).\n\n&lt;%= lit data: resource.data, entry: \"./frontend/javascript/components/headers.js\" do %&gt;\n  &lt;page-header title=\"${data.title}\"&gt;&lt;/page-header&gt;\n&lt;% end %&gt;\n\n\nThis would typically coincide with a strategy of having multiple esbuild entry points, and loading different entry points on different parts of your site. An exercise left for the reader…\n\nSidecar CSS Files\n\nThe “default” manner in which you author styles in Lit components is to use css tagged template literals (as you saw in the happy-days example above). However, some people prefer authoring styles in dedicated CSS files. The esbuild-plugin-lit-css plugin allows you to author perfectly vanilla CSS files alongside your component files and import them.\n\n\n  \n  One major benefit to this approach is it allows you to process your component CSS through PostCSS using the same configuration and plugins as for other CSS files.\n\n\n\n  \n  If you&#8217;ve updated your site from a version prior to Bridgetown 1.3, you may need to update your\nesbuild.config.js file so it includes the following configuration option:\n\n  globOptions: {\n    excludeFilter: /\\.(dsd|lit)\\.css$/\n  }\n\n\n\nIn order to separate the “globally-accessible” stylesheets you may have in src/_components from the Lit component-specific stylesheets (which we only want to get instantiated within component shadow roots), we’ll need to use the following file conventions:\n\n\n  For global stylesheets, use the standard .css suffix.\n  For Lit component stylesheets, use a .lit.css suffix.\n\n\nBridgetown’s bundled Lit configuration provides the building blocks for this setup. You’ll need to edit a few lines in your frontend/javascript/index.js and esbuild.config.js files to opt into this (look at the comments in the files). Once completed, you’ll be able to write components such as this:\n\n// _src/components/my-nifty-tag.lit.js\nimport { LitElement, html } from \"lit\"\n\nimport style from \"./my-nifty-tag.lit.css\" assert { type: \"css\" }\n\nexport class MyNiftyTag extends LitElement {\n  static styles = [style]\n\n  // rest of the component definition here\n}\n\ncustomElements.define(\"my-nifty-tag\", MyNiftyTag)\n\n\nYou can even combine external stylesheets with ones defined directly within a component if you need to share styles between multiple components.\n\nimport { LitElement, html } from \"lit\"\n\nimport style from \"./shared/components.lit.css\" assert { type: \"css\" }\n\nexport class ManyStylesElement extends LitElement {\n  static styles = [\n    style,\n    css`\n      :host {\n        border: 1px solid var(--gray-5);\n        max-width: 80ch;\n      }\n    `\n  ]\n\n  // …\n}\n\n\n\n  \n  While the esbuild Lit CSS plugin doesn&#8217;t require you to include assert { type: \"css\" } at the end of your import statements, it&#8217;s a good idea to get in the habit as it aligns your code with the CSS Module Scripts spec rolling out to browsers now and in future.\n\n\nIn Combination with Ruby Components\n\nA very powerful pattern for Bridgetown component design is to use a Lit component as the template for a Ruby component. This allows you to use the Ruby component anywhere on your site, along with any pre-processing of data you need it to perform, and then the Ruby component can “emit” a Lit web component upon render. As an example:\n\nclass MyRubyComponent &lt; Bridgetown::Component\n  def initialize(value:)\n    @value = process_value(value)\n  end\n\n  def process_value\n    @value = \"Value: #{@value}\"\n  end\n\n  def template\n    lit :my_lit_component, value: @value\n  end\nend\n\n\n&lt;!-- elsewhere --&gt;\n&lt;%= render MyRubyComponent.new(value: \"Here is my value\") %&gt;\n\n\nIn this example, you wouldn’t need a sidecar template for your component in ERB or whatever, because the Lit component serves as the template.\n\nTechnical and Performance Considerations\n\nWith a bit of careful planning of which entry point(s) you use, the data you provide, and the structure of your HTML markup within the lit helper, you can achieve good Lit SSR performance while still taking full advantage of the Ruby templates and components you know and love.\n\nMore documentation on this is available in the plugin README."
        },
        {
          "id": "docs-components-ruby",
          "title": "Ruby Components",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "components",
          "tags": "",
          "url": "/docs/components/ruby",
          "content": "A component is a reusable piece of template logic that can be included in any part of the site, and a full suite of components can comprise what is often called a “design system”. You can render Ruby component objects directly in your Ruby-based templates, and you can render components from within other components. This provides the basis for a fully-featured view component architecture for ERB, Serbea, and beyond.\n\nRuby components can be combined with front-end component strategies using web components or other JavaScript libraries/frameworks.\n\nTable of Contents\n\n  Basic Building Blocks\n  Use Bridgetown::Component for Template Rendering    \n      Content\n      Slotted Content\n    \n  \n  Helpers\n  Lifecycle\n  Rendering Ruby Components from Liquid Templates\n  Sidecar JS/CSS Assets\n\n\nBasic Building Blocks\n\nBridgetown automatically loads .rb files you add to the src/_components folder, so that’s likely where you’ll want to save your component class definitions. It also load components from plugins which provide a components source manifest. Bridgetown’s component loader is based on Zeitwerk, so you’ll need to make sure your class names and namespaces line up with your component folder hierarchy (e.g., _components/shared/navbar.rb should define Shared::Navbar.).\n\nTo create a basic Ruby component, define a render_in method which accepts a single view_context argument as well as optional block. Whatever string value you return from the method will be inserted into the template. For example:\n\nclass MyComponent\n  def render_in(view_context, &amp;block)\n    \"Hello from MyComponent!\"\n  end\nend\n\n\n&lt;%= render MyComponent.new %&gt;\n\n  output: Hello from MyComponent!\n\n\nThe view_context is whichever template or component processor is in charge of rendering this object.\n\nTypically though, you won’t be writing Ruby components as standalone objects.  Introducing Bridgetown::Component!\n\n\n  \n  Bear in mind that Ruby components aren’t accessible from Liquid templates. So if you need a component which can be used in either templating system, consider writing a Liquid component. Read more information here.\n\n\nUse Bridgetown::Component for Template Rendering\n\nBy subclassing Bridgetown::Component, you gain the ability to write a template in ERB, Serbea, or Streamlined.\n\nFor template engines like ERB or Serbea, add a “sidecar” template file right next to the component’s .rb file. The template will automatically get rendered by the component (and you won’t need to define a render_in method yourself).\n\n# src/_components/field_component.rb\nclass FieldComponent &lt; Bridgetown::Component\n  def initialize(type: \"text\", name:, label:)\n    @type, @name, @label = type, name, label\n  end\nend\n\n\n\n  ERB\n  Serbea\n\n  \n\n    &lt;!-- src/_components/field_component.erb --&gt;\n&lt;field-component&gt;\n  &lt;label&gt;&lt;%= @label %&gt;&lt;/label&gt;\n  &lt;input type=\"&lt;%= @type %&gt;\" name=\"&lt;%= @name %&gt;\" /&gt;\n&lt;/field-component&gt;\n    \n\n  \n  \n\n    &lt;!-- src/_components/field_component.serb --&gt;\n&lt;field-component&gt;\n  &lt;label&gt;{{ @label }}&lt;/label&gt;\n  &lt;input type=\"{{ @type }}\" name=\"{{ @name }}\" /&gt;\n&lt;/field-component&gt;\n    \n\n  \n\n\nRendering out the component in a parent template and passing along arguments looks like this:\n\n\n  ERB\n  Serbea\n\n  \n\n    &lt;%= render FieldComponent.new(type: \"email\", name: \"email_address\", label: \"Email Address\") %&gt;\n\n  &lt;!-- output: --&gt;\n  &lt;field-component&gt;\n    &lt;label&gt;Email Address&lt;/label&gt;\n    &lt;input type=\"email\" name=\"email_address\" /&gt;\n  &lt;/field-component&gt;\n    \n\n  \n  \n\n    {%@ FieldComponent type: \"email\", name: \"email_address\", label: \"Email Address\" %}\n\n  &lt;!-- output: --&gt;\n  &lt;field-component&gt;\n    &lt;label&gt;Email Address&lt;/label&gt;\n    &lt;input type=\"email\" name=\"email_address\" /&gt;\n  &lt;/field-component&gt;\n    \n\n  \n\n\nBesides sidecar templates, you can use Ruby’s “squiggly heredoc” syntax as a template language with our Streamlined template engine:\n\nclass FieldComponent\n  attr_reader :type, :name, :label\n\n  def initialize(type: \"text\", name:, label:)\n    @type, @name, @label = type, name, label\n  end\n\n  def template\n    html -&gt; { &lt;&lt;~HTML\n      &lt;field-component&gt;\n        &lt;label&gt;#{text -&gt; { label }}&lt;/label&gt;\n        &lt;input #{html_attributes(type:, name:)} /&gt;\n      &lt;/field-component&gt;\n    HTML\n    }\n  end\nend\n\n\nStreamlined adds some special helpers so that writing properly-escaped HTML as well as rendering out a hash as attributes or looping through an array is much easier than with plain heredoc syntax. We’ve found that for complex interplay between Ruby &amp; HTML code, Streamlined is easier to deal with than either ERB or Serbea.\n\nRead more about how to use Ruby template syntax here.\n\n\n  \n  You can also write multiple “inner” Ruby classes namespaced within an outer module or class. For example, you could author a shared.rb file containing components Shared::Navbar, Shared::Footer, etc. For the sidebar templates, they would live in a folder of the underscored parent namespace (aka MyRubyModule becomes my_ruby_module). In this case, shared/navbar.erb, shared/footer.erb, etc.\n\n\n\n  \n  Need to add component compatibility with Rails projects? Try our experimental ViewComponent shim.\n\n\nContent\n\nBridgetown components are provided access to a content variable which is the output of the block passed into the component via the parent render:\n\n&lt;!-- some page template --&gt;\n&lt;%= render(Layout::Box.new(border: :large)) do %&gt;\n  I'm in a box!\n&lt;% end %&gt;\n\n&lt;!-- src/_components/layout/box.erb --&gt;\n&lt;layout-box border=\"&lt;%= @border %&gt;\"&gt;\n  &lt;%= content %&gt; &lt;!-- I'm in a box! --&gt;\n&lt;/layout-box&gt;\n\n\nSlotted Content\n\nYou can provide specific named content from within the calling template to a component. If the content variable above could be considered the “default” slot, you’ll now learn how to work with named content slots.\n\nHere’s an example of supplying and rendering an image within a card.\n\n# src/_components/card.rb\nclass Card &lt; Bridgetown::Component\n  def initialize(title:, footer:)\n    @title, @footer = title, footer\n  end\nend\n\n\n&lt;!-- src/_components/card.erb --&gt;\n&lt;app-card&gt;\n  &lt;figure&gt;&lt;%= slotted :image %&gt;&lt;/figure&gt;\n  &lt;header&gt;&lt;%= @title %&gt;&lt;/header&gt;\n  &lt;app-card-inner&gt;\n    &lt;%= content %&gt;\n  &lt;/app-card-inner&gt;\n  &lt;footer&gt;&lt;%= @footer %&gt;&lt;/footer&gt;\n&lt;/app-card&gt;\n\n\n&lt;!-- some page template --&gt;\n&lt;%= render(Card.new(title: \"Card Header\", footer: \"Card Footer\")) do |card| %&gt;\n  &lt;% card.slot :image do %&gt;&lt;img src=\"&lt;%= resource.data.image %&gt;\" /&gt;&lt;% end %&gt;\n\n  Some card content goes here!\n&lt;% end %&gt;\n\n\nThe slotted helper can also provide default content should the slot not already be defined:\n\n&lt;%= slotted :image do %&gt;\n  &lt;img src=\"/images/unknown.png\" /&gt;\n&lt;% end %&gt;\n\n\nMultiple captures using the same slot name will be cumulative. The above image slot could be appended to by calling slot :image multiple times. If you wish to change this behavior, you can pass replace: true as a keyword argument to slot to clear any previous slot content. Use with extreme caution!\n\nFor more control over slot content, you can use the pre_render hook. Builders can register hooks to transform slots in specific ways based on their name or context. This is perhaps not all that useful when you’re writing both the content and the components, but for customization of third-party components it could come in handy.\n\nclass Builders::FigureItOut &lt; SiteBuilder\n  def build\n    hook :slots, :pre_render do |slot|\n      return unless slot.name == \"image\" &amp;&amp; slot.context == SomeComponent\n\n      slot.content = \"#{slot.content}&lt;figcaption&gt;Cool Image&lt;/figcaption&gt;\".html_safe\n    end\n  end\nend\n\n\n\n  \n  Both slot and slotted accept an argument instead of a block for content. So you could call &lt;% slot :slotname, \"Here's some content\" %&gt; rather than supplying a block.\n\n\n\n  \n  Bridgetown’s main Ruby template rendering pipeline also has its own slotting mechanism.\n\n\n\n  \n  Don’t let the naming fool you…Bridgetown’s slotted content feature is not related to the concept of slots in custom elements and shadow DOM (aka web components). But there are some surface-level similarities. Many view-related frameworks provide some notion of slots (perhaps called something else like content or layout blocks), as it’s helpful to be able to render named “child” content within “parent” views.\n\n\nHelpers\n\nAs expected, helpers are available as well exactly like in standard templates:\n\n\n  ERB\n  Serbea\n\n  \n\n    &lt;!-- src/_components/posts/excerpt.erb --&gt;\n&lt;post-excerpt&gt;\n  &lt;h3&gt;&lt;%= link_to @post.data.title, @post %&gt;&lt;/h3&gt;\n\n  &lt;%= markdownify @post.data.description %&gt;\n&lt;/post-excerpt&gt;\n    \n\n  \n  \n\n    &lt;!-- src/_components/posts/excerpt.serb --&gt;\n&lt;post-excerpt&gt;\n  &lt;h3&gt;{{ @post.data.title | link_to: @post }}&lt;/h3&gt;\n\n  {{ @post.data.description | markdownify }}\n&lt;/post-excerpt&gt;\n    \n\n  \n\n\nWhile components are intended to be encapsulated, sometimes you want quick access to global data through site. In that case, you can set the @site instance variable and then the site accessor will be available in your component:\n\nclass ExternalWidget &lt; Bridgetown::Component\n  def initialize(id:)\n    @id = id\n    @site = Bridgetown::Current.site\n  end\n\n  def before_render\n    api_key = site.config.external_api_key\n    # request data from a third-party service...\n  end\nend\n\n\nLifecycle\n\nIn addition to rendering a template for you, Bridgetown::Component provides a couple lifecycle hooks:\n\n\n  render? – if you define this method and return false, the component will not get rendered at all.\n  before_render – called right before the component is rendered when the view_context is known and all helpers available.\n\n\nRendering Ruby Components from Liquid Templates\n\nYou can use the ruby_render helper from Liquid templates to render Ruby components.\n\n{% ruby_render \"test_component\", title: \"Ruby FTW!\" %}\n\n\nIn this example, TestComponent is initialized with the keyword argument title, then rendered.\n\nPassing in content via a block is not yet supported.\n\nSidecar JS/CSS Assets\n\nSome of the components you write will comprise more than pure markup. You may want to affect the styling and behavior of a component as well. For a conceptual overview of this architecture, read our Components introduction.\n\nThe easiest way to write frontend component code using “vanilla” web APIs is to wrap your component in a custom element. You can then apply CSS directly to that component from a stylesheet, and even add interactivity via JavaScript. Here’s a “trifecta” example (all the files would live in the same folder as the Ruby component):\n\n&lt;!-- output from your component template aka `alert.html.erb` --&gt;\n&lt;ui-alert variant=\"warning\"&gt;\n  &lt;p&gt;This message will self-destruct in five seconds. Good luck!&lt;/p&gt;\n&lt;/ui-alert&gt;\n\n\n/* alert.css */\nui-alert {\n  --alert-background: lightgray;\n  --alert-color: darkslategray;\n\n  display: block;\n  padding: 1rem;\n  color: var(--alert-color);\n  background: var(--alert-background);\n  border: 2px solid color-mix(in srgb, var(--alert-background), black 25%);\n  border-radius: .5rem;\n\n  &amp;[variant=\"warning\"] {\n    --alert-background: lemonchiffon;\n    --alert-color: saddlebrown;\n  }\n}\n\n\n// alert.js\nclass AlertElement extends HTMLElement {\n  static {\n    customElements.define(\"ui-alert\", this)\n  }\n\n  connectedCallback() {\n    // remove in five seconds…\n    setTimeout(() =&gt; this.remove(), 5000)\n  }\n}\n\n\nBear in mind when you write your component-scoped CSS, err on the side of using child combinators for nested elements so you don’t accidentally overwrite element styles within other components. Alternatively, you can use shadow DOM for fully-encapsulated HTML/CSS/JS within a component template!\n\nFor another spin on this concept, check out our Lit Components documentation. You can also read up on how Bridgetown’s frontend build pipeline works."
        },
        {
          "id": "docs-components-view-component",
          "title": "ViewComponent (Rails Compatibility Layer)",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "components",
          "tags": "",
          "url": "/docs/components/view-component",
          "content": "If you’ve used GitHub’s ViewComponent on existing Rails projects, you’re in luck! We’ve created a compatibility shim which “fools” ViewComponent into thinking it’s booted up in a Rails app when it’s actually not. ViewComponent itself is mainly only reliant on the ActionView framework within Rails, so we include that along with the shim, and then you’re off to the races. (Note: this functionality is still considered experimental.)\n\nLet’s break it down.\n\nQuick Tutorial\n\nFirst, you’ll need to add the plugin to your Gemfile. In a Bridgetown project folder, run the following command:\n\nbundle add bridgetown-view-component\n\n\nand then add init :\"bridgetown-view-component\" to config/initializers.rb.\n\nNext create a shared folder in src/_components and add the following two files:\n\n# src/_components/shared/header.rb\nmodule Shared\n  class Header &lt; ViewComponent::Base\n    include Bridgetown::ViewComponentHelpers\n\n    def initialize(title:, description:)\n      @title, @description = title, description\n    end\n  end\nend\n\n\n&lt;!-- src/_components/shared/header.erb --&gt;\n&lt;header style=\"text-align:center; color: teal\"&gt;\n  &lt;h1 style=\"color: darkgreen\"&gt;&lt;%= @title %&gt;&lt;/h1&gt;\n\n  &lt;%= markdownify @description %&gt;\n&lt;/header&gt;\n\n\nNow let’s set up a new layout to render our component. Add src/_layouts/vc.erb:\n\n---\nlayout: default\n---\n\n&lt;%= render(Shared::Header.new(\n      title: resource.data.title,\n      description: resource.data.description\n    )) %&gt;\n\n&lt;%= yield %&gt;\n\n\nFinally, update your home page (src/index.md) like so:\n\n---\nlayout: vc\ntitle: ViewComponent\ndescription: It's _here_ and it **works**!\n---\n\nYay! 😃\n\n\nNow run bin/bridgetown start, load your website at localhost:4000, and you should see the new homepage with the Shared::Header ViewComponent rendered into the layout!\n\nHelpers\n\nSo far, pretty standard fare for ViewComponent, but you’ll notice we had to add include Bridgetown::ViewComponentHelpers to the definition of our Shared::Header class. That’s because, out of the box, ViewComponent doesn’t know about any of Bridgetown’s helpers. We could have injected helpers directly into the base class, but that might adversely affect components written with Rails in mind, so at least in this early phase we’re including the module manually.\n\n\n  \n  As a shortcut, you could create your own base class, say SiteViewComponent, which inherits from ViewComponent::Base, include the Bridgetown::ViewComponentHelpers module, and then subclass all your site components from SiteViewComponent.\n\n\nRails Helpers\n\nIncluding Bridgetown::ViewComponentHelpers in a ViewComponent provides access to Bridgetown helpers within the component. However, to facilitate that, most of the default Action View Helpers get disabled, since many helpers rely on Rails and will not work with Bridgetown.\n\nBridgetown::ViewComponentHelpers#allow_rails_helpers provides an API to enable supplied Action View Helpers like ActionView::Helpers::TagHelper:\n\nclass HeaderComponent &lt; ViewComponent::Base\n  Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag\n  include Bridgetown::ViewComponentHelpers\n\n  def call\n    tag.h1 content, class: \"my-8 text-3xl font-bold tracking-tight text-primary-white sm:text-4xl\"\n  end\nend\n\n\n  \n  The Rails helpers must be included before the Bridgetown View Component helpers, as shown in this example.\n\n\nIn this example, Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag enables ActionView::Helpers::TagHelper. We can create an inline ViewComponent that leverages tag.h1 to create an &lt;h1&gt; element with our supplied content.\n\nIn your template, &lt;%= render HeaderComponent.new.with_content(\"👋\") %&gt; would output:\n\n&lt;h1 class=\"my-8 text-3xl font-bold tracking-tight text-primary-white sm:text-4xl\"&gt;👋&lt;/h1&gt;\n\n\nLike helpers, you can include Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag in a base class that your components inherit from to reduce duplication.\n\nUsing Primer\n\nPrimer is a component library and design system published by GitHub, and you can use it now with Bridgetown! However, you’ll need to do a bit of extra “shim” work to get Primer view components loaded within the Bridgetown context.\n\nFirst, add the following to your Gemfile:\n\ngem \"railties\" # required by Primer\ngem \"actionpack\" # required by Primer\ngem \"primer_view_components\"\n\n\nNext, add the following file to your plugins folder:\n\n# plugins/builders/primer_builder.rb\n\nrequire \"action_dispatch\"\nrequire \"rails/engine\"\nrequire \"primer/view_components/engine\"\n\nclass Builders::PrimerBuilder &lt; SiteBuilder\n  def build\n    site.config.loaded_primer ||= begin\n      primer_loader = Zeitwerk::Loader.new\n      Primer::ViewComponents::Engine.config.eager_load_paths.each do |path|\n        primer_loader.push_dir path\n      end\n      primer_loader.setup\n      Rails.application.config = Primer::ViewComponents::Engine.config\n      true\n    end\n  end\nend\n\n\nWhat this does is import a couple of additional Rails dependencies, set up the autoloading functionality provided by Zeitwerk, and ensure Primer’s engine config is added to the Rails shim. We also want to guarantee this code only runs once when in Bridgetown’s watch mode.\n\nLet’s also add the Primer CSS link tag to your site’s head:\n\n&lt;link href=\"https://unpkg.com/@primer/css@^19.0.0/dist/primer.css\" rel=\"stylesheet\" /&gt;\n\n\nNow you can use Primer components in any Ruby template in your Bridgetown project!\n\n&lt;%= render(Primer::FlashComponent.new(scheme: :success)) do %&gt;\n  &lt;span markdown=\"1\"&gt;This is a **success** flash message!&lt;/span&gt;\n&lt;% end %&gt;"
        },
        {
          "id": "docs-configuration-environments",
          "title": "Environments",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/environments",
          "content": "The “environment” of a Bridgetown site can affect the way certain processes work. Typically your site is run in the development environment. When running automated tests it will be run in the test environment, and upon deployment it should be run in the production environment.\n\nWhen running CLI commands, you can specify the Bridgetown environment by\nprepending it to your command:\n\nBRIDGETOWN_ENV=production bin/bridgetown build\n\n\nor by using the -e or --environment flag:\n\nbin/bridgetown build -e production\nbin/bridgetown console --environment=test\n\n\nAlternatively, you can set the environment value using your computer or server\nsettings…most hosting companies allow environment variables to be specified via\na control panel of some kind. Or at the command line, look for a .bashrc or\n.zshrc file in your home folder and add:\n\nexport BRIDGETOWN_ENV=\"production\"\n\n\nConditional Content\n\nSuppose you set this conditional statement in your code:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% if Bridgetown.env.production? %&gt;\n  &lt;%= render \"analytics\" %&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% if bridgetown.environment == \"production\" %}\n   {% render \"analytics\" %}\n{% endif %}\n    \n\n  \n\n\nWhen you build your Bridgetown site, the content inside the if statement won’t be\nrendered unless you also specify a production environment.\n\nThe default value for BRIDGETOWN_ENV is development. Thus if you omit\nBRIDGETOWN_ENV from the build/serve commands, the default value will be\nBRIDGETOWN_ENV=\"development\".\n\nSome elements you might want to hide in development environments include comment forms or analytics. Conversely, you might want to expose an “Edit This Page” button in a development or staging environment but not include it in production environments.\n\nDefining Environments\n\nThe Legacy YAML format defines environments as key/value pairs, config/initializers.rb uses the environment as a condition for applying settings.\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\n\nif Bridgetown.env.development?\n  unpublished true\n  future true\nelsif Bridgetown.env.staging?\n  unpublished true\nend\n\n    \n\n  \n  \n\n    # bridgetown.config.yml\n\ndevelopment:\n  unpublished: true\n  future: true\n\nstaging:\n  unpublished: true\n    \n\n  \n\n\nThe development environment will build documents that are marked as unpublished as\nwell as having a future date, the staging environment will only\nbuild unpublished. And the production environment would exclude both sets.\n\nEnvironment Specific Metadata\n\nIn your src/_data/site_metadata.yml, you can add a block of YAML options\nper environment. For example, given the following metadata:\n\n# src/_data/site_metadata.ymlTITLE\n\ntitle: My Website\n\ndevelopment:\n  title: My (DEV) Website\n\n\nYour site title would be “My Website” if built with a production environment,\nand “My (DEV) Website” if built with a development environment. If you define your environments in the Legacy YAML config, you can also set per environment metadata values there. It is recommended to set metadata values in site_metadata.yml and Bridgetown values in initializers.\n\n\n  \n  Accessing the Environment in Your Ruby Code and Plugins\n\nAnywhere in Ruby code you write, you can check the current environment via Bridgetown.environment (env for short). You might decide to perform certain tests or verify data or perform some kind of operation in a development or test environment that you&#8217;d leave out in a production environment (or visa-versa)."
        },
        {
          "id": "docs-configuration-initializers",
          "title": "Initializers",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/initializers",
          "content": "In addition to setting some basic options in your bridgetown.config.yml configuration file, you can use your site’s config/initializers.rb file to set options, instantiate gem-based plugins, and write initializer blocks to configure third-party gems.\n\nHere’s a sample config/initializers.rb file showcasing many features of the configuration DSL:\n\nBridgetown.configure do |config|\n  init :dotenv\n\n  config.autoload_paths &lt;&lt; \"jobs\"\n\n  init :ssr do\n    sessions true\n    setup -&gt; site do\n      # perform site setup tasks only in the server context\n    end\n  end\n  init :\"bridgetown-routes\"\n\n  # you can configure site settings here just like you would in bridgetown.config.yml\n  permalink \"pretty\"\n  timezone \"America/Los_Angeles\"\n\n  # some initializers accept additional options\n  init :stripe, api_key: ENV[\"STRIPE_API_KEY\"]\n\n  only :server do\n    # code which runs only in server context\n\n    init :parse_routes\n\n    # you can also provide initializer options in block DSL form:\n    init :mail do\n      password ENV[\"SENDGRID_API_KEY\"]\n    end\n  end\n\n  only :static, :console do\n    # code which runs only in static and console contexts\n  end\n\n  only :rake do\n    # code which runs only in the Rake context\n  end\n\n  except :static, :console do\n    # you can define hooks within your initializers file:\n    hook :site, :after_init do |site|\n      # runs after a site is initialized in server, Rake, etc. contexts, but not static or console\n    end\n  end\nend\n\nBridgetown.initializer :stripe do |api_key:|\n  Bridgetown.logger.info \"Stripe:\", \"Setting the API key\"\n  Stripe.api_key = api_key\nend\n\n\nTable of Contents\n\n  Conventions\n  Features of the Configuration DSL    \n      The init method and initializers\n      Using only, except, and understanding initialization contexts\n      Adding hooks\n      Adding roda blocks\n      SSR &amp; Dynamic Routes\n      Inline Builders\n      Source Manifests\n    \n  \n  Low-level Boot Customization\n  Built-in Initializers    \n      Dotenv\n      External Content Sources\n      Wikilinks\n      Inflector\n      Parse Roda Routes\n    \n  \n\n\nConventions\n\nWhen calling an initializer, by default Bridgetown will try to require a gem by the same name. This is quite handy if you want to define an initializer specifically to configure a third-party gem. In the example above, we defined an initializer for stripe, so when calling that initializer it requires stripe automatically.\n\nYou can disable this behavior in one of two ways:\n\n\n  You can pass require_gem: false to your initializer. In the example above, if you called init :stripe, require_gem: false, it would not require stripe automatically, and you would have to do so manually within your initializer.\n  You can add initializer names to Bridgetown::Configuration::REQUIRE_DENYLIST. For example, by adding Bridgetown::Configuration::REQUIRE_DENYLIST &lt;&lt; :stripe at the top of your initializers file, the stripe gem would not be required automatically.\n\n\nAnother convention is that you can put additional files in your config folder each containing an initializer, and then when you call an initializer in your configuration, it will automatically load the relevant file in config before executing. More on that below.\n\nYou can disable this behavior by passing require_initializer: false to init—perhaps in a case where you already have a similarly-named file in your config folder which you don’t want to get processed as an initializer.\n\nFeatures of the Configuration DSL\n\nThe Bridgetown.configure block passes you a config object which is also the exact object that’s the scope of the configure block. This means you can set new configuration options either by calling configuration keys like methods, much like Bridgetown’s Ruby front matter feature— or you can set options on the config object.\n\nset_a_value \"Same thing\"\nconfig.set_a_value = \"Same thing\"\n# ^ They do the same thing!\n\n\nHaving access to the config object is handy if you want to manipulate existing options:\n\nconfig.autoload_paths &lt;&lt; \"models\"\n\n\nBesides setting primary configuration options, you can call init, only,  except, roda, and hook. Plugins authors also have access to builder and source_manifest. Continue reading for further details.\n\nThe init method and initializers\n\nAs seen above, when you call init you can pass additional configuration options requested by the initializer in one of two ways: using a Hash, or using a configure block. Thus these two are equivalent:\n\ninit :stripe, api_key: ENV[\"STRIPE_API_KEY\"]\n\n\ninit :stripe do\n  api_key ENV[\"STRIPE_API_KEY\"]\nend\n\n\nInside of the block, you can use the same DSL as the main configure block. You can also reference keys that were previously set:\n\ninit :some_initializer do\n  value \"Abc123\"\n  another_value \"#{value}456\"\nend\n\n\nThese configuration values will get passed directly to the initializer. But what if the plugin requires you to set values on the main config instead (as many legacy Bridgetown plugins will)? No problem! While it’s not strictly necessary, you can still use the init block and reference the main config object from enclosing scope for a clear visual grouping:\n\ninit :legacy_plugin do\n  config.legacy_plugin_setting = 123\nend\n\n\nIf you’re using a legacy plugin—or any third-party Ruby gem without an initializer—you’ll get a message something like:\n\nInitializing: The `insert_gem_name_here' initializer could not be found\n\n\nNo worries! You can write your own initializer. 😎\n\nAs in the example at the top of the page, you can place an initializer right alongside the configure block in config/initializers.rb. You can also place a file named the same as the gem or plugin directly in config. In the use case of using the Stripe gem, you could add to config/stripe.rb:\n\nBridgetown.initializer :stripe do |api_key:|\n  Bridgetown.logger.info \"Stripe:\", \"Setting the API key\"\n  Stripe.api_key = api_key\nend\n\n\nThen when you call init :stripe, api_key: ENV[\"STRIPE_API_KEY\"], code to the effect of require \"stripe\" will get called automatically and the initializer will get passed the value of api_key. (You can disable automatic requiring by adding require_gem: false to the init call.)\n\nInitializers are not only useful in Bridgetown site projects, they’re vital to the development of gem-based plugins. The configuration in progress is passed as the first argument to an initializer block so you can access it and modify it using the same configuration DSL:\n\nBridgetown.initializer :my_initializer do |config|\n  config.init :another_initializer\nend\n\n\nUsing only, except, and understanding initialization contexts\n\nThere are multiple initialization contexts within the Bridgetown environment. By default, using init or setting configuration options will apply to all possible contexts. But you can limit certain settings by using only and except. Here’s a list of available contexts:\n\n\n  static: This context is activated only for static builds (aka when running bin/bridgetown build or bin/bridgetown deploy, or during the static build process of bin/bridgetown start).\n  server: This context is activated only when running the Roda web server (aka when running bin/bridgetown start).\n  console: This context is activated only when running the Bridgetown console via bin/bridgetown console.\n  rake: This context is activated when you run a Rake task and it either calls run_initializers or accesses site.\n\n\nSo for example, your configure block could include this:\n\nonly :static do\n  puts \"I get run only for static builds!\"\nend\n\nexcept :static do\n  puts \"I get run for any context other than a static build!\"\nend\n\n\nYou can pass multiple contexts to  only or except:\n\nonly :static, :server do\n  puts \"I get run for both static builds and the server process, but not for the console or Rake tasks.\"\nend\n\n\nAnd finally, you can set configuration options that are broadly applicable, but override them in a specific context.\n\nmy_val 123\n\nonly :server do\n  init :parse_routes\n  my_val 456\nend\n\nputs my_val # =&gt; 123 for most contexts, 456 for the server context\n\n\nAdding hooks\n\nIt’s generally recommended to add hooks using a Builder, but in case you need to tap into a particular hook before builders have been initialized, you can use a hook call within the configuration file directly:\n\nhook :site, :after_init do |site|\n  # do something with the site var\nend\n\n\nYou also have the option of using the more verbose Bridgetown::Hooks.register_one code within an specific initializer file within config or in a gem:\n\n# config/initializers.rb\ninit :redact_string, require_gem: false do\n  bad_string \"Super Secret Spy\"\nend\n\n# config/redact_string.rb\nBridgetown.initializer :redact_string do |bad_string:|\n  Bridgetown::Hooks.register_one :resources, :post_read do |resource|\n    resource.content = resource.content.gsub(bad_string, \"[REDACTED]\")\n  end\nend\n\n\nAdding roda blocks\n\nIf you wish to configure your site’s Roda server, including setting up Roda plugins, you can add a roda block to your configuration. This provides a convenient alternative to placing configuration in your Roda class directly.\n\nThe app argument of a roda block is the class of your Roda application (typically RodaApp) in a Bridgetown project.\n\nroda do |app|\n  app.plugin :default_headers,\n    'Content-Type'=&gt;'text/html',\n    'Strict-Transport-Security'=&gt;'max-age=16070400;',\n    'X-Content-Type-Options'=&gt;'nosniff',\n    'X-Frame-Options'=&gt;'deny',\n    'X-XSS-Protection'=&gt;'1; mode=block'\nend\n\n\nWhile it’s not strictly required that you place a Roda block inside of an only :server do block, it’s probably a good idea that you do since Roda blocks aren’t used in any other configuration context.\n\n\n  \n  As mentioned above, you can still add and configure plugins directly in your Roda class file (server/roda_app.rb) like any standard Roda application, but using a Roda configuration block alongside your other initialization steps is a handy way to keep everything consolidated. Bear in mind that the Roda blocks are all executed prior to anything defined within the class-level code of server/roda_app.rb, so if you write any code in a Roda block that relies on state having already been defined in the app class directly, it will fail. Best to keep Roda block code self-contained, or reliant only on other settings in the Bridgetown initializers file.\n\n\nSSR &amp; Dynamic Routes\n\nThe SSR features of Bridgetown, along with its companion file-based routing features, are configurable via initializers.\n\ninit :ssr, sessions: true # the dotenv initializer is also recommended, more on that below\n\n# optional:\ninit :\"bridgetown-routes\"\n\n\nIf you want to run some specific site setup code on first boot, or any time there’s a file refresh in development, provide a setup block inside of the SSR initializer.\n\ninit :ssr do\n  sessions true\n  setup -&gt; site do\n    # access the site object, add data with `site.data`, whatever\n  end\nend\n\n\nFor the file-based routing plugin, you can provide additional configuration options to add new source paths (relative to the src folder, unless you specify an absolute file path) or add other routable extensions (for example to support a custom template engine):\n\ninit :\"bridgetown-routes\", additional_source_paths: [\"some_more_routes\"], additional_extensions: [\"tmpl\"]\n\n\nFor more on how SSR works in Bridgetown, check out our Routes documentation here.\n\nInline Builders\n\nInstead of writing a standalone Builder class in your site project or gem-based plugin, you can also define a builder directly within a configuration or initializer. This is a handy shortcut for minimalist Bridgetown site structures, or a plugin with very modest needs in its associated builder. Note that the builder is always subclassed directly from Bridgetown::Builder and not the site project’s SiteBuilder.\n\nBridgetown.initializer :create_a_builder do |config|\n  config.builder :MyNewBuilder do\n    def build\n      # you can write methods and use the Builder DSL here\n    end\n  end\nend\n\n# then...\n\nBridgetown.configure do\n  init :create_a_builder\n\n  builder :SomeOtherBuilder do\n    def build\n      # ...\n    end\n  end\nend\n\n\nSource Manifests\n\nPlugin &amp; theme authors can add new content (resources, layouts, components, and more) to any Bridgetown project where they’re installed by using the Source Manifest API. Read this documentation to learn more.\n\nLow-level Boot Customization\n\nIf you need to run Ruby code at the earliest possible moment, essentially right when the bridgetown executable has finished its startup process, you can add a config/boot.rb file to your repo. This is particularly useful if you wish to extend bridgetown with new commands.\n\n# Normally the following is run automatically, so by adding config/boot.rb, you should include\n# this Bundler setup:\nBundler.setup(:default, Bridgetown.env)\n\n# Now you can require a gem which adds a command to `bridgetown` via Samovar:\nrequire \"some_gem_here\"\n\n# Or require your own Ruby file:\nrequire_relative \"../ruby_code_file.rb\"\n\n\nRead more about defining Samovar-based commands here.\n\nBuilt-in Initializers\n\nBridgetown ships with several initializers you can add to your configuration. In future versions of Bridgetown, we expect to make our overall architecture a little more modular so you can use the initializer system to specify only those key features you need (and by omission which ones you don’t!).\n\nDotenv\n\nThe Dotenv gem allows you to manage environment variables with your Bridgetown project. Add the gem to your Gemfile (bundle add dotenv), and then add the initializer to your configuration:\n\ninit :dotenv\n\n\nNow anywhere in your Ruby plugins, templates, etc., you can access environment variables via ENV once you’ve defined your .env file. Our integration also supports specially-named files such as .env.development, .env.test, etc.\n\nExternal Content Sources\n\nYou can load in additional content, such as Markdown files and associated images, from folders outside of a Bridgetown site project. Read this documentation to learn more.\n\nWikilinks\n\nYou can enable [[wikilinks]] style links within your Markdown content. Read this documentation to learn more.\n\nInflector\n\nYou can configure the inflector used by Zeitwerk and models. A few acronyms are provided by default like HTML, CSS, and JS, so a file like html_processor.rb could be defined by HTMLProcessor. You can add more inflection rules like so:\n\nconfig.inflector.configure do |inflections|\n  inflections.acronym \"W3C\"\n  inflections.plural \"virus\", \"viruses\" # specify a rule for #pluralize\n  inflections.singular \"thieves\", \"thief\"   # specify a rule for #singularize\nend\n\n\nBridgetown’s inflector is based on Dry::Inflector, so you can read up on how to add inflection rules here.\n\nParse Roda Routes\n\nBecause of how Roda’s dynamic routing tree works, there’s no programmatic method of listing out all the routes in your application.\n\nHowever, Roda provides a convention which lets you add code comments next to your routing blocks. These comments are then converted to a JSON file containing route information which can then be printed out with a single command. Here is documentation on how to write these route comments.\n\nTo begin with, run bundle add roda-route_list to add the route parser to your Gemfile. (You can skip this step if you’re also using the bridgetown-routes plugin.)\n\nThen add to your configuration:\n\nonly :server do\n  init :parse_routes\nend\n\n\nNow every time your application server starts up, it will save .routes.json to your repo root with a JSON listing of the routes. Then you can run the following command to print out all your routes:\n\nbin/bridgetown roda:routes\n\n\nIn addition, this will add the route_list plugin to your Roda app automatically. This allows you to call RodaApp.route_list to get a Ruby hash of all the routes, as well as access the listed_route method for accessing specific routes by name. For example, if you had a route comment defined as:\n\n# route[subscribe_to_newsletter]: POST /account/subscribe-to-newsletter/:newsletter\n\n\nYou could then get the relative URL for use in links or redirection, e.g.:\n\nr.redirect listed_route(:subscribe_to_newsletter, newsletter: \"product_promotion\")"
        },
        {
          "id": "docs-configuration-liquid",
          "title": "Liquid Options",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/liquid",
          "content": "Liquid&#8217;s response to errors can be configured by setting error_mode. The\noptions are\n\n\n  lax &#8212; Ignore all errors.\n  warn &#8212; Output a warning on the console for each error.\n  strict &#8212; Output an error message and stop the build.\n\n\nYou can also configure Liquid&#8217;s renderer to catch non-assigned variables and\nnon-existing filters by setting strict_variables and / or strict_filters\nto true respectively.\n\nDo note that while error_mode configures Liquid&#8217;s parser, the strict_variables\nand strict_filters options configure Liquid&#8217;s renderer and are consequently,\nmutually exclusive."
        },
        {
          "id": "docs-configuration-markdown",
          "title": "Markdown Options",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/markdown",
          "content": "Kramdown\n\nKramdown is the Markdown renderer for Bridgetown. Below is a list of the\ncurrently supported options:\n\n\n  auto_id_prefix - Prefix used for automatically generated header IDs\n  auto_id_stripping - Strip all formatting from header text for automatic ID generation\n  auto_ids - Use automatic header ID generation\n  entity_output - Defines how entities are output\n  footnote_backlink - Defines the text that should be used for the footnote backlinks\n  footnote_backlink_inline - Specifies whether the footnote backlink should always be inline\n  footnote_nr - The number of the first footnote\n  gfm_quirks - Enables a set of GFM specific quirks\n  hard_wrap - Interprets line breaks literally\n  header_offset - Sets the output offset for headers\n  html_to_native - Convert HTML elements to native elements\n  line_width - Defines the line width to be used when outputting a document\n  link_defs - Pre-defines link definitions\n  mark_highlighting - When true, allows ==text== or ::text:: to highlight text with &lt;mark&gt; tags (this is a custom Bridgetown-only option)\n  math_engine - Set the math engine\n  math_engine_opts - Set the math engine options\n  parse_block_html - Process kramdown syntax in block HTML tags\n  parse_span_html - Process kramdown syntax in span HTML tags\n  smart_quotes - Defines the HTML entity names or code points for smart quote output\n  syntax_highlighter - Set the syntax highlighter\n  syntax_highlighter_opts - Set the syntax highlighter options\n  toc_levels - Defines the levels that are used for the table of contents\n  transliterated_header_ids - Transliterate the header text before generating the ID\n  typographic_symbols - Defines a mapping from typographical symbol to output characters\n\n\nFor more details about these options have a look at the Kramdown configuration documentation.\n\nCustom Markdown Processors\n\nIf you&#8217;re interested in creating a custom markdown processor, you&#8217;re in luck! Create a new class in the Bridgetown::Converters::Markdown namespace:\n\nclass Bridgetown::Converters::Markdown::MyCustomProcessor\n  def initialize(config)\n    require 'funky_markdown'\n    @config = config\n  rescue LoadError\n    STDERR.puts 'You are missing a library required for Markdown. Please run:'\n    STDERR.puts '  $ [sudo] gem install funky_markdown'\n    raise FatalException.new(\"Missing dependency: funky_markdown\")\n  end\n\n  def convert(content)\n    ::FunkyMarkdown.new(content).convert\n  end\nend\n\n\nOnce you&#8217;ve created your class and have it properly set up either as a plugin\nin the plugins folder or as a gem, specify it in your config:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  markdown :MyCustomProcessor\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\nmarkdown: MyCustomProcessor"
        },
        {
          "id": "docs-configuration-options",
          "title": "Configuration Options",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/options",
          "content": "The tables below list the available settings for Bridgetown, and the various options (specified in the configuration file) and flags (specified on the command-line) that control them.\n\nGlobal Configuration\n\n\n  \n    \n      Setting\n      \n        Options and Flags\n      \n    \n  \n  \n    \n      \n        Site Source\n        Change the directory where Bridgetown will read files\n        Default: src\n      \n      \n        source: DIR\n        -s, --source DIR\n      \n    \n    \n      \n        Site Destination\n        Change the directory where Bridgetown will write files\n        Default: output\n      \n      \n        destination: DIR\n        -d, --destination DIR\n      \n    \n    \n      \n        Template Engine\n        Change the template engine Bridgetown uses by default to process content files.\n        Default: erb\n      \n      \n        template_engine: ENGINE (erb, serbea, liquid, etc.)\n      \n    \n    \n      \n        Permalink\n        Change the default permalink style or template used by pages &amp; blog posts. Documentation here and read this additional information.\n      \n      \n        permalink: STYLE or TEMPLATE\n      \n    \n    \n      \n        Fast Refresh\n        Control the behavior of Bridgetown's live reload functionality in development.\n        Default: true\n      \n      \n        fast_refresh: BOOL\n      \n    \n    \n      \n        Slugify Mode\n        Determine the method of transforming characters when slugs are generated. Info on available options here.\n        Default: pretty\n      \n      \n        slugify_mode: MODE\n      \n    \n    \n      \n        \n          Disable Disk Cache\n        \n        \n          Disable caching of content to disk in order to skip creating a\n          .bridgetown-cache or similar directory at the source\n          to avoid interference with virtual environments and third-party\n          directory watchers.\n        \n      \n      \n        disable_disk_cache: BOOL\n        --disable-disk-cache\n      \n    \n    \n      \n        Exclude\n        \n          Exclude directories and/or files from the\n          conversion. These exclusions are relative to the site's\n          source directory and cannot be outside the source directory.\n        \n      \n      \n        exclude: [DIR, FILE, ...]\n      \n    \n    \n      \n        Include\n        \n          Force inclusion of directories and/or files in the conversion.\n          .htaccess is a good example since dotfiles are excluded\n          by default.\n        \n      \n      \n        include: [DIR, FILE, ...]\n      \n    \n    \n      \n        Keep files\n        \n          When clobbering the site destination, keep the selected files.\n          Useful for files that are not generated by Bridgetown; e.g. files or\n          assets that are generated by your build tool.\n          The paths are relative to the destination.\n        \n      \n      \n        keep_files: [DIR, FILE, ...]\n      \n    \n    \n      \n        Additional Watch Paths\n        Supply additional directories not normally covered by Bridgetown or Zeitwerk's autoloader to the file watcher to trigger a rebuild/reload. The paths are relative to the site root (but you can also provide absolute paths).\n      \n      \n        additional_watch_paths: [DIR, DIR, ...]\n      \n    \n    \n      \n        Time Zone\n        \n            Set the time zone for site generation. This sets the TZ\n            environment variable, which Ruby uses to handle time and date\n            creation and manipulation. Any entry from the\n            IANA Time Zone\n            Database is valid, e.g. America/New_York. A list of all\n            available values can be found \n            here. When serving on a local machine, the default time zone is set by your operating system. But when served on a remote host/server, the default time zone depends on the server's setting or location.\n        \n      \n      \n        timezone: TIMEZONE\n      \n    \n    \n      \n        Encoding\n        \n            Set the encoding of files by name. The default value is utf-8.\n            Available encodings can be shown by the\n            command ruby -e 'puts Encoding::list.join(\"\\n\")'.\n        \n      \n      \n        encoding: ENCODING\n      \n    \n    \n      \n        Defaults\n        \n            Set defaults for front matter\n            variables.\n        \n      \n      \n        More Information Here\n      \n    \n  \n\n\n\n  \n  Destination folders are cleaned upon site builds\n\nThe contents of destination are automatically cleaned when the site is built.\nFiles or folders that are not created by your site will be removed. If you need to\nretain certain files/folders, specify them within the keep_files configuration\ndirective. (For instance, Bridgetown automatically keeps compiled frontend assets\nfrom esbuild.)\n\nDo not use an important location for destination instead, use it as\na staging area and copy files from there to your web server.\n\n\nBuild Command Options\n\n\n  \n    \n      Setting\n      Options and Flags\n    \n  \n  \n    \n      \n        Regeneration\n        Enable auto-regeneration of the site when files are modified.\n      \n      \n        -w, --[no-]watch\n      \n    \n    \n      \n        Configuration\n        Specify config files instead of using bridgetown.config.yml automatically. Settings in later files override settings in earlier files.\n      \n      \n        --config FILE1[,FILE2,...]\n      \n    \n    \n      \n        Environment\n        Use a specific environment value in the build.\n      \n      \n        BRIDGETOWN_ENV=production\n      \n    \n    \n      \n        Future\n        Publish posts or collection documents with a future date.\n      \n      \n        future: BOOL\n        --future\n      \n    \n    \n      \n        Unpublished\n        Render posts that were marked as unpublished.\n      \n      \n        unpublished: BOOL\n        -U, --unpublished\n      \n    \n    \n      \n        Ruby Front Matter\n        Process Ruby front matter (default true)\n      \n      \n        ruby_in_front_matter: BOOL\n      \n    \n    \n      \n        Force polling\n        Force watch to use polling.\n      \n      \n        force_polling: BOOL\n        --force_polling\n      \n    \n    \n      \n        Verbose output\n        Print verbose output.\n      \n      \n        -V, --verbose\n      \n    \n    \n      \n        Silence Output\n        Silence the normal output from Bridgetown\n        during a build\n      \n      \n        -q, --quiet\n      \n    \n    \n      \n        Liquid profiler\n        \n            Generate a Liquid rendering profile to help you identify performance bottlenecks.\n        \n      \n      \n        profile: BOOL\n        --profile\n      \n    \n    \n      \n        Strict Front Matter\n        \n            Cause a build to fail if there is a YAML syntax error in a page's front matter.\n        \n      \n      \n        strict_front_matter: BOOL\n        --strict_front_matter\n      \n    \n    \n      \n        Base Path\n        Serve the website from the given base path.\n      \n      \n        base_path: URL\n        --base_path URL\n      \n    \n  \n\n\n\n  \n  Do not use tabs in configuration files\n\nThis will either lead to parsing errors, or Bridgetown will revert to the\ndefault settings. Use spaces instead.\n\n\nAdditional Configuration File Settings\n\nBridgetown runs with the following configuration options by default. Alternative\nsettings for these options can be explicitly specified in the config/initializers.rb or bridgetown.config.yml configuration file.\n\nNote that if you change plugins_dir, the option is relative to the current working directory, not the content source folder (aka src). Other *_dir options are relative to the source folder.\n\n# Where things are\nplugins_dir         : plugins\ncollections_dir     : .\nlayouts_dir         : _layouts\ncomponents_dir      : _components\nislands_dir         : _islands\npartials_dir        : _partials\ncache_dir           : .bridgetown-cache\ncollections:\n  posts:\n    output          : true\nadditional_watch_paths: []\n\n# Handling Reading\ninclude             : [\".htaccess\", \"_redirects\", \".well-known\"]\nexclude             : [],\nkeep_files          : [\".git\", \".svn\", \"_bridgetown\"]\nencoding            : \"utf-8\"\nmarkdown_ext        : \"markdown,mkdown,mkdn,mkd,md\"\nstrict_front_matter : false\n\n# Filtering Content\nfuture              : false\nunpublished         : false\n\n# Conversion\nmarkdown            : kramdown\nhighlighter         : rouge\n\n# System\ntimezone            : null # e.g. America/Los_Angeles\nquiet               : false\nverbose             : false\ndefaults            : []\n\nliquid:\n  error_mode        : warn\n  strict_filters    : false\n  strict_variables  : false\n\n# Markdown Processor\nkramdown:\n  auto_ids          : true\n  entity_output     : as_char\n  toc_levels        : [1, 2, 3, 4, 5, 6]\n  smart_quotes      : lsquo,rsquo,ldquo,rdquo\n  input             : GFM\n  hard_wrap         : false\n  guess_lang        : true\n  footnote_nr       : 1\n  show_warnings     : false\n  include_extraction_tags: false\n  mark_highlighting : true"
        },
        {
          "id": "docs-configuration-web-server",
          "title": "Web Server Configuration",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/web-server",
          "content": "Bridgetown requires a Rack-compliant web server, (along with Roda) for serving up statically-built files as well as any dynamic routes. The default server is Falcon, but we also natively support Puma.\n\nIn development (bin/bridgetown start), Bridgetown uses Rackup to start the web server. Since this is a generic interface, the configuration options are limited. By default, Bridgetown serves over HTTP via port 4000, bound to 0.0.0.0 (this makes it accessible via localhost as well as the network).\n\nYou can change the port using -P:\n\n$ bin/bridgetown start -P 6000\n\n\nAnd, to change the IP address to something other than 0.0.0.0, provide a --bind / -B argument:\n\n$ bin/bridgetown start -B 192.168.1.1\n\n\nIn production, use your server&#8217;s command line program to start it directly. Bridgtown creates a configuration file for your chosen web server in the config/ folder. Use this to customize your deployment and refer to your chosen server&#8217;s documentation for further information.\n\n\n  Configuring Falcon for production\n  The Puma configuration file"
        },
        {
          "id": "docs-configuration-willamette",
          "title": "Willamette, a First-Party Bridgetown Theme",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "customize-your-site",
          "tags": "",
          "url": "/docs/configuration/willamette",
          "content": "If you&#8217;re searching for Bridgetown themes to help you get a leg up on your new website project, Willamette might be what you&#8217;re looking for!\n\nCreate blogs, portfolios, documentation sites, galleries, business dashboards, and more. Willamette is a new premier open source theme by the makers of Bridgetown, designed to serve the publishing needs of indie creators &amp; developers.\n\n\n  Learn More"
        },
        {
          "id": "docs-content-dsd",
          "title": "Declarative Shadow DOM",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "layouts",
          "tags": "",
          "url": "/docs/content/dsd",
          "content": "Welcome to the future! Declarative Shadow DOM (DSD) represents a huge shift in the way we architect and promote modularity on a web page. You can use DSD in your layouts, components, and generally anywhere it would be beneficial to increase the separation between presentation logic &amp; content and work with advanced scoped styling APIs.\n\nHeads up: if you’re upgrading from Bridgetown 1.2 or previous, you’ll need to use esbuild (not Webpack) and update your base esbuild configuration:\n\nbin/bridgetown esbuild update\n\n\nSee the esbuild setup documentation for further upgrade instructions.\n\n\n  \n  You may also need to update your esbuild.config.js file so it includes the following configuration option:\n\n  globOptions: {\n    excludeFilter: /\\.(dsd|lit)\\.css$/\n  }\n\n\n\nTable of Contents\n\n  Intro to DSD\n  The dsd Helper\n  Components with Sidecar CSS\n  Working with DSD in JavaScript and Hydrating Islands\n\n\nIntro to DSD\n\nIt’s helpful to describe the power and flexibility of DSD by comparing it to what has come before. Let’s look at a typical web page layout and how we might style it. (We’ll only concern ourselves with &lt;body&gt; for this example.)\n\n&lt;body&gt;\n  &lt;header&gt;I'm the Page Header&lt;/header&gt;\n  \n  &lt;article&gt;\n    &lt;header&gt;I'm the Article Header&lt;/header&gt;\n  &lt;/article&gt;\n  \n  &lt;style&gt;\n  header {\n    color: indigo;  \n  }\n  &lt;/style&gt;\n&lt;/body&gt;\n\n\nOops, this isn’t what we want. By styling the header tag, we’ve affected both the main header of the page and the header of an individual article block. Let’s adding some selector-based scoping to remedy this:\n\n&lt;body&gt;\n  &lt;header&gt;I'm the Page Header&lt;/header&gt;\n  \n  &lt;article&gt;\n    &lt;header&gt;I'm the Article Header&lt;/header&gt;\n  &lt;/article&gt;\n  \n  &lt;style&gt;\n  body &gt; header {\n    color: indigo;  \n  }\n  \n  article &gt; header {\n    color: darkorchid;\n  }\n  &lt;/style&gt;\n&lt;/body&gt;\n\n\nThis is better, but our page layout styles and our “component” styles are still too intermingled. We could remedy this by creating components for things like the article tag, but in more advanced components keeping styles of a component’s “internals” and its public-facing child content from colliding with each other can get tricky. And what if you wanted each layout also to have some unique styles but you don’t want to add override &lt;style&gt; tags or mess around with scoping to body IDs which can land you in specificity wars. Which of these would win?\n\nbody#fancy-layout article &gt; header {\n  font-weight: 600;\n}\n\narticle.boldest &gt; header {\n  font-weight: 900;\n}\n\n\nWouldn’t it be great if we could separate the internal styling from outward-facing styling of each modular building block of a website? Wouldn’t it be great if we could define “styling APIs” for our components? Wouldn’t it be great if we could simplify the markup of our actual content by ensuring it’s not locked inside of all the presentational/structural minutiae of a layout?\n\nThe dsd Helper\n\nEnter Declarative Shadow DOM.\n\nBridgetown lets us use the {% dsd %}…{% enddsd %} Liquid tag or &lt;%= dsd do %&gt;…&lt;% end %&gt; Ruby helper to define a DSD template within any HTML template. Here’s an example expanding from the one above:\n\n&lt;body&gt;\n  &lt;%= dsd do %&gt;\n    &lt;header&gt;&lt;slot name=\"header\"&gt;&lt;/slot&gt;&lt;/header&gt;\n    &lt;slot&gt;&lt;/slot&gt;\n    \n    &lt;style&gt;\n      header {\n        color: indigo;\n      }\n    &lt;/style&gt;\n  &lt;% end %&gt;\n\n  &lt;h1 slot=\"header\"&gt;I'm the Page Header&lt;/h1&gt;\n  \n  &lt;p&gt;Page content.&lt;/p&gt;\n  \n  &lt;article&gt;\n    &lt;%= dsd do %&gt;\n      &lt;header&gt;&lt;slot name=\"header\"&gt;&lt;/slot&gt;&lt;/header&gt;\n      &lt;slot&gt;&lt;/slot&gt;\n      \n      &lt;style&gt;\n        header {\n          color: darkorchid;\n        }\n      &lt;/style&gt;\n    &lt;% end %&gt;\n\n    &lt;h2 slot=\"header\"&gt;I'm the Article Header&lt;/h2&gt;\n    \n    &lt;p&gt;Article content.&lt;/p&gt;\n  &lt;/article&gt;\n&lt;/body&gt;\n\n\nWhat’s great about this approach is:\n\n\n  Only the element with a DSD template is affected by the associated styles. The header tag at the body level, and the header tag at the article level are separated from each other behind shadow boundaries. This provides what we like to call encapsulation (borrowing terminology from object-oriented programming). Before all HTML + styles operated in a single global namespace called “the DOM”. Now we can actually define encapsulated HTML + style DOM trees!\n  Scoping isn’t just about styles…it works in JavaScript too! Consider document.body.querySelectorAll(\"header\"). Normally, this would give you a list of all header tags across the entire webpage, no matter where they appear. But now, you could call document.body.shadowRoot.querySelectorAll(\"header\") and get that single header in your DSD template. Wut?? Yep, it totally works.\n  By utilizing the slots mechanism that’s part of the shadow DOM spec, you can build your DSD template around various pieces of presentation logic, styled by the template accordingly, and then in the “light DOM” your content can reference those slots to make it super obvious what the content is and how it might get presented. Of course many server-side templating systems, Bridgetown included, can make this somewhat clear from a development standpoint by providing building blocks such as layouts and resources and components, but by the time it gets to the browser, you don’t really have any sense how the content and the presentation logic are built out and modularized. Everything gets flatted into a tree of DOM nodes. With shadow DOM, you can actually see at the markup and browser dev tools levels how everything gets composed together across your components and templates, making inspecting and debugging much easier. It’s like HTML suddenly got super powers!\n\n\nIn addition to the benefits above, you also have the ability to leverage CSS Shadow Parts (which only work when you have, er, shadow DOM—hence the name!). What’s a shadow part? It’s when you use the part= attribute on an element inside your DSD template, and by doing so it makes it styleable from the “outside”. Defining parts and labeling them appropriately is a fantastic way to build up a true “style API” for each layout or component.\n\nComponents with Sidecar CSS\n\nAs mentioned already, you can use DSD in your Liquid and Ruby components. In addition, Ruby components allow you to write CSS in dedicated stylesheets (aka my_component.dsd.css) and reference them directly from your component’s DSD template. Let’s take a look:\n\n&lt;!-- src/_components/simple_component.erb --&gt;\n&lt;simple-component&gt;\n  &lt;%= dsd do %&gt;\n    &lt;slot name=\"caption\"&gt;&lt;/slot&gt;\n    &lt;div&gt;\n      &lt;slot&gt;&lt;/slot&gt;\n    &lt;/div&gt;\n    &lt;%= dsd_style %&gt;\n  &lt;% end %&gt;\n\n  &lt;%= content %&gt;\n&lt;/simple-component&gt;\n\n\n/* src/_components/simple_component.dsd.css */\n:host {\n  display: block;\n  background: var(--surface-1);\n  padding: var(--size-4);\n}\n\nslot[name=\"caption\"] {\n  display: block;\n  font-weight: bold;\n}\n\ndiv {\n  margin-block-start: var(--size-4);\n}\n\n\nMake sure you use the .dsd.css extension so esbuild knows not to attempt bundling the component stylesheet into the global index.css stylesheet.\n\n\n  \n  Sidecar CSS files processed through the dsd_style helper do not get run through PostCSS—aka they must be 100% “vanilla” CSS. Don’t be surprised if you try using a feature that’s uniquely enabled by your PostCSS config and it’s not available within the DSD template.\n\n\n\n  \n  There are certain “gotchas” when working with scoped styles inside a shadow root. Only a small number of global styles get inherited within a DSD template. For example, you may be surprised if you add an &lt;a href&gt; tag inside your DSD template and it looks like a browser’s default link style, not your site’s link style! There are all sorts of workarounds for issues that may arise, and we hope to refer to helpful educational resources as time passes and DSD becomes more widespread. In the meantime…try asking the community for assistance!\n\n\nWorking with DSD in JavaScript and Hydrating Islands\n\nFurther documentation coming soon…\n\nMeanwhile, you may be interested in our documentation on Islands Architecture."
        },
        {
          "id": "docs-content-external-sources",
          "title": "External Content Sources",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "resources",
          "tags": "",
          "url": "/docs/content/external-sources",
          "content": "There are times you may want Bridgetown to include resources you’ve created which live outside of your site project folder hierarchy. In particular, you may have a folder(s) of Markdown files already saved somewhere and would like those to be included in your site build.\n\nTo add a new content source to your project, use the external_sources plugin initializer in your config/initializers.rb file:\n\ninit :external_sources do\n  contents do\n    pages \"path/to/folder\"\n  end\nend\n\n\nIn this example, pages is the name of the collection you want to load the content into. The default pages option is a safe bet, but you can set up one or more custom collections and load the content in that way. For example:\n\ncollections do\n  docs do\n    output true\n    # Using a `:path.*`-style permalink is recommended to ensure links between\n    # Markdown files work as intended:\n    permalink \"documentation/:path.*\"\n  end\nend\n\n# Set up front matter defaults for the collection:\nconfig.defaults &lt;&lt; {\n  scope: { collection: :docs },\n  values: { layout: :default },\n}\n\n# Configure the plugin:\ninit :external_sources do\n  contents do\n    docs \"/var/wiki\"\n  end\nend\n\n\nIf you want to include only certain files from an external source, you can do so in a filters block. For example, here is how to add a filter to external sources to the pages collection, so as to include only files and folders whose name does not start with an underscore:\n\ninit :external_sources do\n  contents do\n    pages \"path/to/folder\"\n  end\n\n  filters do\n    pages -&gt;(name, path) { !name.start_with?(\"_\") }\n  end\nend\n\n\nLoading Markdown Content from Other Applications\n\nUnlike with standard Bridgetown content files within src where you must use front matter if you wish Markdown files get processed as resources instead of static files, Markdown files in external sources don’t require any front matter. However, in order to ensure those files are rendered with a layout, you’ll need to set up front matter defaults (as shown in the example above).\n\nYou may also want to set up a hook to establish extra front matter data based on content within the files, such as extracting a # Title Heading:\n\n# plugins/builders/hooks.rb\nclass Builders::Hooks &lt; SiteBuilder\n  def build\n    # Use the name of your collection for the first argument:\n    hook :docs, :post_read do |resource|\n      title = nil\n      resource.content = resource.content.sub(%r!^# .*?$!) do |line|\n        title = line.delete_prefix(\"# \")\n        \"\"\n      end\n      resource.data.title = title if title\n    end\n  end\nend\n\n\nThere are many applications which let you author and edit Markdown content on the filesystem. One such application which has become popular across a variety of platforms is Obsidian. An issue you may run into with content systems like this is when links to other Markdown files end in an extension such as .md. This will result in 404 Not Found errors, since the built HTML page URLs do not end in .md. You can use an HTML Inspector to locate and remove such extensions:\n\n# plugins/builders/inspectors.rb\nclass Builders::Inspectors &lt; SiteBuilder\n  def build\n    inspect_html do |document|\n      document.query_selector_all(\"a\").each do |anchor|\n        next unless anchor[:href].end_with?(\".md\")\n\n        anchor[:href] = anchor[:href].delete_suffix(\".md\")\n      end\n    end\n  end\nend\n\n\nFolder Inversion and Minimalist Installations\n\nAn interesting method of organizing your content and Bridgetown project sites would be to place all of Bridgetown’s Ruby, JavaScript, layouts, templates, and other supporting files in a subfolder of the main content folder. That way, content authors are able to focus on only the content saved at the top-level, with all of the Bridgetown support files tucked away and effectively “hidden”. (Perhaps even in a folder named .bridgetown!)\n\ninit :external_sources do\n  contents do\n    pages \"..\" # relative paths are always resolved against the Bridgetown root folder\n  end\nend\n\n\nYou also have the option of trimming away some of the boilerplate and support files that Bridgetown ships with by default in a new project. Here is a list of the only files you need for Bridgetown to run:\n\n├── config\n│   └── initializers.rb\n├── Gemfile\n├── Gemfile.lock\n└── src\n   └── _layouts\n      └── default.erb\n\n\nNote that this doesn’t include esbuild for managing frontend assets like JS &amp; CSS (you would need to go “buildless” and do that yourself manually or use CDNs like esm.sh)."
        },
        {
          "id": "docs-content-front-matter-defaults",
          "title": "Front Matter Defaults",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "resources",
          "tags": "",
          "url": "/docs/content/front-matter-defaults",
          "content": "Using front matter is the way you specify metadata for the file-based resources for your site, setting things like a default layout, or customizing the title, or providing taxonomy terms.\n\nSometimes you will find you&#8217;re repeating a few configuration options over and over. For example, setting the same layout in each file, adding the same category, etc. You might even want to add author names which are the same for the majority of posts.\n\nThere are two ways to accomplish this: the data cascade, and via your site&#8217;s configuration file.\n\nThe Data Cascade\n\nYou can add _defaults.yml (also .yaml or .json) files anywhere in your source tree, which will then cause a &#8220;data cascade&#8221;. In other words, any resources in that folder or in a subfolder will use the front matter data contained in the defaults file. Defaults files in subfolders can also potentially overwrite values contained in parent folders (hence the term &#8220;cascade&#8221;).\n\nFor example, if you want all &#8220;posts&#8221; collection resources to have the layout &#8220;post&#8221; without having to repeatedly write layout: post front matter, add _defaults.yml to the src/_posts folder:\n\nlayout: post\n\n\nNow, if you have some posts in a subfolder (let&#8217;s say fancy_posts) and you want those posts to use the &#8220;fancy_post&#8221; layout, you could add a second _defaults.yml file in that subfolder like so:\n\nlayout: fancy_post\n\n\nNow all the fancy_posts posts will use the fancy_post layout. If you had other front matter variables in the parent _defaults.yml in src/_posts, those would carry over to the fancy_posts defaults unless you decide to override them explicitly.\n\nAlso, keep in mind these are &#8220;default&#8221; values, so if you were to add layout: some_other_layout to a post, it would overwrite either layout: post or layout: fancy_post. This is what makes front matter defaults so powerful!\n\n\n  \n  Trick out your collections\n\nDefaults files work well for custom collections! Add a _defaults.yml to the collection root folder to set layouts and other variables for your entire collection.\n\n\n\n  \n  Think globally\n\nYou can also add a defaults file to src itself! For example, if you wanted every resource on your site to start off with a default thumbnail image, you could add image: /images/thumbnail_image.jpg to a defaults file in src and it would apply globally.\n\n\nConfiguration-based Front Matter Defaults\n\nInstead of (or in addition to) the data cascade, you can set front matter defaults in your configuration file using a special rules-based syntax. To do this, add a defaults key to the bridgetown.config.yml file in your project&#8217;s root folder.\n\nLet&#8217;s say that you want to add a default layout to all pages and posts in your site. You would add this to your bridgetown.config.yml file:\n\nTODO: we need Initializers syntax examples here\n\ndefaults:\n  - values:\n      layout: \"default\"\n\n\n\n  \n  Stop and rerun bridgetown start\n\nThe bridgetown.config.yml main configuration file contains global configurations and variable definitions that are read once at execution time. Changes made to bridgetown.config.yml will not trigger an automatic regeneration.\n\nUse Data Files to set up metadata variables and other structured content you can be sure will get reloaded during automatic regeneration.\n\n\nYou probably don&#8217;t want to set a layout on every file in your project, so you can also specify a collection value under the scope key.\n\ndefaults:\n  - scope:\n      collection: \"posts\"\n    values:\n      layout: \"default\"\n\n\nThis will only set the layout for resources in the posts collection.\n\nYou can set multiple scope/values pairs for defaults.\n\ndefaults:\n  - scope:\n      collection: \"pages\"\n    values:\n      layout: \"my-site\"\n  - scope:\n      path: \"projects\" # scopes to a particular path within your source folder\n      collection: \"pages\"\n    values:\n      layout: \"project\" # overrides previous default layout\n      author: \"Ursula K. Le Guin\"\n\n\nWith these defaults, all pages would use the my-site layout. Any html files that exist in the projects/ folder will use the project layout, if it exists. Those files will also have the resource.data.author variable set to Ursula K. Le Guin.\n\ncollections:\n  my_collection:\n    output: true\n\ndefaults:\n  - scope:\n      collection: \"my_collection\"\n    values:\n      layout: \"default\"\n\n\nIn this example, the layout is set to default inside the collection with the name my_collection.\n\nGlob patterns in Front Matter defaults\n\nIt is also possible to use glob patterns (currently limited to patterns that contain *) when matching defaults. For example, it is possible to set specific layout for each special-page.html in any subfolder of section folder.\n\ncollections:\n  my_collection:\n    output: true\n\ndefaults:\n  - scope:\n      path: \"section/*/special-page.html\"\n    values:\n      layout: \"specific-layout\"\n\n\n\n  \n  Globbing and Performance\n\nPlease note that globbing a path is known to have a negative effect on performance. Globbing a path will increase your build times in proportion to the size of the associated collection directory.\n\n\nPrecedence\n\nBridgetown will apply all of the configuration settings you specify in the defaults section of your bridgetown.config.yml file. You can choose to override settings from other scope/values pair by specifying a more specific path for the scope.\n\nIf you set defaults in the site configuration by adding a defaults section to your bridgetown.config.yml file, you can override those settings in an individual resource&#8217;s front matter. For example:\n\n# In bridgetown.config.yml\n...\ndefaults:\n  - scope:\n      path: \"projects\"\n      collection: \"pages\"\n    values:\n      layout: \"project\"\n      author: \"Ursula K. Le Guin\"\n      category: \"project\"\n...\n\n\n# In projects/foo_project.md\n---\nauthor: \"John Smith\"\nlayout: \"foobar\"\n---\nThe post text goes here...\n\n\nThe projects/foo_project.md resource would have the layout set to foobar instead of project and the author set to John Smith instead of Ursula K. Le Guin when the site is built."
        },
        {
          "id": "docs-content-pagination",
          "title": "Pagination",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "resources",
          "tags": "",
          "url": "/docs/content/pagination",
          "content": "Pagination support is built-in to Bridgetown, but it is not enabled by default. You can enable it in the config file using:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # within Bridgetown.configure do |config| block\npagination do\n  enabled true\nend\n    \n\n  \n  \n\n    pagination:\n  enabled: true\n    \n\n  \n\n\nPage Configuration\n\nTo facilitate pagination on any given page (like index.html, blog.md, etc.) include configuration in the resource&#8217;s front matter to specify which collection you&#8217;d like to paginate through:\n\n---\nlayout: page\npaginate:\n  collection: posts\n---\n\n\nThen you can use the paginator.resources logic to iterate through the collection&#8217;s resources.\n\n\n  ERB\n  Liquid\n\n  \n\n    # for earlier versions paginator.resources.each\n&lt;% paginator.each do |post| %&gt;\n  &lt;h1&gt;&lt;%= post.data.title %&gt;&lt;/h1&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% for post in paginator.resources %}\n  &lt;h1&gt;{{ post.data.title }}&lt;/h1&gt;\n{% endfor %}\n    \n\n  \n\n\nBy default, paginated pages will have 10 items per page. You can change this by modifying the per_page key like so:\n\npaginate:\n  collection: posts\n  per_page: 4\n\n\nYou can also control the sort field and order of the paginated result set separately from the default sort of the collection:\n\npaginate:\n  collection: movies\n  sort_field: rating\n  sort_reverse: true\n\n\nAttributes for Defining Pagination\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      collection\n      Required The collection to paginate\n    \n    \n      offset\n      Default: 0 Supports skipping x number of posts from the beginning of the post list\n    \n    \n      per_page\n      Default 10 Number of resources per page\n    \n    \n      permalink\n      Default: \"/page/:num/\" Supports :num as customizable elements\n    \n    \n      title\n      Default: \":title (Page :num)\" Supports :num customizable elements\n    \n    \n      sort_reverse\n      Default: true Sorts the posts in reverse order\n    \n    \n      sort_field\n      Default: \"date\" Sorts the posts by the specified field\n    \n    \n      limit\n      Default: 0 Limits how many content objects to paginate (default: 0, means all)\n    \n    \n      debug\n      Default: false Turns on debug output for the gem\n    \n  \n\n\nExcluding a Resource from the Paginator\n\nYou can exclude a resource from being included in the paginated items list via its front matter.\n\nexclude_from_pagination: true\n\n\nPagination Links\n\nTo display pagination links, use the paginator object as follows:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% if paginator.total_pages &gt; 1 %&gt;\n  &lt;ul class=\"pagination\"&gt;\n    &lt;% if paginator.previous_page %&gt;\n    &lt;li&gt;\n      &lt;a href=\"&lt;%= paginator.previous_page_path %&gt;\"&gt;Previous Page&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;% end %&gt;\n    &lt;% if paginator.next_page %&gt;\n    &lt;li&gt;\n      &lt;a href=\"&lt;%= paginator.next_page_path %&gt;\"&gt;Next Page&lt;/a&gt;\n    &lt;/li&gt;\n    &lt;% end %&gt;\n  &lt;/ul&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% if paginator.total_pages &gt; 1 %}\n  &lt;ul class=\"pagination\"&gt;\n    {% if paginator.previous_page %}\n    &lt;li&gt;\n      &lt;a href=\"{{ paginator.previous_page_path }}\"&gt;Previous Page&lt;/a&gt;\n    &lt;/li&gt;\n    {% endif %}\n    {% if paginator.next_page %}\n    &lt;li&gt;\n      &lt;a href=\"{{ paginator.next_page_path }}\"&gt;Next Page&lt;/a&gt;\n    &lt;/li&gt;\n    {% endif %}\n  &lt;/ul&gt;\n{% endif %}\n    \n\n  \n\n\nProperties Available\n\nThe paginator Ruby / Liquid object provides the following properties:\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      page\n      The number of the current page\n    \n    \n      per_page\n      Number of resources per page\n    \n    \n      resources\n      Resources (aka posts, etc.) available for the current page\n    \n    \n      total_resources\n      Total number of resources\n    \n    \n      total_pages\n      Total number of paginated pages\n    \n    \n      first_page_path\n      Returns the path to the first page, or nil if none\n    \n    \n      last_page\n      The number of the last page, or nil if none\n    \n    \n      last_page_path\n      Returns the path to the last page, or nil if none\n    \n    \n      next_page\n      The number of the next page, or nil if no subsequent page exists\n    \n    \n      next_page_path\n      The path to the next page, or nil if no previous page exists\n    \n    \n      previous_page\n      The number of the previous page, or nil if no previous page exists\n    \n    \n      previous_page_path\n      The path to the previous page, or nil if no previous page exists\n    \n  \n\n\nConsiderations When Using Pagination\n\nOn paginated pages, the originating collection is replaced by the paginator. Code in a layout like resource.collection.label will generate undefined method errors on your paginated page. Accessing the collection directly rather than through the paginator will also fail."
        },
        {
          "id": "docs-content-permalinks",
          "title": "Permalinks",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "resources",
          "tags": "",
          "url": "/docs/content/permalinks",
          "content": "A permalink is the determination of what the output URL of your resource will be. Every resource uses a permalink processor to figure out where to save your transformed resource in the output folder tree.\n\nResources in the pages collection are the most straightforward. The filenames and folder structure of your pages will result in matching output URLs. For example, a file saved at src/_pages/this/is/great.md would be output to /this/is/great/.\n\nFor resources in the posts collection, Bridgetown ships with few permalink “styles”. The posts permalink style is configured by using the permalink key in the config file. If the key isn’t present, the default is pretty.\n\nThe available styles are:\n\n\n  pretty: /[locale]/:categories/:year/:month/:day/:slug/\n  pretty_ext: /[locale]/:categories/:year/:month/:day/:slug.*\n  simple: /[locale]/:categories/:slug/\n  simple_ext: [locale]/:categories/:slug.*\n\n\n(Including .* at the end means it will output the resource with its own slug and extension. Alternatively, / at the end will put the resource in a folder of that slug with index.html inside.)\n\nTo set a permalink style or template for a custom collection, add it to your collection metadata. For example:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    articles do\n      permalink \"pretty\"\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  articles:\n    permalink: pretty\n    \n\n  \n\n\nwould make your articles collection behave the same as posts. Or you can create your own template:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    articles do\n      permalink \"/lots-of/:collection/:year/:title/\"\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  articles:\n    permalink: /lots-of/:collection/:year/:title/\n    \n\n  \n\n\nThis would result in URLs such as /lots-of/articles/2021/super-neato/.\n\nPlaceholders\n\nAll of the segments you see above starting with a colon, such as :year or :slug, are called placeholders. Bridgetown ships with a number of placeholders, but you can also create your own! See the placeholders plugin page for details.\n\nHere’s the full list of built-in placeholders available:\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      :year\n      Four-digit year based on the resource's date.\n    \n    \n      :short_year\n      Two-digit year based on the resource's date within its century (00..99).\n    \n    \n      :month\n      Month based on the resource's date (01..12).\n    \n    \n      :i_month\n      Month based on the resource's date without leading zeros (1..12).\n    \n    \n      :day\n      Day of the month based on the resource's date (01..31).\n    \n    \n      :i_day\n      Day of the month based on the resource's date without leading zeros (1..31).\n    \n    \n      :categories\n      The specified categories for the resource. If a resource has multiple\ncategories, Bridgetown will create a hierarchy (e.g. /category1/category2).\nBridgetown automatically parses out double slashes in the URLs,\nso if no categories are present, it will ignore this.\n\n    \n    \n      :locale, :lang\n      Adds the locale key of the current rendering context, if its not the default site locale.\n    \n    \n      :title\n      Title from the resource's front matter (aka title: My Resource Title), slugified (aka any character except numbers and letters is replaced as hyphen).\n    \n    \n      :slug\n      Extracted from the resources’s filename. May be overridden via the resources’s slug front matter.\n    \n    \n      :name\n      Extracted from the resources’s filename and cannot be overridden.\n    \n    \n      :path\n      Constructs URL segments out of the relative path of the resource within its collection folder. Used by the pages collection as well as custom collections if no specific permalink config is provided.\n    \n    \n      :collection\n      Outputs the label of the resource's custom collection (will be blank for the built-in pages and posts collections)."
        },
        {
          "id": "docs-installation-fedora",
          "title": "Bridgetown on Fedora",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation/fedora",
          "content": "Install Ruby\n\nUsing Rbenv\n\nUpdate your package list:\n\nThen install dependencies:\n\nsudo dnf install git-core zlib zlib-devel gcc-c++ patch readline readline-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison curl sqlite-devel\n\nInstall rbenv\n\ngit clone https://github.com/rbenv/rbenv.git ~/.rbenv\necho 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' &gt;&gt; ~/.bashrc\necho 'eval \"$(rbenv init -)\"' &gt;&gt; ~/.bashrc\nsource ~/.bashrc\n\n\nInstall ruby-build to provide rbenv install\ngit clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build\necho 'export PATH=\"$HOME/.rbenv/plugins/ruby-build/bin:$PATH\"' &gt;&gt; ~/.bashrc\nsource ~/.bashrc\n\nThen install Ruby and check the version\nrbenv install 4.0.3\nrbenv global 4.0.3\n\nruby -v\n\ngem install bundler -N\n\n\nCheck the rbenv command reference for more information here\n\nUsing Fedora Repositories\n\nFedora typically uses a recent version of Ruby that is maintained by the \nFedora Ruby special interest group.\n\nFirst, update your package list:\n\nsudo dnf update\n\n\nThen install Ruby as indicated here using\n\nsudo dnf install ruby\n\n\nVerify that ruby is installed\n\nruby -v\n&gt; ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-linux]\n\nThen install bundler as indicated here\n\ngem install bundler\n\n\nAnd that&#8217;s it!\n\nInstall Node\n\nNode is a JavaScript runtime that can execute on a server or development machine. NPM\nis a package manager for JavaScript packages. You&#8217;ll need Node in order to install\nand use esbuild, the frontend asset compiler that runs alongside Bridgetown.\n\nThe easiest way to install Node is via the package manager dnf.\n\nsudo dnf update\nsudo dnf install nodejs nodejs-devel\n\n\nThen verify your installed versions:\n\nnode -v\n&gt; v20.x\n\n\nInstall Bridgetown\n\nNow all that is left is to install Bridgetown!\n\ngem install bridgetown -N\n\n\nCreate a new Bridgetown site at ./mysite, as well as run bundle install and\nnpm install automatically:\n\nbridgetown new mysite\n\ncd mysite\n\n\nNow you should be able to build the site and run a live-reload server:\n\nbin/bridgetown start\n\n\nTry opening the site up in http://localhost:4000. See something? Awesome, you&#8217;re ready to roll! If not, try revisiting your installation and setup steps, and if all else fails, reach out to the Bridgetown community for support."
        },
        {
          "id": "docs-installation-macos",
          "title": "Bridgetown on macOS",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation/macos",
          "content": "Install Ruby\n\nWith rbenv\n\nPeople often use rbenv to manage multiple\nRuby versions, which comes in handy when you need to run a specific Ruby version on a project.\n\n# Install Homebrew\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n\n# Install rbenv and ruby-build\nbrew install rbenv\n\n# Set up rbenv integration with your shell\nrbenv init\n\n\nRestart your terminal for changes to take effect.\n\nNow you can install a new Ruby version. At the time of this writing, Ruby 4.0.3 is the latest stable version. (Note: the installation may take a few minutes to complete.)\n\nrbenv install 4.0.3\nrbenv global 4.0.3\n\nruby -v\n\n\n(If for some reason bundler isn&#8217;t installed automatically, run gem install bundler -N)\n\nAnd that&#8217;s it! Check out rbenv command references to learn how to use different versions of Ruby in your projects.\n\nNow jump down to the Install Node section.\n\nWith Homebrew\n\nYou may install Ruby directly through Homebrew.\n\n# Install Homebrew\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n\nbrew install ruby\n\n\nAdd the brew ruby path to your shell config:\n\necho 'export PATH=\"/usr/local/opt/ruby/bin:$PATH\"' &gt;&gt; ~/.zprofile\n\n\nThen relaunch your terminal and check your updated Ruby setup:\n\nwhich ruby\n# /usr/local/opt/ruby/bin/ruby\n\nruby -v\n\n\nYay, we are now running current stable Ruby!\n\nTo set up Bundler for managing Rubygem dependencies as well as Ruby executable paths, run:\n\ngem install --user-install bundler\n\n\nThen append your path file with the following, replacing the X.X with the first two digits of your Ruby version.\n\necho 'export PATH=\"$HOME/.gem/ruby/X.X.0/bin:$PATH\"' &gt;&gt; ~/.zprofile\n\n\nThen relaunch your terminal and check that your gem paths point to your home directory by running:\n\ngem env\n\n\nAnd check that SHELL PATH: includes to a path to ~/.gem/ruby/X.X.0/bin\n\n\n  \n  Every time you update Ruby to a version with a different first two digits, you will need to update your path to match.\n\nYou will also need to add --user-install to any gem install statement you run.\n\n\nInstall Node\n\nNode is a JavaScript runtime that can execute on a server or development machine. NPM\nis a package manager for JavaScript packages. You&#8217;ll need Node in order to install\nand use esbuild, the frontend asset compiler that runs alongside Bridgetown.\n\nThe easiest way to install Node is via Homebrew (which should already be installed after following the instructions above).\n\nbrew update\nbrew install node\n\n\nThen verify your installed version:\n\nnode -v\n\n\nInstall Bridgetown\n\nNow all that is left is to install Bridgetown!\n\ngem install bridgetown -N\n\n\nCreate a new Bridgetown site at ./mysite, as well as run bundle install and\nnpm install automatically:\n\nbridgetown new mysite\n\ncd mysite\n\n\nNow you should be able to build the site and run a live-reload server:\n\nbin/bridgetown start\n\n\nTry opening the site up in http://localhost:4000. See something? Awesome, you&#8217;re ready to roll! If not, try revisiting your installation and setup steps, and if all else fails, reach out to the Bridgetown community for support."
        },
        {
          "id": "docs-installation-ubuntu",
          "title": "Bridgetown on Ubuntu",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation/ubuntu",
          "content": "Install Ruby via rbenv\n\nThe version of Ruby available via Ubuntu’s package manager is often out of date, so the best option is to install Ruby via rbenv. People often use rbenv anyway to manage multiple Ruby versions, which comes in handy when you need to run a specific Ruby version on a project.\n\nFirst, update your package list:\n\nsudo apt update\n\n\nNext, install the dependencies required to install Ruby:\n\nsudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev\n\n\n(If on an older Ubuntu version, libgdbm6 won’t be available. Try installing libgdbm5 instead.)\n\nOnce the dependencies download, you can install rbenv itself. Clone the rbenv repository from GitHub into the directory ~/.rbenv:\n\ngit clone https://github.com/rbenv/rbenv.git ~/.rbenv\n\n\nNext, add ~/.rbenv/bin to your $PATH so that you can use the rbenv command line utility. Do this by altering your ~/.bashrc file so that it affects future login sessions:\n\necho 'export PATH=\"$HOME/.rbenv/bin:$PATH\"' &gt;&gt; ~/.bashrc\n\n\nThen add the command eval \"$(rbenv init -)\" to your ~/.bashrc file so rbenv loads automatically:\n\necho 'eval \"$(rbenv init -)\"' &gt;&gt; ~/.bashrc\n\n\nNext, apply the changes you made to your ~/.bashrc file to your current shell session:\n\nsource ~/.bashrc\n\n\nVerify that rbenv is set up properly by using the type command, which will display more information about the rbenv command:\n\ntype rbenv\n\n\nYour terminal window will display the following:\n\nrbenv is a function\n…\n\n\nNext, install the ruby-build plugin. This plugin adds the rbenv install command which simplifies the installation process for new versions of Ruby:\n\ngit clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build\n\n\nNow you can install a new Ruby version. At the time of this writing, Ruby 4.0.3 is the latest stable version. (Note: the installation may take a few minutes to complete.)\n\nrbenv install 4.0.3\nrbenv global 4.0.3\n\nruby -v\n\n\n(If for some reason bundler isn’t installed automatically, run gem install bundler -N)\n\nAnd that’s it! Check out rbenv command references to learn how to use different versions of Ruby in your projects.\n\nInstall Node\n\nNode is a JavaScript runtime that can execute on a server or development machine. NPM\nis a package manager for JavaScript packages. You’ll need Node in order to install\nand use esbuild, the frontend asset compiler that runs alongside Bridgetown.\n\ncurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\nsudo apt-get update\nsudo apt-get install -y nodejs\n\n\nInstall Bridgetown\n\nNow all that is left is to install Bridgetown!\n\ngem install bridgetown -N\n\n\nCreate a new Bridgetown site at ./mysite, as well as run bundle install and\nnpm install automatically:\n\nbridgetown new mysite\n\ncd mysite\n\n\nNow you should be able to build the site and run a live-reload server:\n\nbin/bridgetown start\n\n\nTry opening the site up in http://localhost:4000. See something? Awesome, you’re ready to roll! If not, try revisiting your installation and setup steps, and if all else fails, reach out to the Bridgetown community for support."
        },
        {
          "id": "docs-installation-upgrade",
          "title": "Upgrading from a Previous Version",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation/upgrade",
          "content": "Ready to try bringing your project up to the latest version? Here are some notes to help you get going. BTW, if you have a really old site, you may want to try incrementally upgrading versions rather than, say, go from 1.0 to 2.0 in one session.\n\nThere’s an upgrade-help channel in our Discord chat. It can be hard to include code examples there, so access to the problematic repo in question is almost always a given in order to troubleshoot. If your code needs to remain private, please create a failing example we can access.\n\nTable of Contents\n\n  Upgrading to Bridgetown 2.2    \n      Falcon Server\n      Native Node Glob\n      Opt-in to Wikilinks\n    \n  \n  Upgrading to Bridgetown 2.1    \n      Samovar, Freyia, and Custom Commands\n      Removal of Active Support\n    \n  \n  Upgrading to Bridgetown 2.0    \n      Switching from Yarn to NPM 📦\n      Specifying Liquid (if necessary) 💧\n      Fixing webpack_path bug 🪲\n      Crashing Related to Roda 💥\n      Supporting Active Support Support 😏\n      Caveats with Fast Refresh in Development ⏩\n      Quick Search and Other Plugins 🔍\n    \n  \n  Upgrading to Bridgetown 1.2\n  Upgrading to Bridgetown 1.1\n\n\nUpgrading to Bridgetown 2.2\n\nBridgetown 2.2 has bumped up the minimum Ruby requirement to 3.3.\n\nThere are no breaking changes in this release, but there a couple of improvements to the defaults you may want to make:\n\nFalcon Server\n\nBridgetown has moved to using Falcon as its default Ruby application server. Puma is still completely supported, but switching to Falcon can provide extra benefits—especially if you run Bridgetown server routes in production.\n\nTo use Falcon in your existing Bridgetown project, remove gem \"puma\" from your Gemfile and add gem \"falcon\". After a bundle install, Bridgetown will detect you have Falcon installed and use that instead of Puma.\n\nNote that due to underlying server layer refactoring, you may occasionally see a Puma “thread error” notice when pressing Ctrl+C to terminate the dev server. It’s harmless, but any unwanted noise in your terminal isn’t OK with us, so we’ll track that down and include a patch in the next point release.\n\nNative Node Glob\n\nYou’ll want to run bin/bridgetown esbuild update to get the latest base config for the esbuild frontend. It removes a dependency on the glob NPM package and instead relies on Node’s native glob functionality. More security and less hassle? Win/win!\n\nOpt-in to Wikilinks\n\nIf you’d like to take advantage of Bridgetown 2.2’s new wikilinks feature, add init :wikilinks to your config/initializers.rb file.\n\nUpgrading to Bridgetown 2.1\n\nMost projects should load fine in 2.1 as-is, but there are a few caveats to be aware of.\n\nFirst, the minimum supported language versions are Ruby 3.2 and Node 22. Usually you can edit your .ruby-version file and tools like rbenv will pick that up. Some deployment options require updating ENV vars. For Node, we recommend using a version manager like nvm where you can define Node versions in an .nvmrc file. Sometimes it’s also simply a matter of updating an ENV var at your hosting provider.\n\nWe’ve also made two substantial changes in the internals of Bridgetown, which could potentially affect certain third-party plugins. These represent the tail-end of our ongoing pledge to ensure Bridgetown is free of dependencies directly managed by 37signals, and now that we’ve completed that effort, we don’t anticipate any further disruptions on that front. Three cheers for stability!\n\nSamovar, Freyia, and Custom Commands\n\nIn Bridgetown 2.1, we’ve migrated away from using Thor for our command-line interface (CLI) and are now using Samovar, created by Samuel Williams. It is also much easier for you to extend Bridgetown’s CLI with your own commands in any project by creating a config/custom_commands.rb file to add your own Bridgetown::Command subclasses. We believe in most cases this is a more powerful &amp; flexible solution than authoring new Rake tasks. Docs here.\n\nNote that for the 2.1 release cycle, we have provided a Thor “shim” so existing sites and plugins which provide Thor commands should continue to work as before. In a future release, we will be removing the shim so please update your commands accordingly. In the meantime, if you run into any compatibility issues with the shim please report them and let us know!\n\nAnother of our prior uses of Thor was for the Automations functionality (which also powers our Bundled Configurations). Because this functionality is so important, and also near-impossible to replicate verbatim using an alternative library, we have extracted the “actions” &amp; “shell” portions of Thor as a hard fork out to a new gem called Freyia. We will be actively developing Freyia as its own independent project going forward, refactoring and adding new features as needed.\n\nRemoval of Active Support\n\nIn Bridgetown 2.1, we have finalized the removal of the Active Support gem. If you have written your own code which assumes the availability of Active Support, you may need to bundle add activesupport and require pieces of it yourself. For example, if you want to use .blank?, .present?, etc. and don’t wish to refactor, you can add the gem and include the following in your config/initializers.rb file:\n\nrequire \"active_support/core_ext/object/blank\"\n\n\nDocumentation on Active Support is available here.\n\nUpgrading to Bridgetown 2.0\n\nThe first thing to know is that there are new minimum versions of both Ruby and Node.js for the v2 release cycle. In general, we try to support the previous two significant releases of these runtimes in addition to the current ones with each major version increase. So you will need to use a minimum of:\n\n\n  Ruby 3.1.4 (⚠️ there’s a bug in earlier versions of Ruby 3.1 which will prevent Bridgetown to run)\n  Node 20.6 (⚠️ earlier versions of Node aren’t compatible with esbuild’s ESM-based config)\n\n\nIf you use versioning dotfiles (for example .ruby-version and .nvmrc), you’ll want to update those in your projects. We do recommend switching to the latest versions (Ruby 3.4 and at least Node 22 LTS as of the time of this writing) if possible.\n\nTo upgrade to Bridgetown 2.0, edit your Gemfile to update the version numbers in the argument for the bridgetown and bridgetown-routes (if applicable) gem to 2.0.0 and then run bundle update bridgetown.\n\nWe also recommend you run bin/bridgetown esbuild update so you get the latest default esbuild configuration Bridgetown provides, and you may need to update your esbuild version in package.json as well.\n\n\n  \n  Only update your esbuild configuration if you&#8217;re also willing to switch to ESModules (rather than CommonJS). This means your package.json file will include \"type\": \"module\" and Node JS code wil use import and export statements rather than require and module.exports going forward. Here&#8217;s an explainer about the JavaScript language switch to ESM.\n\n\nSwitching from Yarn to NPM 📦\n\nBridgetown uses NPM now by default, rather than Yarn, for frontend package managing. You may continue to use Yarn on your existing projects, but if you’d like to switch to NPM, you can delete your yarn.lock file, run npm install (shorthand: npm i), and check in package-lock.json instead. You can also use pnpm if you prefer. Bridgetown is now compatible with all three package managers.\n\nYou’ll also need to update the :frontend tasks in your project’s Rakefile to use your preferred package manager.\n\nSpecifying Liquid (if necessary) 💧\n\nThe default template engine for new Bridgetown sites is ERB, with Liquid being optional. If you’re upgrading a site that expects Liquid to be the default template engine, you will need to add  template_engine :liquid to your config/initializers.rb file (or template_engine: liquid to bridgetown.config.yml). If you don’t even have a config/initializers.rb file in your project yet, see the below section under Upgrading to Bridgetown 1.2.\n\nFixing webpack_path bug 🪲\n\nBridgetown unfortunately used to ship with templates which referenced webpack_path in Liquid or Ruby-based templates, even when using esbuild. That helper is no longer available in Bridgetown 2.0, as we’ve removed support for Webpack entirely.\n\nYou will need to do a search &amp; replace for all uses of webpack_path and change them to asset_path. This is a one-time fix, and then you’ll be good to go for the future or even if you still need to run code on an earlier version of Bridgetown.\n\nCrashing Related to Roda 💥\n\nIf you encounter a weird crash which contains uninitialized constant Bridgetown::Rack::Roda in the error log, you will need to update the syntax of your server/roda_app.rb file so that it’s a direct subclass of Roda and configures the bridgetown_server plugin. Here’s a basic version of that file:\n\nclass RodaApp &lt; Roda\n  plugin :bridgetown_server\n\n  # Some Roda configuration is handled in the `config/initializers.rb` file.\n  # But you can also add additional Roda configuration here if needed.\n\n  route do |r|\n    # Load Roda routes in server/routes (and src/_routes via `bridgetown-routes`)\n    r.bridgetown\n  end\nend\n\n\nAdditionally, you may also hit the following error:\n\nException raised: Errno::ENOENT\nNo such file or directory @ rb_sysopen - /tmp/pids/aux.pid\n\n\nIn which case, refer to the above fix for configuring your Roda server.\n\nSupporting Active Support Support 😏\n\nBridgetown v2 has removed a number of dependencies in the codebase on the Active Support gem (provided by the Rails framework). If that ends up causing problems with your codebase, you may need to require Active Support manually (and even Action View) in your config/initializers.rb file. Here’s a thread on GitHub referencing this situation.\n\nCaveats with Fast Refresh in Development ⏩\n\nBridgetown v2 comes with a “fast refresh” feature by default. This rebuilds only files needed to display updated content in source files, rather than the entire website from scratch. If you need to disable fast refresh because you’re seeing broken pages/links, set fast_refresh false in config/initializers.rb.\n\nQuick Search and Other Plugins 🔍\n\nYou will need to update to the latest v3 of the Quick Search plugin if you use that on your site. You may also want to double-check other Bridgetown plugins you use and make sure you’re on the latest version.\n\n\n\nUpgrading to Bridgetown 1.2\n\nBridgetown 1.2 brings with it a whole new initialization system along with a Ruby-based configuration format. Your bridgetown.config.yml file will continue to work, but over time you will likely want to migrate a good portion of your configuration over to the new format (and maybe even delete the YAML file).\n\nTo upgrade a 1.0 or 1.1 site to 1.2, edit your Gemfile update the version numbers in the argument for the bridgetown and bridgetown-routes (if applicable) gem and then run bundle.\n\nWe also recommend you run bin/bridgetown esbuild update so you get the latest default esbuild configuration Bridgetown provides.\n\nWhen you upgrade to v1.2, your site will run in a legacy mode that automatically requires all gems in your Gemfile within the bridgetown_plugins group as before. This legacy mode is only triggered by the absence of the new config/initializers.rb file. To opt-into the new format, create a config/initializers.rb file like so:\n\nBridgetown.configure do |config|\n  # add configuration here\nend\n\n\nThen you won’t need to use the bridgetown_plugins Gemfile group any longer.\n\n\n  \n  Do not attempt to upgrade other Bridgetown plugins along with upgrading to v1.2 unless you intend to adopt the new configuration format. The latest version of many Bridgetown plugins expect the initializers file to be in use.\n\nAlso be advised: if you are using the dynamic routes plugin, you must upgrade to the new configuration format. Read more below.\n\n\nOnce you’re using the new configuration format, if you need to use a Bridgetown plugin that’s not yet updated to work with v1.2, you can manually add a require statement to your configuration:\n\nBridgetown.configure do |config|\n  require \"my-older-plugin\"\n  require \"some-other-plugin\"\nend\n\n\nOtherwise, you’ll be able to add init statements to load in plugins. For example: init :\"bridgetown-lit-renderer\".\n\nIf you’ve been using the Bridgetown SSR and Routes plugins in your Roda server, you can remove the plugin statements in your server/roda_app.rb and instead use the new initializers:\n\nBridgetown.configure do |config|\n  init :ssr\n  init :\"bridgetown-routes\"\nend\n\n\nOther Roda server configuration can be placed within the file as well:\n\nonly :server do\n  roda do |app|\n    app.plugin :default_headers,\n      'Content-Type'=&gt;'text/html',\n      'Strict-Transport-Security'=&gt;'max-age=16070400;',\n      'X-Content-Type-Options'=&gt;'nosniff',\n      'X-Frame-Options'=&gt;'deny',\n      'X-XSS-Protection'=&gt;'1; mode=block'\n  end\nend\n\n\nIf you’ve installed the dotenv gem previously to manage environment variables, Bridgetown now has builtin support for the gem. You’re free to remove past code which loaded in dotenv and use the new initializer:\n\ninit :dotenv\n\n\nRead the Initializers documentation for further details.\n\nFor plugin authors, the scoping options for helper and filter in the Builder Plugin DSL have been deprecated. You’re encouraged to write simpler helper or filter code that calls the helpers or the filters variables directly to obtain access to the view-specific context. See the Helpers and Filters plugin documentation for more details.\n\nThe Builder DSL also offers new define_resource_method (docs here) and permalink_placeholder (docs here) methods which you can use in lieu of older solutions.\n\nUpgrading to Bridgetown 1.1\n\nTo upgrade your existing 0.2x  Bridgetown site to 1.1, you’ll need to specify the new version in your Gemfile:\n\ngem \"bridgetown\", \"1.1.0\"\n\n\nYou’ll also need to add Puma to your Gemfile:\n\ngem \"puma\", \"~&gt; 5.6\"\n\n\nThen run bundle update. (You’ll also ensure you’re specifying the latest version of any extra plugins you may have added, such as the feed and seo plugins.)\n\nNext you should run bundle binstubs bridgetown-core so you have access to bin/bridgetown, as this is now the canonical way of accessing the Bridgetown CLI within your project.\n\nYou will need to add a few additional files to your project, so we suggest using bridgetown new to create a separate project, then copy these files over:\n\n\n  config.ru\n  Rakefile\n  config/puma.rb\n  server/*\n\n\nAlso be sure to run bin/bridgetown webpack update so you get the latest default Webpack configuration Bridgetown provides.\n\nFinally, you can remove start.js and sync.js and well as any scripts in package.json besides webpack-build and webpack-dev (and you can also remove the browser-sync and concurrently dev dependencies in package.json).\n\nGoing forward, if you need to customize any aspect of Bridgetown’s build scripts or add your own, you can alter your Rakefile and utilize Bridgetown’s automatic Rake task support.\n\n\n  \n  Your plugins folder will now be loaded via Zeitwerk by default. This means you&#8217;ll need to namespace your Ruby files using certain conventions or reconfigure the loader settings. Read the documentation here.\n\n\nThe other major change you’ll need to work on in your project is switching your plugins/templates to use resources. There’s a fair degree of documentation regarding resources here. In addition, if you used the Document Builder API in the past, you’ll need to upgrade to the Resource Builder API.\n\n\n  \n  Get That Live Reloading Going Again\n\nThe live reloading mechanism in v1.0 is no longer injected automatically into your HTML layout, so you&#8217;ll need to add {% live_reload_dev_js %} (Liquid) or &lt;%= live_reload_dev_js %&gt; (ERB) to your HTML head in order to get live reload working. Please make sure you&#8217;ve added BRIDGETOWN_ENV=production as an environment variable to your production deployment configuration so live reload requests won&#8217;t be triggered on your public website."
        },
        {
          "id": "docs-installation-windows",
          "title": "Bridgetown on Windows",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation/windows",
          "content": "The easiest way to use Bridgetown on Windows is to install the Windows Subsystem for Linux. This provides a Linux development environment in which you can install Bash, Ruby, and other tools necessary to run Bridgetown in an optimized fashion.\n\nInstalling the Windows Subsystem for Linux\n\nYou must be running Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11.\n\nOpen PowerShell and run:\n\nwsl --install --distribution Ubuntu-24.04\n\n\nYou might need to restart your computer during this process. Once the setup finishes, you’ll be asked to create a username and password for your Ubuntu installation. After that, you can launch Ubuntu directly from the Start menu and log in with your new credentials.\n\n\n  \n  If the Ubuntu terminal doesn&#8217;t open, try running the installation command again and make sure the WSL feature is enabled in your Control Panel.\n\n\nInstall dependencies on Ubuntu\n\nLaunch WSL from the Start menu and update your package list:\n\nsudo apt update\n\n\nNext, install the required dependencies:\n\nsudo apt install build-essential rustc libssl-dev libyaml-dev zlib1g-dev libgmp-dev\n\n\nInstalling the Mise version manager\n\nWe’ll use Mise, a version manager, to install Ruby and Node. Mise makes it easy to update development tools or switch between different versions whenever you need.\n\ncurl https://mise.run | sh\necho 'eval \"$(~/.local/bin/mise activate bash)\"' &gt;&gt; ~/.bashrc\nsource ~/.bashrc\n\n\nInstall Ruby\n\nThe following command installs the latest version of ruby-3.x (if any 3.x version is not already installed) and makes it the global default version:\n\nmise use --global ruby@3\n\n\nOnce Ruby is installed, you can verify it works by running:\n\nruby --version\n\n\nInstall Node\n\nThe following command installs the latest version of node-24.x and makes it the global default:\n\nmise use --global node@24\n\n\nOnce Node is installed, you can also verify it works by running:\n\nnode --version\n\n\nInstall Bridgetown\n\nNow all that is left is to install Bridgetown!\n\ngem install bridgetown -N\n\n\nCreate a new Bridgetown site at ./mysite, as well as run bundle install and\nnpm install automatically:\n\nbridgetown new mysite\n\ncd mysite\n\n\nNow you should be able to build the site and run a live-reload server:\n\nbin/bridgetown start\n\n\nTry opening the site up in http://localhost:4000. See something? Awesome, you’re ready to roll! If not, try revisiting your installation and setup steps, and if all else fails, reach out to the Bridgetown community for support.\n\n\n  \n  Important for WSL Users\n\nFor the best experience, always create your projects within the native Linux file system (e.g., ~/my-projects) rather than on the Windows mount (e.g., /mnt/c/).\n\nDeveloping inside /mnt/c/ causes significant performance lag, permission errors, and breaks live-reloading. This issue impacts WSL Version 2 and is not exclusive to Bridgetown. You can learn more about it here.\n\nInstalling VS Code on Windows and the WSL extension enables separate environments: the editor interface runs on Windows, while code execution and plugins run on WSL. Learn more about this in the Developing in WSL topic."
        },
        {
          "id": "docs-liquid-filters",
          "title": "Liquid Filters",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "template-engines",
          "tags": "",
          "url": "/docs/liquid/filters",
          "content": "All of the standard Liquid filters are supported (see below).\n\nTo make common tasks easier, Bridgetown even adds a few handy filters of its own,\nall of which you can find on this page. You can also create your own filters\nusing plugins.\n\n\n  \n    \n      Description\n      Filter and Output\n    \n  \n  \n      \n        \n          Relative URL\n          \n            Prepend the base_path value to the input. Useful if your site is hosted at a subpath rather than the root of the domain.\n          \n        \n        \n            {{ &quot;/assets/image.jpg&quot; | relative_url }}\n            /my-basepath/assets/image.jpg\n        \n      \n      \n        \n          Absolute URL\n          \n            Prepend the url and base_path value to the input.\n          \n        \n        \n            {{ &quot;/assets/image.jpg&quot; | absolute_url }}\n            http://example.com/my-basepath/assets/image.jpg\n        \n      \n      \n        \n          Date to XML Schema\n          \n            Convert a Date into XML Schema (ISO 8601) format.\n          \n        \n        \n            {{ site.time | date_to_xmlschema }}\n            2008-11-07T13:07:54-08:00\n        \n      \n      \n        \n          Date to RFC-822 Format\n          \n            Convert a Date into the RFC-822 format used for RSS feeds.\n          \n        \n        \n            {{ site.time | date_to_rfc822 }}\n            Mon, 07 Nov 2008 13:07:54 -0800\n        \n      \n      \n        \n          Date to String\n          \n            Convert a date to short format.\n          \n        \n        \n            {{ site.time | date_to_string }}\n            07 Nov 2008\n        \n      \n      \n        \n          Date to String in ordinal US style\n          \n            Format a date to ordinal, US, short format.\n          \n        \n        \n            {{ site.time | date_to_string: &quot;ordinal&quot;, &quot;US&quot; }}\n            Nov 7th, 2008\n        \n      \n      \n        \n          Date to Long String\n          \n            Format a date to long format.\n          \n        \n        \n            {{ site.time | date_to_long_string }}\n            07 November 2008\n        \n      \n      \n        \n          Date to Long String in ordinal UK style\n          \n            Format a date to ordinal, UK, long format.\n          \n        \n        \n            {{ site.time | date_to_long_string: &quot;ordinal&quot; }}\n            7th November 2008\n        \n      \n      \n        \n          Where\n          \n            Select all the objects in an array where the key has the given value.\n          \n        \n        \n            {{ site.members | where:&quot;graduation_year&quot;,&quot;2014&quot; }}\n            \n        \n      \n      \n        \n          Where Expression\n          \n            Select all the objects in an array where the expression is true. (Tip: You might want to try using the find tag instead.)\n          \n        \n        \n            {{ site.members | where_exp:&quot;item&quot;,\n&quot;item.graduation_year == 2014&quot; }}\n            \n            {{ site.members | where_exp:&quot;item&quot;,\n&quot;item.graduation_year &lt; 2014&quot; }}\n            \n            {{ site.members | where_exp:&quot;item&quot;,\n&quot;item.projects contains &#39;foo&#39;&quot; }}\n            \n        \n      \n      \n        \n          Group By\n          \n            Group an array's items by a given property.\n          \n        \n        \n            {{ site.members | group_by:&quot;graduation_year&quot; }}\n            [{&quot;name&quot;=&gt;&quot;2013&quot;, &quot;items&quot;=&gt;[...]},\n{&quot;name&quot;=&gt;&quot;2014&quot;, &quot;items&quot;=&gt;[...]}]\n        \n      \n      \n        \n          Group By Expression\n          \n            Group an array's items using a Liquid expression.\n          \n        \n        \n            {{ site.members | group_by_exp: &quot;item&quot;,\n&quot;item.graduation_year | truncate: 3, &#39;&#39;&quot; }}\n            [{&quot;name&quot;=&gt;&quot;201&quot;, &quot;items&quot;=&gt;[...]},\n{&quot;name&quot;=&gt;&quot;200&quot;, &quot;items&quot;=&gt;[...]}]\n        \n      \n      \n        \n          XML Escape\n          \n            Escape some text for use in XML.\n          \n        \n        \n            {{ page.content | xml_escape }}\n            \n        \n      \n      \n        \n          CGI Escape\n          \n            CGI escape a string for use in a URL. Replaces any special characters with appropriate %XX replacements. CGI escape normally replaces a space with a plus + sign.\n          \n        \n        \n            {{ &quot;foo, bar; baz?&quot; | cgi_escape }}\n            foo%2C+bar%3B+baz%3F\n        \n      \n      \n        \n          URI Escape\n          \n            Percent encodes any special characters in a URI. URI escape normally replaces a space with %20. Reserved characters will not be escaped.\n          \n        \n        \n            {{ &quot;http://foo.com/?q=foo, \\bar?&quot; | uri_escape }}\n            http://foo.com/?q=foo,%20%5Cbar?\n        \n      \n      \n        \n          Obfuscate Link\n          \n            Obfuscate emails, telephone numbers etc. The link text is replaced by a ciphered string (using the ROT47 algorithm, so numbers are included). On page load, this cipher is reversed, so the string is readable again. Takes an optional argument to specify the URI scheme prefix (default \"mailto\")\n          \n        \n        \n            {{ &quot;+1 234 567&quot; | obfuscate_link:&quot;tel&quot; }}\n            \n        \n      \n      \n        \n          Number of Words\n          \n            Count the number of words in some text.\n          \n        \n        \n            {{ page.content | number_of_words }}\n            1337\n        \n      \n      \n        \n          Reading Time\n          \n            Returns the average number of minutes to read the supplied content. Based on 250 WPM but that can be changed using the reading_time_wpm configuration variable. You can also pass an argument to specify which decimal point to round to (defaults to 0 for no decimals).\n          \n        \n        \n            {{ page.content | reading_time }} minutes\n            4 minutes\n            {{ page.content | reading_time: 1 }} minutes\n            3.2 minutes\n        \n      \n      \n        \n          Array to Sentence\n          \n            Convert an array into a sentence. Useful for listing tags. Optional argument for connector.\n          \n        \n        \n            {{ page.tags | array_to_sentence_string }}\n            foo, bar, and baz\n            {{ page.tags | array_to_sentence_string: &quot;or&quot; }}\n            foo, bar, or baz\n        \n      \n      \n        \n          Markdownify\n          \n            Convert a Markdown-formatted string into HTML.\n          \n        \n        \n            {{ page.excerpt | markdownify }}\n            \n        \n      \n      \n        \n          Smartify\n          \n            Convert \"quotes\" into &ldquo;smart quotes.&rdquo;\n          \n        \n        \n            {{ page.title | smartify }}\n            \n        \n      \n      \n        \n          Slugify\n          \n            Convert a string into a lowercase URL \"slug\". See below for options.\n          \n        \n        \n            {{ &quot;The _config.yml file&quot; | slugify }}\n            the-config-yml-file\n            {{ &quot;The _config.yml file&quot; | slugify: &quot;pretty&quot; }}\n            the-_config.yml-file\n            {{ &quot;The _cönfig.yml file&quot; | slugify: &quot;ascii&quot; }}\n            the-c-nfig-yml-file\n            {{ &quot;The cönfig.yml file&quot; | slugify: &quot;latin&quot; }}\n            the-config-yml-file\n        \n      \n      \n        \n          Titleize\n          \n            Transform a lowercase string, slug, or identifier string into a capitalized title.\n          \n        \n        \n            {{ &quot;to-kill-a-mockingbird&quot; | titleize }}\n            To Kill A Mockingbird\n            {{ &quot;as_easy_as_123&quot; | titleize }}\n            As Easy As 123\n            {{ &quot;working hard or hardly working&quot; | titleize }}\n            Working Hard Or Hardly Working\n        \n      \n      \n        \n          Data To JSON\n          \n            Convert Hash or Array to JSON.\n          \n        \n        \n            {{ site.data.projects | jsonify }}\n            \n        \n      \n      \n        \n          Normalize Whitespace\n          \n            Replace any occurrence of whitespace with a single space.\n          \n        \n        \n            {{ &quot;a \\n b&quot; | normalize_whitespace }}\n            \n        \n      \n      \n        \n          Sort\n          \n            Sort an array. Optional arguments for hashes 1.&nbsp;property name 2.&nbsp;nils order (first or last).\n          \n        \n        \n            {{ page.tags | sort }}\n            \n            {{ site.collections.posts.resources | sort: &quot;author&quot; }}\n            \n            {{ site.collections.pages.resources | sort: &quot;title&quot;, &quot;last&quot; }}\n            \n        \n      \n      \n        \n          Sample\n          \n            Pick a random value from an array. Optionally, pick multiple values.\n          \n        \n        \n            {{ site.collections.pages.resources | sample }}\n            \n            {{ site.collections.pages.resources | sample: 2 }}\n            \n        \n      \n      \n        \n          To Integer\n          \n            Convert a string or boolean to integer.\n          \n        \n        \n            {{ some_var | to_integer }}\n            \n        \n      \n      \n        \n          Array Filters\n          \n            Push, pop, shift, and unshift elements from an Array. These are NON-DESTRUCTIVE, i.e. they do not mutate the array, but rather make a copy and mutate that.\n          \n        \n        \n            {{ page.tags | push: &quot;Spokane&quot; }}\n            [&quot;Seattle&quot;, &quot;Tacoma&quot;, &quot;Spokane&quot;]\n            {{ page.tags | pop }}\n            [&quot;Seattle&quot;]\n            {{ page.tags | shift }}\n            [&quot;Tacoma&quot;]\n            {{ page.tags | unshift: &quot;Olympia&quot; }}\n            [&quot;Olympia&quot;, &quot;Seattle&quot;, &quot;Tacoma&quot;]\n        \n      \n      \n        \n          Inspect\n          \n            Convert an object into its String representation for debugging.\n          \n        \n        \n            {{ some_var | inspect }}\n            \n        \n      \n  \n\n\nOptions for the slugify filter\n\nThe slugify filter accepts an option, each specifying what to filter.\nThe default is pretty (unless the slugify_mode setting is changed in the site config). They are as follows (with what they filter):\n\n\n  none: no characters\n  raw: spaces\n  default: spaces and non-alphanumeric characters\n  pretty: spaces and non-alphanumeric characters except for ._~!$&amp;'()+,;=@\n  ascii: spaces, non-alphanumeric, and non-ASCII characters\n  latin: like default, except Latin characters are first transliterated (e.g. àèïòü to aeiou).\n\n\nDetecting nil values with where filter\n\nYou can use the where filter to detect documents and pages with properties that are nil or \"\". For example,\n\n// Using `nil` to select posts that either do not have `my_prop`\n// defined or `my_prop` has been set to `nil` explicitly.\n{% assign filtered_posts = collections.posts.resources | where: 'my_prop', nil %}\n\n\n// Using Liquid's special literal `empty` or `blank` to select\n// posts that have `my_prop` set to an empty value.\n{% assign filtered_posts = collections.posts.resources | where: 'my_prop', empty %}\n\n\nBinary operators in where_exp filter\n\nYou can use Liquid binary operators or and and in the expression passed to the where_exp filter to employ multiple\nconditionals in the operation.\n\nFor example, to get a list of documents on English horror flicks, one could use the following snippet:\n\n{{ collections.movies.resources | where_exp: \"item\", \"item.genre == 'horror' and item.language == 'English'\" }}\n\n\nOr to get a list of comic-book based movies, one may use the following:\n\n{{ collections.movies.resources | where_exp: \"item\", \"item.sub_genre == 'MCU' or item.sub_genre == 'DCEU'\" }}\n\n\nStandard Liquid Filters\n\nFor your convenience, here is the list of all Liquid filters with links to examples in the official Liquid documentation.\n\n\n  abs\n  append\n  at_least\n  at_most\n  capitalize\n  ceil\n  compact\n  concat\n  date\n  default\n  divided_by\n  downcase\n  escape\n  escape_once\n  first\n  floor\n  join\n  last\n  lstrip\n  map\n  minus\n  modulo\n  newline_to_br\n  plus\n  prepend\n  remove\n  remove_first\n  replace\n  replace_first\n  reverse\n  round\n  rstrip\n  size\n  slice\n  sort\n  sort_natural\n  split\n  strip\n  strip_html\n  strip_newlines\n  times\n  truncate\n  truncatewords\n  uniq\n  upcase\n  url_decode\n  url_encode"
        },
        {
          "id": "docs-liquid-tags",
          "title": "Liquid Tags",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "template-engines",
          "tags": "",
          "url": "/docs/liquid/tags",
          "content": "All of the standard Liquid\ntags are supported.\nBridgetown has a few built in tags to help you build your site. You can also create\nyour own tags using plugins.\n\nComponent rendering\n\nYou can use the render and rendercontent tags to embed content and template partials into your main templates. Read the documentation here.\n\nFind tag\n\nYou can use the find tag to loop through a data object or collection and pull out one or more items to use in your Liquid template. Whereas before you could use the where_exp filter to accomplish a similar purpose, this tag is more succinct and has support for single item variables.\n\nThe syntax of the tag is as follows:\n\n# Single item:\n\n{% find [item] in [array/collection], [expressions] %}\n\n# Multiple items:\n\n{% find [items] where [array/collection], [expressions] %}\n\n\nFor example, to find a single entry in the albums collection and assign it to the variable album:\n\n{% find album in collections.albums.resources, band == resource.data.band, year &gt;= 1980, categories contains \"Rock\" %}\n\n\nOr to find multiple items and assign that array to the variable albums:\n\n{% find albums where collections.albums.resources, band == resource.data.band, year &gt;= 1980, categories contains \"Rock\" %}\n\n\nEach expression (separated by a comma) adds an “AND” clause to the conditional logic. If you need OR logic instead, you can still use the where_exp filter, or you can write additional find tags and concat the arrays together (you’ll probably also want to use the uniq filter to ensure you don’t end up with duplicates).\n\n{% find rock_albums where collections.albums.resources, band == resource.data.band, year &gt;= 1980, categories contains \"Rock\" %}\n{% find folk_albums where collections.albums.resources, band == resource.data.band, year &gt;= 1980, categories contains \"Folk\" %}\n\n{% assign albums = rock_albums | concat: folk_albums | uniq %}\n\n\nClass Map tag\n\nIf you’ve ever had to write a bunch of conditional code and variable assigns to toggle on/off CSS classes based on input variables, you know it can get pretty messy.\n\nBut not anymore! Introducing class_map:\n\n&lt;div class=\"{% class_map has-centered-text: resource.data.centered, is-small: small-var %}\"&gt;\n  …\n&lt;/div&gt;\n\n\nIn this example, the class_map tag will include has-text-centered only if resource.data.centered is truthy, and likewise is-small only if small-var is truthy. If you need to run a comparison with a specific value, you’ll still need to use assign but it’ll still be simpler than in the past:\n\n{% if product.data.feature_in == \"socks\" %}{% assign should_bold = true %}{% endif %}\n&lt;div class=\"{% class_map product: true, bold-text: should_bold, float-right: true %}\"&gt;\n  …\n&lt;/div&gt;\n\n\nCode snippet highlighting\n\nBridgetown has built in support for syntax highlighting of over 100 languages\nthanks to Rouge. To render a code block with syntax highlighting, surround your code as follows:\n\n{% highlight ruby %}\ndef foo\n  puts 'foo'\nend\n{% endhighlight %}\n\n\nThe argument to the highlight tag (ruby in the example above) is the\nlanguage identifier. To find the appropriate identifier to use for the language\nyou want to highlight, look for the “short name” on the Rouge\nwiki.\n\n\n  \n  Bridgetown processes all Liquid filters in code blocks\n\nIf you are using a language that contains curly braces, you will likely need to\nplace {&#37; raw &#37;} and {&#37; endraw &#37;} tags\naround your code. If needed, you can add render_with_liquid: false in your\nfront matter to disable Liquid entirely for a particular document.\n\n\n\n  \n  You can also use fenced code blocks in Markdown (starting and ending with three\nbackticks ```) instead of using the highlight tag. However, the\nhighlight tag includes additional features like line numbers (see below).\n\n\nLine numbers\n\nThere is a second argument to highlight called linenos that is optional.\nIncluding the linenos argument will force the highlighted code to include line\nnumbers. For instance, the following code block would include line numbers next\nto each line:\n\n{% highlight ruby linenos %}\ndef foo\n  puts 'foo'\nend\n{% endhighlight %}\n\n\nStylesheets for syntax highlighting\n\nIn order for the highlighting to show up, you’ll need to include a highlighting\nstylesheet. You can use CSS that’s compatible with Pygments—example gallery\nhere\nor from its repository.\n\nCopy the CSS file (native.css for example) into your styles directory and import\nthe syntax highlighter styles into your index.scss:\n\n@import \"native.css\";\n\n\nLinks\n\nLinking to pages\n\nTo link to a post, a page, collection item, or file, the link tag will generate the correct permalink URL for the path you specify. For example, if you use the link tag to link to mypage.html, even if you change your permalink style to include the file extension or omit it, the URL formed by the link tag will always be valid.\n\nYou must include the file’s original extension when using the link tag. Here are some examples:\n\n{% link _collection/name-of-document.md %}\n{% link _posts/2016-07-26-name-of-post.md %}\n{% link news/index.html %}\n{% link /assets/files/doc.pdf %}\n\n\nYou can also use the link tag to create a link in Markdown as follows:\n\n[Link to a document]({% link _collection/name-of-document.md %})\n[Link to a post]({% link _posts/2016-07-26-name-of-post.md %})\n[Link to a page]({% link news/index.html %})\n[Link to a file]({% link /assets/files/doc.pdf %})\n\n\nThe path to the post, page, or collection is defined as the path relative to the root directory (where your config file is) to the file, not the path from your existing page to the other page.\n\nFor example, suppose you’re creating a link in page_a.md (stored in pages/folder1/folder2) to page_b.md (stored in  pages/folder1). Your path in the link would not be ../page_b.html. Instead, it would be /pages/folder1/page_b.md.\n\nIf you’re unsure of the path, add {{ resource.relative_path }} to the page and it will display the path.\n\nOne major benefit of using the link or post_url tag is link validation. If the link doesn’t exist, Bridgetown won’t build your site. This is a good thing, as it will alert you to a broken link so you can fix it (rather than allowing you to build and deploy a site with broken links).\n\nNote you cannot add filters to link tags. For example, you cannot append a string using Liquid filters, such as {% link mypage.html | append: \"#section1\" %}. To link to sections on a page, you will need to use regular HTML or Markdown linking techniques.\n\nLinking to posts\n\nIf you want to include a link to a post on your site, the post_url tag will generate the correct permalink URL for the post you specify.\n\n{% post_url 2010-07-21-name-of-post %}\n\n\nIf you organize your posts in subdirectories, you need to include subdirectory path to the post:\n\n{% post_url /subdir/2010-07-21-name-of-post %}\n\n\nThere is no need to include the file extension when using the post_url tag.\n\nYou can also use this tag to create a link to a post in Markdown as follows:\n\n[Name of Link]({% post_url 2010-07-21-name-of-post %})"
        },
        {
          "id": "docs-migrating-features-since-jekyll",
          "title": "Bridgetown Features Post-Jekyll",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "migrating",
          "tags": "",
          "url": "/docs/migrating/features-since-jekyll",
          "content": "Here&#8217;s a rundown of some 40 features Bridgetown has implemented since the fork from Jekyll in early 2020:\n\n\n  All-new &#8220;resource&#8221; content engine built from the ground up to facilitate demanding content needs.\n  Pages, posts, and custom collection items all share a common interface and behave in a predicable manner.\n  Fully custom taxonomies and defined relations (belongs to, has many, etc.) between resources.\n  Content model objects with load/save abilities which underlie resources.\n  Fast refresh by default for the development server.\n  The resource extension API.\n  Ruby front matter in addition to YAML.\n  Inspectors for Nokogiri-based modification of HTML &amp; XML resources.\n  Configurable auto-sorted collections.\n  Robust I18n support for multilingual deployments.\n  An object-oriented componentized view layer.\n  Support for ERB &amp; other Ruby template engines.\n  Ruby-based automation scripts &amp; Rake tasks.\n  A console command for interacting and testing with your site via IRB.\n  Customizable console methods.\n  Fast, integrated frontend building via esbuild.\n  PostCSS support by default (Dart Sass support also available).\n  A Rack &amp; Puma-based web server to supersede WEBrick.\n  A next-gen plugin API via Builders.\n  Plugin source manifests &amp; frontend integration with NPM auto-install.\n  A clearer, modern  file &amp; folder structure.\n  A powerful external API DSL for generating new content.\n  Support for pagination and prototype (aka archive) pages available out of the box.\n  YAML file-based front matter defaults with folder cascades.\n  Bundled configurations for popular libraries &amp; tools such as Lit, Web Awesome, and Open Props.\n  Easy website testing setup w/Minitest or Cypress.\n  Auto-reloadable local plugins via Zeitwerk.\n  Samovar-based CLI tools with straightforward extensibility.\n  &lt;mark&gt; highlighter support in Markdown content via :: or ==.\n  SSR via an integration with Roda, a blazing-fast Ruby web toolkit.\n  File-based dynamic routes.\n  Environment-specific metadata.\n  Streamlined installation processes on modern Unix-style OSes with modern Ruby versions.\n  YARD API documentation\n  Modern Liquid (v5+) support.\n  SSG/SSR &amp; client-side hydration of Lit-based web components.\n  Ruby-based configuration file format\n  A large number of &#8220;breaking&#8221; fixes Jekyll had left unaddressed.\n  Active first-party support via community Discord + commercial support.\n\n\nReady to migrate to Bridgetown? Here&#8217;s an overview guide of the steps you&#8217;ll want to take."
        },
        {
          "id": "docs-migrating-jekyll",
          "title": "Migrating from Jekyll",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "migrating",
          "tags": "",
          "url": "/docs/migrating/jekyll",
          "content": "While Bridgetown still bears conceptual similarities to Jekyll at first glance, quite a lot has changed. (Read this rundown of 40 new and enhanced features!) The default folder structure of a Bridgetown website is a bit different as well. Our recommendation is that, rather than try to transform an existing Jekyll repo into a Bridgetown one, you create a new Bridgetown site from scratch and then copy various files and content over from your Jekyll project in a systematic fashion.\n\nHere are some steps you can take to do exactly that:\n\n\n  Create your project. After installing Bridgetown, run bridgetown new to create your blank repo. The frontend folder is for saving frontend assets (CSS, JavaScript, fonts, and possibly images), the src folder is for content and static files (which also may include images), and the plugins folder is for any custom Ruby plugins you want to add. If much of your content is primarily in Markdown or HTML file, you shouldn’t have much difficulty loading them in with Bridgetown. We also support data files (YAML, JSON, CSV) in a similar fashion. More docs here.\n  Set up your metadata. For metadata like your site title, email address, seo defaults, whatever, you’ll generally want to put those in src/_data/site_metadata.yml rather than in the main config file. The bonus is, you can change these values and live reload will allow you to preview your changes in real-time. More docs here.\n  Review your frontend bundling setup. With esbuild, you can import both Sass and vanilla CSS files which are then processed through PostCSS. Unlike with Jekyll, Bridgetown’s frontend pipeline via esbuild requires you to define all your imports from the entrypoints (index.js or index.(s)css). Typically there’s no automatic concatenation of files within the frontend folders. More docs here.\n  Need to create a theme? Bridgetown’s theming system is different from Jekyll. You can either pull theme files (content, stylesheets, etc.) directly into your project and use them that way, or you can create a new gem-based Bridgetown theme. More docs here\n  Copy over your content. You can copy your Liquid layouts over to src/_layouts , and other content can go into similar places within src. You’ll need to update your Liquid code to use resources—for example, instead of looping through site.posts you’ll loop through collections.posts.resources. And instead of page.url you’ll need to write page.relative_url. More docs here.\n  Update your includes. Jekyll includes will need to be converted to Liquid components inside of src/_components which are accessed via the render tag. More docs here.\n  Update your plugins. Plugins will need to be updated to use Bridgetown APIs. Some “legacy” APIs are still supported (like generators) and hooks work in a similar fashion. Be sure to use the right class names and module namespaces so that your code is compatible with the Zeitwerk autoloader. More docs here.\n  Find equivalent plugin gems. For some third-party gems you might be using in your Jekyll project, there could be Bridgetown equivalents (jekyll-feed -&gt; bridgetown-feed). View the plugins directory.\n  Still stuck? If you run into major roadblocks, we encourage you to take advantage of our community support channels to help get you unstuck. Note that it’s always much easier if you’re able to share a repo link so someone can try to reproduce your issue and offer useful advice.\n\n\nAdditional Points to Consider:\n\nLearn new commands. To build a Bridgetown site, run bin/bridgetown build (bin/bt b for short). To start a server, run bin/bridgetown start (bin/bt s for short). And for deployment builds, you’ll want to run bin/bridgetown deploy and make sure the BRIDGETOWN_ENV environment variable is set to production. And a very cool feature you’ll be introduced to with Bridgetown: you can run bin/bridgetown console (bin/bt c for short) to boot up an IRB-based REPL where you can inspect your collections and data, interact with plugins, and test out various bits of Ruby code right there in your terminal. More docs here.\n\nWhat about drafts? There’s no special “drafts” folder or draft mode for content in Bridgetown. You can set certain items to published: false, and in order to see those items locally in order to preview, pass the --unpublished or -U command line option.\n\nContent is less confusing. The behavior of pages vs. posts vs. custom collection items is much closer and more predictable due to the revamped “resource” content engine in Bridgetown. There’s literally a pages collection now (you can either store pages directly in the src folder or within the _pages subfolder), and you can configure custom collections to behave identically to the posts collection if you so with. Custom collection items can contribute categories and tags to the overall site (they’re not posts-only). Read more about collections.\n\nMind your permalinks. Permalinks are formatted a bit differently now. You’ll want to end your permalink templates with either a slash / (preferred) or a wildcard .* in order to ensure the appropriate extension is used. Read more about permalinks.\n\nYou’re no longer limited to Liquid. Liquid is a fine template language to get started with, but more advanced site builds will quickly run into its limitations. With Bridgetown, you have the option to use ERB, plain Ruby, or other template languages like Serbea or Haml. In addition, you can write Ruby view components to DRY up much of your template code and create more of a disciplined design system. Read more about templates and components to see what’s possible."
        },
        {
          "id": "docs-plugins-cache-api",
          "title": "Cache API",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/cache-api",
          "content": "Bridgetown includes a Caching API which is used both internally as well as exposed for plugins and components. It can be used to cache the output of deterministic functions to speed up site generation. This cache will be persistent across builds (saved to byte streams inside .bridgetown-cache), but\ncleared when Bridgetown detects any changes to config/initializers.rb and/or bridgetown.config.yml.\n\nThere’s also a per-build, temporary, in-memory cache hash you can use to save any expensive operations or objects within a single build process.\n\nTable of Contents\n\n  Usage    \n      Bridgetown::Cache.new(name) → new_cache\n      getset(key) {block}\n      clear\n    \n  \n  Additional Methods    \n      cache[key] → value\n      cache[key] = value\n      key?(key) → true or false\n      delete(key)\n    \n  \n  Temporary In-Memory Cache\n\n\nUsage\n\nBridgetown::Cache.new(name) → new_cache\n\nIf there has already been a Cache created with name, this will return a\nreference to that existing Cache. Otherwise, create a new Cache called name.\n\nIf this Cache will be used by a Gem-packaged plugin, name should either be the\nname of the Gem, or prefixed with the name of the Gem followed by :: (if a\nplugin expects to use multiple Caches). If this Cache will be used internally by\nBridgetown, name should be the name of the class that is using the Cache (ie:\n\"Bridgetown::Converters::Markdown\").\n\nCached objects are shared between all Caches created with the same name, but\nare not shared between Caches with different names. There can be an object\nstored with key 1 in Bridgetown::Cache.new(\"a\") and an object stored with key\n1 in Bridgetown::Cache.new(\"b\") and these will not point to the same cached\nobject. This way, you do not need to ensure that keys are globally unique.\n\ngetset(key) {block}\n\nThis is the most common way to utilize the Cache.\n\nblock is a bit of code that takes a lot of time to compute, but always\ngenerates the same output given a particular input (like converting Markdown to\nHTML). key is a String (or an object with to_s) that uniquely identifies\nthe input to the function.\n\nIf key already exists in the Cache, it will be returned and block will never\nbe executed. If key does not exist in the Cache, block will be executed and\nthe result will be added to the Cache and returned.\n\ndef cache\n  @@cache ||= Bridgetown::Cache.new(\"ConvertMarkdown\")\nend\n\ndef convert_markdown_to_html(markdown)\n  cache.getset(markdown) do\n    expensive_conversion_method(markdown)\n  end\nend\n\n\nIn the above example, expensive_conversion_method will only be called once for\nany given markdown input. If convert_markdown_to_html is called a second\ntime with the same input, the cached output will be returned.\n\nBecause posts will frequently remain unchanged from one build to the next, this\nis an effective way to avoid performing the same computations each time the site\nis built.\n\nclear\n\nThis will clear all cached objects from a particular Cache. The Cache will be\nempty, both in memory and on disk.\n\nAdditional Methods\n\nThe following methods should probably only be used in special circumstances:\n\ncache[key] → value\n\nFetches key from Cache and returns its value. Raises if key does not exist\nin Cache.\n\ncache[key] = value\n\nAdds value to Cache under key.\nReturns nothing.\n\nkey?(key) → true or false\n\nReturns true if key already exists in Cache. False otherwise.\n\ndelete(key)\n\nRemoves key from Cache.\nReturns nothing.\n\nTemporary In-Memory Cache\n\nThe site object (available from within most code paths, or you can use Bridgetown::Current.site) exposes a tmp_cache hash (of type HashWithDotAccess::Hash). You can use this to save and recall data:\n\ntemporary_value = site.tmp_cache[:temporary_value] ||= do_expensive_stuff\n\n\nThus the do_expensive_stuff operation will run only once for the lifetime of a single build, even if you need to instantiate the temporary_value variable a myriad of times."
        },
        {
          "id": "docs-plugins-commands",
          "title": "Commands",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/commands",
          "content": "Bridgetown sites and plugins can provide commands for the bridgetown executable. Commands are built using the Samovar CLI toolkit.\n\nTo provide a command from within your site repo, create a config/custom_commands.rb and define one or more Bridgetown::Command subclasses as described below.\n\nTo provide a command using a gem, add a folder within your gem&#8217;s lib folder with the path bridgetown/features and inside save a Ruby file with your exact gem name. Within that file, subclass Bridgetown::Command and notify Bridgetown how to include your command as described below.\n\nYou will also need to add a clause in your .gemspec to notify Bridgetown a command-line feature should be loaded:\n\nspec.metadata = {\n  \"bridgetown_features\" =&gt; \"true\"\n}\n\n\nCommand Structure\n\nCommands can be written &#8220;standalone&#8221; or they can written in a command [subcommand] format. In the latter case, given a base command of river, your logic will be contained within one or more subcommands:\n\nbridgetown river # outputs a help message about the available subcommands\nbridgetown river bank\nbridgetown river flows\n\n\nYou can also use the ConfigurationOverridable concern to load the site configuration and optionally override keys with command line options passed to your command.\n\nThe simplest possible form of a command is as follows:\n\nmodule Bridgetown\n  module Commands\n    class Howdy &lt; Bridgetown::Command\n      self.description = \"Give a hearty howdy\"\n\n      def call\n        puts \"Well howdy there!\"\n      end\n    end\n\n    register_command :howdy, Howdy\n  end\nend\n\n\nThe body of your command code goes in the call method, and once your class is defined you call the register_command method within Bridgetown::Commands to include it in the CLI.\n\nHere&#8217;s an example of how to write a command with multiple subcommands. Each subcommand is its own nested class, and you wire them together using Samovar&#8217;s nested method:\n\n# lib/my_example_plugin/features/my_example_plugin.rb\n\nmodule Bridgetown\n  module Commands\n    class River &lt; Bridgetown::Command\n      self.description = \"Take me to the river\"\n\n      class Bank &lt; Bridgetown::Command\n        self.description = \"Walk along the river bank\"\n\n        options do\n          option \"-w/--where &lt;TO&gt;\", \"Where to?\", required: true\n        end\n\n        def call\n          puts \"Out for a stroll...to #{options[:where]}?\"\n        end\n      end\n\n      class Flow &lt; Bridgetown::Command\n        include Bridgetown::Commands::Automations\n        include Bridgetown::Commands::ConfigurationOverridable\n\n        self.description = \"Old man river, he just keeps on rolling along\"\n\n        options do\n          option \"--destination &lt;DEST&gt;\", \"Override configuration file destination\"\n        end\n\n        def call\n          config = configuration_with_overrides(options)\n          destination = config.destination\n\n          say_status :river, \"Flowing to your destination: #{destination}\"\n        end\n      end\n\n      nested :command, {\n        \"bank\" =&gt; Bank,\n        \"flow\" =&gt; Flow,\n      }, required: true\n\n      def call = @command.call\n    end\n\n    register_command :river, River\n  end\nend\n\n\nIf you want full access to automations from within your command, you can include the Freyia &amp; Bridgetown automation tasks:\n\ninclude Bridgetown::Commands::Automations\n\n\nThen your command can run those automations:\n\nsay_status :river, \"Go with the flow! :)\"\n\n\nYou might also need a site object loaded up into memory. Ensure you have included the ConfigurationOverridable mixin as in the example above, then initialize a site at the start of your command logic:\n\nconfig = configuration_with_overrides(options)\nconfig.run_initializers! context: :static\nsite = Bridgetown::Site.new(config)\n\n# optionally run setup hooks and read in content if needed:\nsite.reset\nsite.read\nsite.generate"
        },
        {
          "id": "docs-plugins-converters",
          "title": "Converters",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/converters",
          "content": "If you have a new markup or template language you’d like to use with your site,\nyou can process it by implementing your own converter. The Markdown and ERB\nsupport in Bridgetown is implemented using this very method.\n\n\n  \n  Remember your Front Matter\n\nBridgetown will only convert files that have a YAML or Ruby Front Matter header at the top, even for converters you add using a plugin.\n\n\nBelow is a converter which will take all posts ending in .upcase and process\nthem using the UpcaseConverter:\n\n# ./plugins/upcase_converter.rb\n\nclass UpcaseConverter &lt; Bridgetown::Converter\n  priority :low\n\n  input :upcase\n\n  def convert(content)\n    content.upcase\n  end\nend\n\n\nIn this example, the convert method is provided the raw content of the file\n(without front matter), and it returns an uppercase string back. The converter\nwill only run for files with the extension(s) defined using the input class\nmethod.\n\nIf you need to examine the source page/document or layout object which is\nresponsible for processing the file, you can access it using a second convertible\nargument:\n\ndef convert(content, convertible)\n  content + \" — brought to you by the #{convertible.class} object.\"\nend"
        },
        {
          "id": "docs-plugins-external-apis",
          "title": "HTTP Requests and the Resource Builder",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/external-apis",
          "content": "In this section, you’ll learn how to make web requests and easily parse the response to save site data or construct new resources like blog posts or collection entries.\n\nBelow is an example of making an HTTP GET request to a remote API, looping through an array parsed from the JSON response, and saving new posts based on each item.\n\n\n  \n  The examples on this page use the HTTPX Ruby gem, but you can use any HTTP client. If you use a gem, just remember to run bundle add httpx (or your preferred gem) so that you can require it.\n\n\nrequire \"httpx\"\n\nclass LoadPostsFromAPI &lt; SiteBuilder\n  def build\n    HTTPX.get(\"https://domain.com/posts.json\").json.each do |post|\n      add_resource :posts, \"#{post[\"slug\"]}.md\" do\n        ___ post\n        layout :post\n        categories post[\"taxonomy\"][\"category\"].map { |category| category[\"slug\"] }\n        date Bridgetown::Utils.parse_date(post[\"date\"])\n        content post[\"body\"]\n      end\n    end\n  end\nend\n\n\nTable of Contents\n\n  The Resource Builder    \n      Collections\n      Customizing Permalinks\n      Merging Hashes Directly into Front Matter\n    \n  \n  DSL Scope\n  Builder Lifecycle and Data Files\n  Conclusion\n\n\nThe Resource Builder\n\nAdding content from an API to the site.data object is certainly useful, but an even more powerful feature is the Resource Builder. Call the add_resource method to generate resources which function in exactly the same way as if those files were already stored in your repository. It uses a special DSL, similar to Ruby Front Matter.\n\nHere’s an example of creating a new blog post:\n\ndef build\n  add_resource :posts, \"2020-05-17-way-to-go-bridgetown.md\" do\n    layout :post\n    title \"Way to Go, Bridgetown!\"\n    author \"rlstevenson\"\n    content \"It's pretty _nifty_ that you can add **new blog posts** this way.\"\n  end\nend\n\n\nThis is the programmatic equivalent of saving a new file src/_posts/2020-05-17-way-to-go-bridgetown.md with the following contents:\n\n---\ntitle: Way to Go, Bridgetown!\nauthor: rlstevenson\n---\n\nIt's pretty _nifty_ that you can add **new blog posts** this way.\n\n\nCollections\n\nYou can save a resource in any collection:\n\nadd_resource :authors, \"rlstevenson.md\" do\n  name \"Robert Louis Stevenson\"\n  born 1850\n  nationality \"Scottish\"\nend\n\n\nYou don’t even need to use a collection that’s previously been configured in initializers.rb or bridgetown.config.yml. You can make up new collections and use existing layouts to place your content within the appropriate templates, assuming the expected front matter is compatible.\n\nadd_resource :blogish, \"fake-blog-post.html\" do\n  layout :post\n  title \"I'm a blog post…sort of\"\n  date \"2020-05-17\"\n  content \"&lt;p&gt;I might look like a blog post, but I'm &lt;em&gt;not!&lt;/em&gt;&lt;/p&gt;\"\nend\n\n\nThat resource would then get written out to the /blogish/fake-blog-post/ URL.\n\nAnother aspect of the Resource Builder to keep in mind is that content is a “special” variable. Everything except content is considered front matter, and content is everything you’d add to a file after the front matter.\n\nCustomizing Permalinks\n\nIf you’d like to customize the permalink of a new resource, you can specifically set the permalink front matter variable:\n\nadd_resource :posts, \"blog-post.md\" do\n  title \"Strange Paths\"\n  date \"2019-07-23\"\n  permalink \"/path/to/the/:slug/\"\n  content \"…\"\nend\n\n\nThe post would then be accessible via /path/to/the/blog-post/.\n\nMerging Hashes Directly into Front Matter\n\nIf you have a hash of variables you’d like to merge into a resource’s front matter, you can use the ___ method.\n\nvars = {\n  title: \"I'm a Draft\",\n  categories: [\"category1\", \"category2\"],\n  published: false\n}\n\nadd_resource :posts, \"post.html\" do\n  ___ vars\nend\n\n\nThis is great when you have data coming in from external APIs and you’d like to inject all of that data into the front matter with a single method call.\n\nBear in mind that this doesn’t include your content variable. So you’ll still need to set that separately when using the ___ method, for example:\n\ndata = HTTPX.get(article_url).json\nadd_resource :pages, \"articles/#{data[\"slug\"]}.html\" do\n  ___ data\n  content data[\"body\"]\nend\n\n\nDSL Scope\n\nIf you’re not familiar with Ruby DSLs, you may run into an issue where you need to call a method from your builder plugin within add_resource and it’s not in scope. For example, this won’t work:\n\ndef string_value\n  \"I'm a string!\"\nend\n\ndef build\n  add_resource :pages, \"page.html\" do\n    title string_value\n    content \"Page content.\"\n  end\nend\n\n\nThe reason it won’t work is because in this example, title is actually interpreted as a method call within the DSL block, which means string_value is a similar call. That would be fine if you’d already added string_value as a front matter key, in which case string_value would return that front matter variable. But in this case, you want to use the string_value method of your plugin.\n\nTo accomplish that, provide a lambda using the from: -&gt; { } syntax. Let’s rewrite the above example to work as expected:\n\ndef string_value\n  \"I'm a string!\"\nend\n\ndef build\n  add_resource :pages, \"page.html\" do\n    title from: -&gt; { string_value }\n    content \"Page content.\"\n  end\nend\n\n\nNow the title front matter variable will be set to “I’m a string”.\n\nBuilder Lifecycle and Data Files\n\nSomething to bear in mind is that that code in your build method is run as part of the site’s pre_read hook, which means that no data or content in your site repository has yet been loaded at that point. So you can’t, say, build resources based on existing data files as you might assume:\n\ndef build\n  # THIS WON'T WORK!!!\n  site.data[:stuff_from_the_repo].each do |k, v|\n    add_resource :stuff, \"#{k}.md\" do\n      ___ v\n      content v[:content]\n    end\n  end\nend\n\n\nInstead, what you can do is define a post_read custom hook and then read in the data:\n\ndef build\n  hook :site, :post_read do\n    site.data[:stuff_from_the_repo].each do |k, v|\n      add_resource :stuff, \"#{k}.md\" do\n        ___ v\n        content v[:content]\n      end\n    end\n  end\nend\n\n\nConclusion\n\nAs you’ve seen from these examples, you can use data from external APIs to create new content for your Bridgetown website with the add_resource method provided by the Builder API. While there are numerous benefits to storing content directly in your site repository, Bridgetown gives you the best of both worlds—leaving you to decide where you want your content to live and how you’ll put it to good use as you build your site."
        },
        {
          "id": "docs-plugins-filters",
          "title": "Filters",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/filters",
          "content": "Filters are Ruby methods you can provide to Liquid templates to transform input data in various ways.\n\nAll methods take at least one argument which represents the input\nof the filter, and you can also support multiple method arguments (and even optional ones). The return value will be the output of the filter.\n\nExample:\n\nclass Builders::Filters &lt; SiteBuilder\n  def build\n    liquid_filter :cache_busting_url do |url|\n      \"http://www.example.com/#{url}?#{Time.now.to_i}\"\n    end\n  end\nend\n\n\n{{ \"mydynamicfile.js\" | cache_busting_url }}\n\n\noutputs:\n\nhttp://www.example.com/mydynamicfile.js?1586194585\n\n\nSupporting Arguments\n\nYou can accept multiple arguments to your filter by adding them to your block or method, and optional ones are specified with a default value (perhaps nil or false). For example:\n\nclass Builders::Filters &lt; SiteBuilder\n  def build\n    liquid_filter :multiply_and_optionally_add do |input, multiply_by, add_by = nil|\n      value = input * multiply_by\n      add_by ? value + add_by : value\n    end\n  end\nend\n\n\nYou can then use it as:\n\n\n5 times 10 equals {{ 5 | multiply_and_optionally_add:10 }}\n\n  output: 5 times 10 equals 50\n\n5 times 10 plus 3 equals {{ 5 | multiply_and_optionally_add:10, 3 }}\n\n  output: 5 times 10 plus 3 equals 53\n\n\n\nAnd of course you can chain any number of built-in and custom filters together:\n\n\n5 times 10 minus 4 equals {{ 5 | multiply_and_optionally_add:10 | minus:4 }}\n\n  output: 5 times 10 minus 4 equals 46\n\n\n\nUsing Instance Methods\n\nAs with other parts of the Builder API, you can also use an instance method to register your filter:\n\nclass Builders::Filters &lt; SiteBuilder\n  def build\n    liquid_filter :cache_busting_url, :bust_it\n  end\n\n  def bust_it(url)\n    \"http://www.example.com/#{url}?#{Time.now.to_i}\"\n  end\nend\n\n\nIf your filter name and method name are the same, you can omit the second argument.\n\nFilter Execution Scope\n\nThe code within the filter block or method is executed within the scope of the builder object. This means you will need to use the filters method to call other filters.\n\nclass Builders::Filters &lt; SiteBuilder\n  def build\n    liquid_filter :slugify_and_upcase do |url|\n      filters.slugify(url).upcase\n    end\n  end\nend\n\n\nYou also have access to the Liquid context via filters_context, which provides current template objects such as the page (e.g., filters_context.registers[:page]).\n\nWhen to use a Filter vs. a Tag\n\nFilters are great when you want to transform input data from one format to another and potentially allow multiple transformations to be chained together. If instead you want to insert a customized piece of content/HTML code into a page, then it’s probably better to write a Tag.\n\n\n  \n  If you prefer to use the Legacy API (aka Liquid::Template.register_filter ) to construct Liquid filters, refer to the Liquid documentation here."
        },
        {
          "id": "docs-plugins-foundation-gem",
          "title": "Foundation Gem",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/foundation-gem",
          "content": "The Foundation gem is part of the Bridgetown Ruby API, offering some handy helpers for strings, hashes, class hierarchies, and more. Over time we intend to migrate reusable, utility-like patterns elsewhere in the Bridgetown codebase to this Foundation gem. We hope this can prove useful even on non-Bridgetown projects (see below).\n\nA number of the features in Foundation are provided via a Ruby feature called refinements. This may not be so well-known to some developers, so we’ll explain how it works.\n\nTable of Contents\n\n  How Refinements Work (and How to Add Your Own)\n  Using Foundation Outside of Bridgetown\n  Foundation API    \n      Object        \n          within?\n        \n      \n      Class        \n          descendants\n        \n      \n      String        \n          indent / indent!\n          starts_with? / ends_with?\n          questionable?\n          colors (red, cyan, etc.)\n        \n      \n      Module        \n          nested_within?\n          nested_parents / nested_parent\n          nested_name\n        \n      \n      Packages        \n          Ansi\n          PidTracker\n          SafeTranslations\n        \n      \n      Intuitive Expectations for Minitest        \n          Solargraph Configuration\n        \n      \n    \n  \n\n\nHow Refinements Work (and How to Add Your Own)\n\nA refinement is like a mixin for an existing Ruby class, but instead of monkey-patching that class in a global way, it only patches within a lexical scope. When you activate a refinement via using, you have access to that refinement only within that scope (which could be a module or class, or an entire Ruby file).\n\nThe Foundation refinements are all bundled together within a single module, so to “opt-in” with your code, call using Bridgetown::Refinements.\n\nHere’s an example:\n\nusing Bridgetown::Refinements\n\n\"abc\".within? %w[xyz abc] # true\n\n\nThe within? method is only available in a scope where the refinements are activated. Here’s an example of scoping to a single class:\n\nclass TryWithin\n  using Bridgetown::Refinements\n\n  def initialize(str) = @str = str\n  def is_within?(arr) = @str.within? arr\nend\n\nTryWithin.new(\"abc\").is_within?(%w[xyz abc]) # true\n\n\"abc\".within? %w[xyz abc] # raises NoMethodError\n\n\nThere are certain contexts in which it’s not possible use using, for example in Roda server blocks or Ruby templates like .erb. In those cases, you can “refine an object” directly with the refine helper and use refinement methods on that:\n\nrefine(\"abc\").within? %w[xyz abc] # true\n\n\nThere’s also a Bridgetown.refine(obj) method you can call if the helper is not available in a certain execution context. Otherwise, you can write include Bridgetown::Refinements::Helper—but most likely, if you’re in a position to do that you can simply go with the normal using way of activating refinements.\n\nUnder the hood, a “refined object” is a SimpleDelegator Ruby class which wraps the original object, so using the refined object is indistinguishable from using the original object (other than refinement methods are available).\n\nFoundation makes it easy to add your own refinements! There’s only a small amount of boilerplate required:\n\nmodule Adding\n  refine Numeric do\n    def add(input)\n      self + input\n    end\n  end\nend\n\nBridgetown.add_refinement(Adding) do\n  # This is required boilerplate:\n  using Bridgetown::Refinements\n  def method_missing(...) = __getobj__.send(...) # rubocop:disable Style\nend\n\n\nand then:\n\nclass AddNumbers\n  using Bridgetown::Refinements\n\n  def initialize(num) = @num = num\n  def together(new_num) = @num.add new_num\nend\n\nAddNumbers.new(10).together(15) # 25\n\n\nYou could put this in a config file or in a plugin gem, whatever makes sense for your use case.\n\nBridgetown::Refinements also automatically includes the HashWithDotAccess refinement which adds an as_dots method to Ruby Hash to convert a standard hash to one with string/symbol/method key access.\n\nUsing Foundation Outside of Bridgetown\n\nYou can bundle add bridgetown-foundation to any Ruby project to gain access to features in Foundation. For the entire set of features you can require the gem when your application boots:\n\nrequire \"bridgetown-foundation\"\n\n\nFor certain features, you may be able to require them standalone without pulling in the rest of the gem. (Make sure you always require the basic version module to start.) For example, to use the Intuitive Expectations feature:\n\nrequire \"bridgetown/version\"\nrequire \"bridgetown/foundation/intuitive_expectations\"\n\n\nFoundation API\n\nThe following methods are accessed via refinements unless otherwise noted.\n\nObject\n\nwithin?\n\nThis method lets you check if the receiver is “within” the other object. In most cases, this check is accomplished via the include? method…aka, 10.within? [5, 10] would return true as [5, 10].include? 10 is true. String/String comparison are case-insensitive, so \"FOO\".within?(\"foobar\") is true.\n\nHowever, for certain comparison types: Module/Class, Hash, and Set, the lesser-than (&lt;) operator is used instead. This is so you can check BigDecimal.within? Numeric, {easy_as: 123}.within?({indeed: \"it's true\", easy_as: 123}), and Set[:b, :c].within? Set[:a, :b, :c, :d] (under the hood Set evaluates using proper_subset?).\n\nFor Array/Array comparisons, a difference (&amp;&amp;) is checked, so [1,2].within? [3,2,1] is true, but [1,2].within? [2,3] is false.\n\nFor Range, the cover? method is used, so (3..4).within?(2..6) is true, but (1..4).within?(2..6) is false.\n\nClass\n\ndescendants\n\nMonkey-patch. This class method lets you retrieve a flattened array of all of a class’ descendants (and their descendants if applicable, etc.). There are two stipulations for a class to be included: it has to have a name (so no anonymous classes), and it has to be available in the global namespace (aka accessible via Kernel.const_get).\n\nString\n\nindent / indent!\n\nThis lets you indent a string by a certain number of spaces. \"it\\n  is indented\\n\\nnow\".indent(2) would result in \"  it\\n    is indented\\n\\n  now\" (each line now starts with two spaces). indent! modifies a string in-place.\n\nstarts_with? / ends_with?\n\nMonkey-patch. Aliases for Ruby’s native start_with? and end_with? methods.\n\nquestionable?\n\nThis returns a Bridgetown::Foundation::QuestionableString copy of the string, now with the ability to use a question method. \"test\".questionable.test? will return true, whereas \"test\".questionable.nope? will return false. This is used by Bridgetown.env so you can call Bridgetown.env.production?.\n\ncolors (red, cyan, etc.)\n\nMonkey-patch. You can use ANSI color methods on strings as part of colorized terminal output, e.g., puts \"Error\".red. These colors are provided by Foundation’s Ansi package. If your output somehow gets “stuck” in a color, you can also call reset_ansi on a string.\n\nModule\n\nnested_within?\n\nThis lets you check if a particular module or class is nested inside of a namespace. For example, Bridgetown::Resource::Base.nested_within? Bridgetown::Resource is true, but Bridgetown::Resource::Base.nested_within? Bridgetown::Model is false.\n\nnested_parents / nested_parent\n\nThe first method will return an array of parent classes/modules within the namespace hierarchy. Bridgetown::Resource::Base.nested_parents returns [Bridgetown::Resource, Bridgetown]. And Bridgetown::Resource::Base.nested_parent returns Bridgetown::Resource.\n\nnested_name\n\nThis returns the string identifier of the class/module name without its nested parents. For example: Bridgetown::Resource::Base.name would return \"Bridgetown::Resource::Base\", but Bridgetown::Resource::Base.nested_name returns \"Base\".\n\nPackages\n\nA few of the features in Foundation are provided in the form of Inclusive packages. This is a “syntactic sugar” way of accessing utility methods from modules without using mixins directly. You can load in a package by adding include Inclusive in a Ruby class and then defining a method for accessing one or more packages:\n\npackages def some_package = [Some::Available::Package]\n\ndef later_on\n  some_package.method_here(...)\nend\n\n\nAnsi\n\nThis is used for providing a way to output strings with color in a terminal. (See the docs above on String.) View code here.\n\nPidTracker\n\nThis is used for managing pid files in a multiprocess setting. View code here.\n\nSafeTranslations\n\nThis package is used to manage the display of translations which include HTML (although it’s not inherently HTML-specific), marking them as “safe”. Sample usage:\n\nclass TranslateWithHTML\n  include Inclusive\n\n  packages def translate_package = [Bridgetown::Foundation::Packages::SafeTranslations]\n\n  def translate(key, **options)\n    escaper = -&gt;(input) { input.to_s.encode(xml: :attr).gsub(%r{\\A\"|\"\\Z}, \"\") }\n    translate_package.translate(key, escaper, :html_safe, **options)\n  end\nend\n\n# This will escape the value in the provided hash\nTranslateWithHTML.new.translate(\"key.path.to.entry\", { name: \"Jared White &lt;script&gt;// XSS&lt;/script&gt;\" })\n\n\nView code here.\n\nIntuitive Expectations for Minitest\n\nFoundation provides a set of extensions to Minitest’s built-in Expectation class called Intuitive Expectations which lets you use more concise operators and “Rubyish” syntax. These are automatically provided by the Bridgetown::Test class which you can use for automated testing (set up via our bundled configuration), but you can also add Intuitive Expectations to your own standalone projects used by Minitest.\n\nAdd bridgetown-foundation to your Gemfile and then enrich Minitest near the top of your test or main helper file:\n\nrequire \"bridgetown/version\"\nrequire \"bridgetown/foundation/intuitive_expectations\"\n\nBridgetown::Foundation::IntuitiveExpectations.enrich Minitest\n\n\nWe also recommend setting ENV[\"MT_NO_EXPECTATIONS\"] = \"true\" to avoid expectations methods being added to all objects.\n\nNow you can write expectations using the new syntax:\n\ndescribe \"great expectations\" do\n  it \"works!\" do\n    str = \"yay!\"\n    expect(str) == \"yay!\"\n  end\nend\n\n# some more examples:\n\nexpect(some_int) != 123\nexpect(some_big_str) &lt;&lt; \"howdy\" # or expect(...).include? ...\nexpect(some_bool).true? # aliased to truthy?\nexpect(2..4).within?(1..6)\n\n# you can also chain multiple expectations together:\n\nexpect(big_string)\n  .include?(\"foo\")\n  .include?(\"bar\")\n  .exclude?(\"baz\")\n\nexpect(user)\n  .is?(:moderator?)\n  .isnt?(:admin?)\n\n\nLearn more about the Intuitive Expectations API here.\n\nSolargraph Configuration\n\nIf you are using Solargraph as your LSP, you can add typing through Yard comments so you get type hints in your tests for the new expectation methods. First, make sure you have a configuration YAML file with the following added to require:\n\nrequire:\n- \"bridgetown-foundation\"\n\n\n(Also take out any test/spec references under exclude or this won’t work.)\n\nThen in your test or helper file add:\n\nMinitest::Spec::DSL::InstanceMethods.class_eval do\n  # @!method expect\n  #   Takes a value\n  #   @return [Minitest::Expectation]\nend\n\nMinitest::Expectation.class_eval do\n  # @!parse include Bridgetown::Foundation::IntuitiveExpectations\nend\n\n\nFor typical spec-style tests, add this as well:\n\n# @!parse extend Minitest::Spec::DSL::InstanceMethods\n\n\nOr when using a concrete test superclass:\n\nclass ExampleTestSuperclass &lt; Minitest::Test\n  # @!parse extend Minitest::Spec::DSL::InstanceMethods\n\n  extend Minitest::Spec::DSL\nend"
        },
        {
          "id": "docs-plugins-front-matter-loaders",
          "title": "Front Matter Loaders",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/front-matter-loaders",
          "content": "This API allows you or a third-party gem to augment resources with new types of front matter. To do so, create a new class inheriting from Bridgetown::FrontMatter::Loaders::Base that defines an override of the #read method, then register it using Bridgetown::FrontMatter::Loaders.register.\n\nTypically, loaders define two constants by convention:\n\n\n  HEADER matches the opening line of the front matter\n  BLOCK matches the contents of the front matter block with the first capturing group being the content and the regular expression consuming the ending delimiter\n\n\nThe #read method returns a nullable Bridgetown::FrontMatter::Loaders::Result with these three attributes:\n\n\n  content - the content of the resource without the front matter\n  front_matter - the front matter hash after processing the front matter content\n  line_count - the number of lines making up the front matter content\n\n\nLimitations\n\nCurrently, front matter loaders process the contents of resources in First-In, First-Out (FIFO) order meaning the built-in loaders take precedence over any new ones.\n\nThis means that loaders should not have overlapping delimiter definitions. Because Bridgetown is flexible in its delimiters — e.g. the YAML loader accepts triple- hyphens, tildes, backticks, or pounds for its code blocks — you must take care when picking delimiters so that multiple loaders do not overlap definitions."
        },
        {
          "id": "docs-plugins-gems-and-frontend",
          "title": "Gem-based Plugins and the Frontend",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/gems-and-frontend",
          "content": "When authoring a plugin\nor theme for Bridgetown, you may find\nyourself wanting to ensure users of your plugin are able to load in your\nfrontend assets through esbuild (such as JavaScript, CSS, etc.) The best way to\ndo this is to set up a package.json manifest and publish your frontend code as a package to the NPM registry.\n\nLet&#8217;s assume you&#8217;ve been building an awesome plugin called, unsurprisingly,\nMyAwesomePlugin. In your my-awesome-plugin.gemspec file, all you need to do is\nadd the npm_add metadata matching the NPM package name and keeping the version\nthe same as the Gem version:\n\n  spec.metadata = { \"npm_add\" =&gt; \"my-awesome-plugin@#{MyAwesomePlugin::VERSION}\" }\n\n\nWith that bit of metadata, Bridgetown will know always to look for that package in\nthe users&#8217; package.json file when they load Bridgetown, and it will trigger a\nnpm install command (or equivalent for Yarn or pnpm) if the package and exact version number isn&#8217;t present.\n\nThe SamplePlugin demo repo\nincludes a script/release command you can use to run the test suite, release a\nnew version of the gem, and release a new version of the NPM package all in one\ngo. (This will also be present if you set up your plugin using the bridgetown plugins new command.)\n\n\n  \n  Make sure you update package.json!\n\nIf you bump up your Ruby version number and forget to bump the NPM package version at the same time, the packages will get out of sync! So remember always to update version.rb and package.json so they have the same version number.\n\n\nYou will need to instruct your users how to add the plugin&#8217;s frontend code to their\nesbuild entry points. For example, they might need to update frontend/javascript/index.js with:\n\nimport MyAwesomePlugin from \"my-awesome-plugin\"\n\nconst awesomeness = new MyAwesomePlugin()\nawesomeness.doCoolStuff()\n\n\nConsider writing an automation to make this process\neasier for users."
        },
        {
          "id": "docs-plugins-generators",
          "title": "Generators",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/generators",
          "content": "You can write a generator when you need Bridgetown to add data to existing content or to programmatically create new pages, posts, and the like. Generators run after Bridgetown has made an inventory of the existing content, but before the site is rendered out.\n\n\n  \n  Tip: if all you&#8217;re doing is creating new resources, perhaps based on data from an external API, you&#8217;ll likely want to use Resource Builder API rather than write a generator. You can also take a look at hooks for fine-grained access to Bridgetown&#8217;s build lifecycle.\n\n\nBuilder API\n\nAdd a generator call to your build method. You can supply a block or pass in a method name as a symbol.\n\ndef build\n  generator do\n    # update or add content here\n  end\n\n  generator :build_search_index\nend\n\ndef build_search_index\n  # do some search index building :)\nend\n\n\nA generator can inject values computed at build time into page variables. In the\nfollowing example, the page reading.html will have two variables ongoing and done\nthat get added via the generator:\n\nclass Builders::BookStatus &lt; SiteBuilder\n  def build\n    generator do\n      book_status = remote_data # perhaps fetching data from an API\n\n      reading = site.collections.pages.resources.detect {|page| page.relative_path.basename.to_s == 'reading.html'}\n      reading.data['ongoing'] = book_status.ongoing\n      reading.data['done'] = book_status.done\n    end\n  end\nend"
        },
        {
          "id": "docs-plugins-helpers",
          "title": "Helpers",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/helpers",
          "content": "Helpers are Ruby methods you can provide to Tilt-based templates (ERB, Slim, etc.) to transform data or output new content in various ways.\n\nExample:\n\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :cache_busting_url do |url|\n      \"http://www.example.com/#{url}?#{Time.now.to_i}\"\n    end\n  end\nend\n\n\n&lt;%= cache_busting_url \"mydynamicfile.js\" %&gt;\n\n\noutputs:\n\nhttp://www.example.com/mydynamicfile.js?1586194585\n\n\nSupporting Arguments\n\nYou can accept multiple arguments to your helper by adding them to your block or method, and optional ones are specified with a default value (perhaps nil or false). For example:\n\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :multiply_and_optionally_add do |input, multiply_by, add_by = nil|\n      value = input * multiply_by\n      add_by ? value + add_by : value\n    end\n  end\nend\n\n\nThen use it like this:\n\n5 times 10 equals &lt;%= multiply_and_optionally_add 5, 10 %&gt;\n\n  output: 5 times 10 equals 50\n\n5 times 10 plus 3 equals &lt;%= multiply_and_optionally_add 5, 10, 3 %&gt;\n\n  output: 5 times 10 plus 3 equals 53\n\n\nUsing Instance Methods\n\nAs with other parts of the Builder API, you can also use an instance method to register your helper:\n\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :cache_busting_url, :bust_it\n  end\n\n  def bust_it(url)\n    \"http://www.example.com/#{url}?#{Time.now.to_i}\"\n  end\nend\n\n\nIf your helper name and method name are the same, you can omit the second argument.\n\nHelper Execution Scope\n\nThe code within the helper block or method is executed within the scope of the builder object. This means you will need to use the helpers method to call other helpers or gain access to the view context.\n\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :slugify_and_upcase do |url|\n      helpers.slugify(url).upcase\n    end\n    helper :slugify_and_downcase\n  end\n\n  def slugify_and_downcase(url)\n    helpers.slugify(url).downcase\n  end\nend\n\n\nThe helpers.view method will return a subclassed instance of Bridgetown::TemplateView which reflects the current template engine in use. For example, it will be Bridgetown::ERBView for ERB templates. This gives you access to engine-specific view methods such as partial, as well as any other custom methods that may have been defined for the view to use.\n\nUsing the Capture Helper\n\nYou can &#8220;capture&#8221; the contents of a block and use that text inside your helper. Optionally, you can pass an object to the block itself from your helper. For example:\n\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :capture_and_upcase do |&amp;block|\n      label = \"upcased\"\n      helpers.view.capture(label, &amp;block).upcase\n    end\n  end\nend\n\n\n\nAnd then, call that helper in your template and use the label argument:\n\n&lt;%= capture_and_upcase do |label| %&gt;\n  I have been &lt;%= label %&gt;!\n&lt;% end %&gt;\n\n  output: I HAVE BEEN UPCASED!\n\n\nHelpers vs. Filters vs. Tags\n\nFilters and tags are aspects of the Liquid template engine which comes installed by default. The behavior of both filters and tags are roughly analogous to helpers in Tilt-based templates. Specialized Bridgetown filters are also made available as helpers, as are a few tags such as asset_path."
        },
        {
          "id": "docs-plugins-hooks",
          "title": "Hooks",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/hooks",
          "content": "Using hooks, your plugin can exercise fine-grained control over various aspects\nof the build process. If your plugin defines any hooks, Bridgetown will call them\nat pre-defined points.\n\nHooks are registered to an owner and an event name. For example, if you want to execute some\ncustom functionality every time Bridgetown renders a post, you could register a\nhook like this:\n\n# Builder API:\ndef build\n  hook :posts, :post_render do |post|\n    # code to call after Bridgetown renders a post\n  end\nend\n\n# or using the hooks API directly:\nBridgetown::Hooks.register_one :posts, :post_render do |post|\n  # code to call after Bridgetown renders a post\nend\n\n\nBe aware that the build method of the Builder API is called during the pre_read site event, so you won&#8217;t be able to write a hook for any earlier events (after_init for example). In those cases, you will still need to use the hooks API directly.\n\nBridgetown provides hooks for :site, :resources, :loader, :clean, and :[collection_label] (aka every collection gets a unique hook, such as posts or countries or episodes, etc.).\n\nIn all cases, Bridgetown calls your hooks with the owner object as the first callback\nparameter.\n\nPost-Write Hook for Performing Special Operations\n\nThe :site, :post_write hook is particularly useful in that you can use it to\nkick off additional operations which need to happen after the site has been \ncompletely built and everything has been saved to the destination folder.\n\nFor example, there might be certain files you want to compress, or maybe you\nneed to notify an external web service about new updates, or perhaps you&#8217;d like\nto run tests against the final output.\n\nPriorities\n\nHooks can be registered with a priority of high, normal, or low, and are run according to that order. The default priority is normal. To register with a different priority other than normal:\n\n# Builder API\ndef build\n  hook :posts, :post_render, priority: :high do |post|\n    # High priority code to call after Bridgetown renders a post\n  end\nend\n\nBridgetown::Hooks.register_one :posts, :post_render, priority: :low do |post|\n  # Low priority code to call after Bridgetown renders a post\nend\n\n\nReloadable vs. Non-Reloadable Hooks\n\nAll hooks are cleared during watch mode (aka bridgetown build -w or bridgetown start) whenever plugin or content files are updated. This makes sense for plugins that are part of the site repository and are therefore reloaded automatically.\n\nHowever, for gem-based plugins, you will want to make sure you define your hooks as non-reloadable, otherwise your hooks will vanish any time the site is updated during watch mode.\n\ndef build\n  hook :site, :post_read, reloadable: false do |post|\n    # do something with site data after it's read from disk\n  end\nend\n\n\nComplete List of Hooks\n\n\n  \n    \n      Owner\n      Event\n      Called\n    \n  \n  \n    \n      \n        :site\n      \n      \n        :after_init\n      \n      \n        Right after the site initializes, but before setup &amp; render. Good\n        for modifying the configuration of the site.\n      \n    \n    \n      \n        :site\n      \n      \n        :after_reset\n      \n      \n        Right after site reset and all internal data structures are in a pristine state. Not run during SSR (see below).\n      \n    \n    \n      \n        :site\n      \n      \n        :after_soft_reset\n      \n      \n        When a site is in SSR mode, any file changes result in a \"soft\" reset for performance reasons. Some state is persisted across resets. You can register a hook to perform additional cleanup/setup after a soft reset.\n      \n    \n    \n      \n        :site\n      \n      \n        :pre_read\n      \n      \n        After site reset/setup when all custom plugins, generators, etc. have loaded\n      \n    \n    \n      \n        :site\n      \n      \n        :post_read\n      \n      \n        After site data has been read and loaded from disk\n      \n    \n    \n      \n        :site\n      \n      \n        :pre_render\n      \n      \n        Right before rendering the whole site\n      \n    \n    \n      \n        :site\n      \n      \n        :post_render\n      \n      \n        After rendering the whole site, but before writing any files\n      \n    \n    \n      \n        :site\n      \n      \n        :post_write\n      \n      \n        After writing the whole site to disk\n      \n    \n    \n      \n        :site\n      \n      \n        :pre_reload\n      \n      \n        Right before reloading site plugins and Zeitwerk autoloaders during the watch process or in the console. The site and paths (file changes, if any, which triggered the reload) arguments are provided\n      \n    \n    \n      \n        :site\n      \n      \n        :post_reload\n      \n      \n        After reloading site plugins and Zeitwerk autoloaders during the watch process or in the console. The site and paths (file changes, if any, which triggered the reload) arguments are provided\n      \n    \n    \n      \n        :resources[collection_label]\n      \n      \n        :post_init\n      \n      \n        Whenever a resource is initialized\n      \n    \n    \n      \n        :resources[collection_label]\n      \n      \n        :post_read\n      \n      \n        Whenever a resource has read all of its data from the origin model, but before rendering/transformation\n      \n    \n    \n      \n        :resources[collection_label]\n      \n      \n        :pre_render\n      \n      \n        Right before rendering a resource\n      \n    \n    \n      \n        :resources[collection_label]\n      \n      \n        :post_render\n      \n      \n        After rendering a resource, but before writing it to disk\n      \n    \n    \n      \n        :resources[collection_label]\n      \n      \n        :post_write\n      \n      \n        After writing a resource to disk\n      \n    \n    \n      \n        :generated_pages\n      \n      \n        :post_init\n      \n      \n        Whenever a page is initialized\n      \n    \n    \n      \n        :generated_pages\n      \n      \n        :pre_render\n      \n      \n        Right before rendering a page\n      \n    \n    \n      \n        :generated_pages\n      \n      \n        :post_render\n      \n      \n        After rendering a page, but before writing it to disk\n      \n    \n    \n      \n        :generated_pages\n      \n      \n        :post_write\n      \n      \n        After writing a page to disk\n      \n    \n    \n      \n        :loader\n      \n      \n        :pre_setup\n      \n      \n        Before initial setup of a Zeitwerk autoloader. The `loader` object and `load_path` are provided as arguments.\n      \n    \n    \n      \n        :loader\n      \n      \n        :post_setup\n      \n      \n        After initial setup of a Zeitwerk autoloader. The `loader` object and `load_path` are provided as arguments.\n      \n    \n    \n      \n        :loader\n      \n      \n        :pre_reload\n      \n      \n        Before a Zeitwerk autoloader reloads all code under its supervision. The `loader` object and `load_path` are provided as arguments.\n      \n    \n    \n      \n        :loader\n      \n      \n        :post_reload\n      \n      \n        After a Zeitwerk autoloader reloads all code under its supervision. The `loader` object and `load_path` are provided as arguments.\n      \n    \n    \n      \n        :clean\n      \n      \n        :on_obsolete\n      \n      \n        During the cleanup of a site's destination before it is built"
        },
        {
          "id": "docs-plugins-inspectors",
          "title": "HTML and XML Inspectors",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/inspectors",
          "content": "The Inspectors API provides a useful way to review or manipulate the output of your HTML or XML resources. It allows for a safer approach of modifying HTML/XML content than alternatives such as string manipulation or regular expressions which can be prone to error or fail on unexpected input.\n\nThe API utilizes Nokogiri or Nokolexbor, Ruby gems which lets you work with a DOM-like API directly on the nodes of a document tree. Nokogiri supports both HTML &amp; XML files, whereas Nokolexbor only supports HTML (but is noticeably faster with most CSS-style querying).\n\nBridgetown doesn’t directly install the Nokogiri or Nokolexbor gems, so if that isn’t already part of your bundle, be sure to update your Gemfile to uncomment your gem(s) of choice and then run bundle install. If using Nokolexbor, be sure to add html_inspector_parser: nokolexbor to your Bridgetown configuration.\n\n\n  \n  Inspectors will only apply to files Bridgetown considers Resources. Thus any HTML or XML file in your project lacking front matter won&#8217;t get processed through your Inspectors. Make sure you add two lines of triple dashes --- to the top of any file to indicate it&#8217;s a Resource.\n\n\nYour First Inspector\n\nLet’s add an oft-requested feature to our site: automatic target=\"_blank\" attributes on all outgoing links.\n\nWe’ll create a new builder plugin and use the inspect_html method to access the document and update all the relevant links.\n\nclass Builders::Inspectors &lt; SiteBuilder\n  def build\n    inspect_html do |document|\n      document.query_selector_all(\"a\").each do |anchor|\n        next if anchor[:target]\n\n        next unless anchor[:href]&amp;.starts_with?(\"http\") &amp;&amp; !anchor[:href]&amp;.include?(site.config.url)\n\n        anchor[:target] = \"_blank\"\n      end\n    end\n  end\nend\n\n\n\n  \n  Note that query_selector_all is an alias for Nokogiri/Nokolexbor&#8217;s css method. We also provide query_selector as an alias for at_css.\n\n\nIn the example above, we loop through all a tags, skip the tag if it already has a target or is not a true external link, otherwise we set the target attribute to _blank.\n\nAnother example of a feature you might want to add is to append “#” links to the ends of headings in your content so that people can copy a permalink to that particular heading:\n\ninspect_html do |document|\n  document.query_selector_all(\"article h2[id], article h3[id]\").each do |heading|\n    heading &lt;&lt; document.create_text_node(\" \")\n    heading &lt;&lt; document.create_element(\n      \"a\", \"#\",\n      href: \"##{heading[:id]}\",\n      class: \"heading-anchor\"\n    )\n  end\nend\n\n\nYou can see this in action right on this very page!\n\n\n  \n  Inspector blocks support an optional second resource argument if you need access to the underlying Resource object.\n\n\nWorks with XML Too\n\nIf you need to work with XML files such as feeds or sitemaps, you can do this as well with the inspect_xml method. It works exactly like inspect_html, except that it can optionally take an extension argument (the default is xml).\n\ninspect_xml do |document, resource|\n  # Work on any .xml file, or…\n  # Manually check the specific XML format:\n  next unless document.root.name == \"urlset\"\n\n  # Yay, we found a sitemap!\nend\n\ninspect_xml \"opml\" do |document|\n  # OPML files are outlines which can contain URLs or other structured text.\n  urls = document.query_selector_all(\"outline[url]\").map { _1[:url] }\n  # Do something with the list of URLs in the .opml file…\nend\n\n\nPerformance Considerations\n\nAll resources which result in HTML or XML output (rather than JSON or some other format) will be processed through any defined Inspectors. For greater performance and fidelity, the Nokogiri/Nokolexbor document for a single resource will be the same across all Inspectors (rather than instantiating a new document for each Inspector).\n\n\n  \n  Nokogiri relies on a C extension which in turn uses libxml2. You should see pretty good performance unless the number of resources in your project is quite extensive.\n\nNokolexbor utilizes a C-based library called Lexbor which in some benchmarks shows a noticeable improvement over Nokogiri. Nokolexbor may also in some cases provide greater browser-like fidelity of HTML5 document trees as compared to Nokogiri.\n\n\nIf you find yourself needing to bypass Inspectors for certain, large resources to avoid the overhead of using Nokogiri/Nokolexbor, you can set the front matter variable bypass_inspectors: true to instruct Bridgetown not to parse that resource. To apply this to a whole array of resources, make it a default with front matter defaults."
        },
        {
          "id": "docs-plugins-placeholders",
          "title": "Permalink Placeholders",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/placeholders",
          "content": "Resources make use of a permalink processor to determine where to save your transformed resources in the output folder tree. Within a permalink, various placeholders can be added which will subsequently be replaced with resource-specific data.\n\nPlaceholders start with a colon :. You can only have one placeholder within a path segment—in other words, /my_path/:my_placeholder/ is valid, but /my_path/:my_placeholder-and:another_placeholder/ is not.\n\nTo define new placeholders within a plugin, use the permalink_placeholder method of your builder. For example, if you wanted a placeholder to resolve a resource data, you could add:\n\ndef build\n  permalink_placeholder :ymd do |resource|\n    \"#{resource.date.strftime(\"%Y\")}#{resource.date.strftime(\"%m\")}#{resource.date.strftime(\"%d\")}\"\n  end\n\n  permalink_placeholder :y_m_d do |resource|\n    \"#{resource.date.strftime(\"%Y\")}-#{resource.date.strftime(\"%m\")}-#{resource.date.strftime(\"%d\")}\"\n  end\nend\n\n\nThus with a permalink key of /blog/:ymd/:slug/, you’d get /blog/20211020/my-post/, or for /blog/:y_m_d/:slug/ you’d get /blog/2021-10-20/my-post/.\n\nYou can also call other placeholders procs from within your placeholder proc:\n\ndef build\n  permalink_placeholder :silly_title do |resource|\n    resource.data.title == \"Silly!\" ? \"silly\" : placeholder_processors[:title].(resource)\n  end\nend"
        },
        {
          "id": "docs-plugins-resource-extensions",
          "title": "Resource Extensions",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/resource-extensions",
          "content": "This API allows you or a third-party gem to augment resources with new methods. There are two ways to use it: in a Builder via a DSL method, or by defining your own modules to register as an extension. The Builder way only works with Ruby-based templates (ERB, etc.), whereas the module way can work with the Resource Liquid Drop as well.\n\nThere’s also a summary extension point which can allow a plugin to provide enhanced summaries for resource content.\n\nBuilder-based Extensions\n\nYou can use the define_resource_method DSL with a block to add a new method onto all Bridgetown::Resource::Base objects.\n\ndef build\n  define_resource_method :upcased_title do\n    data.title.upcase\n  end\nend\n\n\n&lt;!-- some ERB template --&gt;\nTitle: &lt;%= resource.upcased_title %&gt;\n\n\nNote that the block passed to define_resource_method is evaluated within the scope of a resource instance, which is why it’s calling data directly rather than, say, resource.data. Your local builder methods won’t be available within the block—however, any variables will be captured within the scope, so if you add builder = self, you can reference the builder within the block by calling builder.\n\nAlternatively, you can define the method directly on your builder, so then when the resource method is called, it transparently delegates to the builder. The resource accessor will be available within your method (assuming the method is called via delegation).\n\ndef build\n  define_resource_method :upcased_content\nend\n\ndef upcased_content\n  resource ? resource.content.upcase : nil\nend\n\n\nAlso, you can add a resource class method by passing the class_scope: true argument:\n\ndef build\n  define_resource_method :resource_class_name, class_scope: true do\n    \"All your #{name} are belong to us!\"\n  end\nend\n\n# After the build step:\nBridgetown::Resource::Base.resource_class_name\n# =&gt; \"All your Bridgetown::Resource::Base are belong to us!\"\n\n\nModule-based Extensions\n\nYou can use the Bridgetown::Resource.register_extension method to mixin modules to the resource base. Here’s an example of extending both Liquid Drop and Ruby resource objects:\n\nmodule TestResourceExtension\n  def self.return_string\n    \"return value\"\n  end\n\n  module LiquidResource\n    def heres_a_liquid_method\n      \"Liquid #{TestResourceExtension.return_string}\"\n    end\n  end\n\n  module RubyResource\n    def heres_a_method(arg = nil)\n      \"Ruby #{TestResourceExtension.return_string}! #{arg}\"\n    end\n  end\nend\n\nBridgetown::Resource.register_extension TestResourceExtension\n\n\nNow in any Ruby template or other scenario, you can call heres_a_method on a resource:\n\nsite.resources.first.heres_a_method\n\n\nOr in Liquid, it’ll be available through the drop:\n\n{{ site.resources[0].heres_a_liquid_method }}\n\n\nThe extension itself can be any module whatsoever, doesn’t matter—as long as you provide a sub-module of RubyResource and optionally LiquidResource, you’re golden.\n\nResource Summaries\n\nBy default the first line of content is returned when resource.summary is called, but any resource extension can provide a new way to summarize resources by defining summary_extension_output.\n\ndef build\n  define_resource_method :summary_extension_output do\n    \"SUMMARY! #{content.strip[0..10]} DONE\"\n  end\nend\n\n\nYour plugin might provide detailed semantic analysis using AI, or call out to a 3rd-party API (and ideally cache the results for better performance)…anything you can imagine."
        },
        {
          "id": "docs-plugins-source-manifests",
          "title": "Source Manifests",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/source-manifests",
          "content": "A gem-based plugin can optionally provide a Source Manifest which instructs Bridgetown how to load new content such as layouts, pages, static files, and Liquid components from folders in the gem.\n\nAll you need to do is register a new source manifest within your initializer.\n\nBridgetown.initializer :sample_plugin do |config|\n  config.source_manifest(\n    origin: SamplePlugin,\n    components: File.expand_path(\"../components\", __dir__),\n    contents: {\n      pages: File.expand_path(\"../content\", __dir__)\n    },\n    layouts: File.expand_path(\"../layouts\", __dir__)\n  )\nend\n\n\nWhat this does is allow you to create top-level folders in your gem, for example ./layouts, and Bridgetown will load content from whichever folders you specify in your manifest. So if you had the file layouts/fancy.html, a site could\nreference that layout with layout: fancy front matter.\n\nThe origin keyword argument is required (it should be the root module of your gem), but all others are optional.\n\nThe contents hash allows you to specify which collection(s) your content should be added to. Generally you will want to use pages.\n\nNamespacing Your Content\n\nIt&#8217;s considered a best practice to namespace your content whenever possible. In other words, within the one of those folders, create a subfolder with an identifier matching your plugin. In the SamplePlugin demo gem, you&#8217;ll notice that there&#8217;s content/sample_plugin, layouts/sample_plugin, etc., and files are placed within those subfolders.\n\nWhy do that? It&#8217;s so that the plugin name becomes part of the path used to reference the content from the parent website. Thus for layouts/sample_plugin/layout.html, the front matter would be layout: sample_plugin/layout. For a page like content/photo-gallery/portfolio.html, it would be accessible on the site via the URL /photo-gallery/portfolio. For a Liquid Component located at components/sample_plugin/widget.liquid, you&#8217;d render it via {% render \"sample_plugin/widget\" %}.\n\nThis is also useful in cases where the parent site needs to override some content or a layout or whatever in order to make customizations. All the developer would need to do is use the plugins command to access a folder in the gem and copy a namespaced subfolder over to the site. For example:\n\nbridgetown plugins cd AwesomePlugin/Layouts\ncp -r awesome_plugin $BRIDGETOWN_SITE/src/_layouts\n\n\nThe awesome_plugin folder would get copied over to the site&#8217;s _layouts source folder, still properly namespaced, and the site developer could make further changes from there.\n\nUsing Source Manifests to Create Themes\n\nSource manifest functionality, along with the ability to publish an NPM module with frontend assets, plus the power of automations to simplify the setup process means that you can easily design and distribute themes for use by Bridgetown site owners.\n\nRead more about themes here and how to create one yourself."
        },
        {
          "id": "docs-plugins-tags",
          "title": "Liquid Tags",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins/tags",
          "content": "Liquid tags (sometimes called “shortcodes”) provide extra functionality you can use inside of your Markdown content and any HTML template. Built-in examples added by Bridgetown include the post_url and asset_path tags. Below is an example of a custom Liquid tag that will output the time the page was rendered:\n\nclass RenderTime &lt; SiteBuilder\n  def build\n    liquid_tag :render_time do |attributes|\n      \"#{attributes} #{Time.now}\"\n    end\n  end\nend\n\n\nIn the example above, we can place the following tag anywhere in one of our\npages:\n\n&lt;p&gt;{% render_time page rendered at: %}&lt;/p&gt;\n\n\nAnd we would get something like this on the page:\n\n&lt;p&gt;page rendered at: Tue June 22 23:38:47 –0500 2010&lt;/p&gt;\n\n\nTag Blocks\n\nThe render_time tag seen above can also be rewritten as a tag block. Look at this example:\n\nclass RenderTime &lt; SiteBuilder\n  def build\n    liquid_tag :render_time, as_block: true do |attributes, tag|\n      \"#{tag.content} #{Time.now}\"\n    end\n  end\nend\n\n\nWe can now use the tag block anywhere:\n\n{% render_time %}\npage rendered at:\n{% endrender_time %}\n\n\nAnd we would still get the same output as above on the page:\n\n&lt;p&gt;page rendered at: Tue June 22 23:38:47 –0500 2010&lt;/p&gt;\n\n\n\n  \n  In the above example, the tag block and the tag are both registered with the name render_time, but you&#8217;ll want to avoid registering a tag and a tag block using the same name in the same project as this will lead to conflicts.\n\n\nUsing Instance Methods\n\nAs with other parts of the Builder API, you can also use an instance method to register your tag:\n\nclass Upcase &lt; SiteBuilder\n  def build\n    liquid_tag :upcase, :upcase_tag, as_block: true\n  end\n\n  def upcase_tag(attributes, tag)\n    tag.content.upcase\n  end\nend\n\n\nIf your tag name and method name are the same, you can omit the second argument.\n\n{% upcase %}\ni am upper case\n{% endupcase %}\n\n\noutput: I AM UPPER CASE\n\nSupporting Multiple Attributes and Accessing Template Variables\n\nIf you’d like your tag to support multiple attributes separated by a comma:\n\nparam1, param2 = attributes.split(\",\").map(&amp;:strip)\n\n\nThen you could use the tag like this:\n\n{% mytag value1, value2 %}\n\n\nYou can also access local Liquid template variables from within your tag by\naccessing the context object, and that includes nested variables you would\nnormally access such as {{ page.title }}.\n\nGiven a page with a title “My Exciting Webpage”, you could reference it like this:\n\ntag.context[\"page\"][\"title\"] # returns \"My Exciting Webpage\"\n\n\nWhen to use a Tag vs. a Filter\n\nTags and Tag Blocks are great when you want to insert a customized piece of\ncontent/HTML code into a page. If instead you want to transform input data from\none format to another and potentially allow multiple transformations to be chained\ntogether, then it’s probably better to write a Filter.\n\n\n  \n  If you prefer to use the Legacy API (aka Liquid::Template.register_tag) to\nconstruct Liquid tags, refer to the Liquid documentation here."
        },
        {
          "id": "docs-template-engines-erb-and-beyond",
          "title": "Ruby-Based Template Types",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "template-engines",
          "tags": "",
          "url": "/docs/template-engines/erb-and-beyond",
          "content": "Bridgetown’s implementation language, Ruby, has a rich history of providing “Embedded RuBy” aka ERB templates and view layers across a wide variety of tools and frameworks. In addition to ERB, Bridgetown provides two additional Ruby-based template types: Serbea (a superset of ERB), and Streamlined (which is a form of pure Ruby code).\n\n\n  \n  New Bridgetown sites are configured with ERB by default. But you can start off a project with another engine like Serbea or Liquid with a configuration change, and you can use multiple template types in a single project.\n\nNote that Streamlined itself can’t be specified as a “template engine” because it’s not string-based (so you couldn’t “embed” Streamlined code in, say, a Markdown file). Streamlined works well as an augmentation to a site configured with either ERB or Serbea.\n\n\n\n  \n  Under the hood, Bridgetown uses the Tilt gem to load and process ERB &amp; Serbea. Plugin authors can leverage Tilt to add support for other template types.\n\n\nTable of Contents\n\n  ERB Basics    \n      Serbea\n    \n  \n  Dot Access Hashes\n  Partials\n  Rendering Ruby Components\n  Liquid Filters, Tags, and Components\n  Layouts\n  Markdown\n  Extensions and Permalinks\n  Link and URL Helpers\n  Slotted Content\n  Other HTML Helpers    \n      html_attributes\n      capture\n    \n  \n  Custom Helpers\n  Escaping and HTML Safety    \n      When Using Serbea\n    \n  \n  Streamlined    \n      Enforcing Streamlined helpers using Rubocop\n    \n  \n  Universal Rendering\n\n\nERB Basics\n\nFor ERB, resources are typically saved with an .erb extension. Other extensions like .html or .md will be processed through ERB unless another template engine is configured. To embed Ruby code in your template, use the delimiters &lt;% %&gt; for code blocks and &lt;%= %&gt; for output expressions.\n\nAs with all resources, you’ll need to add front matter to the top of the file (or at the very least two lines of triple dashes ---) for the file to get processed. In the Ruby code you embed, you’ll be interacting with the underlying Ruby API for Bridgetown objects (aka Bridgetown::Page, Bridgetown::Site, etc.). Here’s an example:\n\n---\ntitle: I'm a page!\n---\n\n&lt;h1&gt;&lt;%= data.title %&gt;&lt;/h1&gt;\n\n&lt;p&gt;Welcome to &lt;%= Bridgetown.name.to_s %&gt;!&lt;/p&gt;\n\n&lt;footer&gt;Authored by &lt;%= site.data.authors.first.name %&gt;&lt;/footer&gt;\n\n\nFront matter is accessible via the data method on pages, posts, layouts, and other documents. The resource itself is available via resource. Site config values are accessible via the site.config method, and loaded data files via site.data as you would expect.\n\n\n  \n  In addition to site, you can also access the site_drop object which will provide similar access to various data and config values similar to the site variable in Liquid.\n\n\nIf you need to escape an ERB tag (to use it in a code sample for example), use two percent signs:\n\nHere's my **Markdown** file.\n\n```erb\nAnd my &lt;%%= \"ERB code sample\" %&gt;\n```\n\n\nYou can easily loop through resources in a collection:\n\n&lt;% collections.posts.each do |post| %&gt;\n  &lt;li&gt;&lt;a href=\"&lt;%= post.relative_url %&gt;\"&gt;&lt;%= post.data.title %&gt;&lt;/a&gt;&lt;/li&gt;\n&lt;% end %&gt;\n\n\nOr using the paginator, along with the link_to helper:\n\n&lt;% paginator.each do |post| %&gt;\n  &lt;li&gt;&lt;%= link_to post.data.title, post %&gt;&lt;/li&gt;\n&lt;% end %&gt;\n\n\nSerbea\n\nSerbea is a “superset” of ERB which provides the same benefits as ERB but uses curly braces: {% %} or {{ }} and adds support for filters and render directives. Use the file extension .serb. Here’s an example of the above ERB code rewritten in Serbea:\n\n{% collections.posts.each do |post| %}\n  &lt;li&gt;&lt;a href=\"{{ post.relative_url }}\"&gt;{{ post.data.title }}&lt;/a&gt;&lt;/li&gt;\n{% end %}\n\n----\n\n{% paginator.each do |post| %}\n  &lt;li&gt;{{ post.data.title | link_to: post }}&lt;/li&gt;\n{% end %}\n\n\nNotice this is using the filter syntax similar to Liquid for link_to. You can use this kind of syntax with any helpers available in all Ruby templates, as well as methods on objects themselves. Examples:\n\n{{ resource.data.description | markdownify }}\n\n{{ resource.data.title | titleize }}\n\n{{ resource.data.tags | array_to_sentence_string: \"or\" }}\n\n{{ resource.data.upcase_me | upcase }} &lt;!-- in this case upcase is a method on the String object itself! --&gt;\n\n\n(Under the hood, a Ruby method’s first argument will be supplied with the value of the left-side of the pipe | operator, and subsequent arguments continue after that as you write the filter syntax.)\n\nFor Serbea code samples in Markdown, use the serb tag. And like ERB, you can escape using two percent signs:\n\nHere's·my·**Markdown**·file.\n\n```serb\nAnd·my·{%%= \"ERB·code·sample\" %}\n```\n\n\nSerbea also provides a raw helper just like Liquid for escaping Serbea code:\n\n\nProcess me! {% do_something %}\n\nDon't process me! {% raw %}{% do_something %}{% endraw %}\n\n\nThere’s a VS Code extension available for Serbea which includes syntax highlighting as well as commands to convert selected ERB syntax to Serbea, and even a Serbea + Markdown highlighter.\n\nFor details on HTML output safety, see below (Serbea and ERB differ slightly on how escaping is accomplished).\n\nDot Access Hashes\n\nData hashes support standard hash key access, but most of the time you can use “dot access” instead for a more familiar look. For example:\n\n&lt;%= post.data.title %&gt; (but &lt;%= post.data[:title] %&gt; or &lt;%= post.data[\"title\"] %&gt; also work)\n\n&lt;%= resource.data.author %&gt;\n\n&lt;%= site.data.authors.lakshmi.mastodon.handle %&gt;\n\n&lt;% # You can freely mix hash access and dot access: %&gt;\n\n&lt;%= site.data.authors[resource.data.author].github %&gt;\n\n\nPartials\n\nTo include a partial in your ERB template, add a _partials folder to your source folder, and save a partial starting with _ in the filename. Then you can reference it using the &lt;%= render \"filename\" %&gt; helper (or use the partial alias if you’re more comfortable with that). For example, if we were to move the footer above into a partial:\n\n&lt;!-- src/_partials/_author_footer.erb --&gt;\n&lt;footer&gt;Authored by &lt;%= site.data[:authors].first[:name] %&gt;&lt;/footer&gt;\n\n\n---\ntitle: I'm a page!\n---\n\n&lt;h1&gt;&lt;%= data.title %&gt;&lt;/h1&gt;\n\n&lt;p&gt;Welcome to &lt;%= Bridgetown.name %&gt;!&lt;/p&gt;\n\n&lt;%= render \"author_footer\" %&gt;\n\n\nYou can also pass variables to partials using either a locals hash or as keyword arguments:\n\n&lt;%= render \"some/partial\", key: \"value\", another_key: 123 %&gt;\n\n&lt;%= render \"some/partial\", locals: { key: \"value\", another_key: 123 } %&gt;\n\n\nAs an alternative to passing the partial filename as the first argument, you can supply a template keyword argument instead. This makes it easier to pass all arguments via a separate hash:\n\n&lt;% options = { template: \"mypartial\", title: \"Hello!\" } %&gt;\n&lt;%= partial **options %&gt;\n\n\nPartials also support capture blocks, which can then be referenced via the content local variable within the partial.\n\nRendering Ruby Components\n\nFor better encapsulation and reuse of Ruby-based templates as part of a “design system” for your site, we encourage you to write Ruby components using Bridgetown::Component. Check out the documentation and code examples here.\n\nLiquid Filters, Tags, and Components\n\nBridgetown includes access to some helpful Liquid filters as helpers within your ERB templates:\n\n&lt;!-- July 9th, 2020 --&gt;\n&lt;%= date_to_string site.time, \"ordinal\" %&gt;\n\n\nThese helpers are actually methods of the helper object which is an instance of Bridgetown::TemplateView::Helpers.\n\nA few Liquid tags are also available as helpers too, such as class_map and asset_path.\n\nIn addition to using Liquid helpers, you can also render Liquid components from within your ERB templates via the liquid_render helper.\n\n&lt;p&gt;\n  Rendering a component:\n  &lt;%= liquid_render \"test_component\", param: \"Liquid FTW!\" %&gt;\n&lt;/p&gt;\n\n\n&lt;!-- src/_components/test_component.liquid --&gt;\n&lt;p&gt;{{ param }}&lt;/p&gt;\n\n\nLayouts\n\nYou can add an .erb layout to the _layouts folder for use by resources even other layouts. You can freely mix ‘n’ match ERB layouts with Liquid-based documents and Liquid-based layouts with ERB documents.\n\nsrc/_layouts/testing.erb\n\n---\nlayout: default\nsomevalue: 123\n---\n\n&lt;h1&gt;&lt;%= data.title %&gt;&lt;/h1&gt;\n\n&lt;main&gt;An ERB layout! &lt;%= layout.name %&gt; / somevalue: &lt;%= layout.data.somevalue %&gt;&lt;/main&gt;\n\n&lt;%= yield %&gt;\n\n\nsrc/page.html\n---\nlayout: testing\n---\n\nA standard Liquid page. {{ resource.data.layout }}\n\n\nIf your layout or a layout partial needs to load your frontend assets, use the asset_path helper:\n\n&lt;link rel=\"stylesheet\" href=\"&lt;%= asset_path :css %&gt;\" /&gt;\n&lt;script src=\"&lt;%= asset_path :js %&gt;\" defer&gt;&lt;/script&gt;\n\n\nMarkdown\n\nTo embed Markdown within an ERB template, you can use a markdownify block:\n\n&lt;%= markdownify do %&gt;\n   ## I'm a header!\n\n   * Yay!\n   &lt;%= \"* Nifty!\" %&gt;\n&lt;% end %&gt;\n\n\nYou can also pass in any string variable as a method argument:\n\n&lt;%= markdownify some_string_var %&gt;\n\n\nExtensions and Permalinks\n\nSometimes you may want to output a file that doesn’t end in .html when published. Perhaps you want to create a JSON index of a collection, or a special XML feed. If you have familiarity with other Ruby site generators or frameworks, you might instinctively reach for the solution where you use a double extension, say, posts.json.erb to indicate the final extension (json) and the template type (erb).\n\nBridgetown doesn’t support double extensions but rather provides a couple of alternative mechanisms to specify your template engine of choice. The first option is to utilize the default ERB processing, so your posts.json file will be processed through ERB automatically as long as it includes the triple-dashes front matter.\n\nThe second option is to set the file’s permalink using front matter. Here’s an example of a posts.erb file using a custom permalink:\n\n---\npermalink: /posts.json\n---\n[\n  &lt;%\n    collections.posts.resources.each_with_index do |post, index|\n      last_item = index == collections.posts.resources.length - 1\n  %&gt;\n    {\n      \"title\": &lt;%= jsonify post.data.title.strip %&gt;,\n      \"url\": \"&lt;%= absolute_url post.url %&gt;\"&lt;%= \",\" unless last_item %&gt;\n    }\n  &lt;% end %&gt;\n]\n\n\nThis ensures the final relative URL will be /posts.json. (Of course you can also set the permalink to anything you want, regardless of the filename itself.)\n\nLink and URL Helpers\n\nThe link_to and url_for helpers let you create anchor tags which will link to any source page/document/static file (or any relative/absolute URL you pass in).\n\nTo link to source content, pass in a path to file in your src folder that translates to a published URL. For example, if you have a blog post saved at src/_posts/2020-10-29-my-nifty-article.md\n\n&lt;%= link_to \"Click me!\", \"_posts/2020-10-29-my-nifty-article.md\" %&gt;\n\n&lt;!-- output: --&gt;\n&lt;a href=\"/blog/my-nifty-article\"&gt;Click me!&lt;/a&gt;\n\n\nThe link_to helper uses url_for, so you can use that to get the url directly:\n\n&lt;% article_url = url_for(\"_posts/2020-10-29-my-nifty-article.md\") %&gt;\n\n\nNote that url_for is also aliased to link in order to provide compatibility with the link Liquid tag.\n\nYou can pass additional keyword arguments to link_to which will be translated to HTML attributes:\n\n&lt;%= link_to \"Join our livestream!\", \"_events/livestream.md\", class: \"event\", data_expire: \"2020-11-08\" %&gt;\n\n&lt;!-- output: --&gt;\n&lt;a href=\"/events/livestream\" class=\"event\" data-expire=\"2020-11-08\"&gt;Join our livestream!&lt;/a&gt;\n\n\nIn order to simplify more complex lists of HTML attributes you may also pass a hash as the value of one of the keyword arguments.  This will convert all pairs in the hash into HTML attributes and prepend each key in the hash with the keyword argument:\n\n&lt;%= link_to \"Join our livestream!\", \"_events/livestream.md\", data: { controller: \"testable\", action: \"testable#test\" } %&gt;\n\n&lt;!-- output: --&gt;\n&lt;a href=\"/events/livestream\" data-controller=\"testable\" data-action=\"testable#test\"&gt;Join our livestream!&lt;/a&gt;\n\n\nlink_to uses html_attributes under the hood to handle this conversation.\n\nYou can also pass relative or absolute URLs to link_to and they’ll pass-through to the anchor tag without change:\n\n&lt;%= link_to \"Visit Bridgetown\", \"https://www.bridgetownrb.com\" %&gt;\n\n\nFinally, if you pass a Ruby object (i.e., it responds to url), it will work as you’d expect:\n\n&lt;%= link_to \"My last page\", collections.pages.resources.last %&gt;\n\n&lt;!-- output: --&gt;\n&lt;a href=\"/this/is/my-last-page\"&gt;My last page&lt;/a&gt;\n\n\nSlotted Content\n\nYou can contain portions of content in a template file (whether for pages, layouts, or another resources) within “slots”. These content slots can then be rendered higher up the rendering pipeline. For example, a resource can define a slot, and its layout can render it. Or a layout itself can define a slot and its parent layout can render it. You can render slots within partials as well.\n\nBridgetown’s Ruby components also has its own slotting mechanism.\n\nHere’s an example of using slots in ERB templates to relocate page-specific styles up to the HTML &lt;head&gt;.\n\nIn your src/_partials/head.erb file, append the following:\n\n&lt;%= slotted :html_head %&gt;\n\n\nThen on one of your ERB pages, try adding something like:\n\n&lt;% slot :html_head do %&gt;\n  &lt;style&gt;\n    h1 {\n      color: navy;\n    }\n  &lt;/style&gt;\n&lt;% end %&gt;\n\n\nYou’ll then be able to verify that the new style tag only gets rendered out in &lt;head&gt; for the particular page where the slot is provided.\n\nSlotted content will automatically adhere to the format of the context where slot is called. In other words, if you’re in a Markdown file, the slotted content will also be converted from Markdown to HTML. (Additional converter plugins will need to opt-in to support this feature.) To disable this functionality, pass transform: false.\n\nThe slotted helper can also provide default content should the slot not already be defined:\n\n&lt;%= slotted :aside do %&gt;\n  &lt;p&gt;This only displays if there's no \"aside\" slot defined.&lt;/p&gt;\n&lt;% end %&gt;\n\n\nMultiple captures using the same slot name will be cumulative. The above aside slot could be appended to by calling slot :aside multiple times. If you wish to change this behavior, you can pass replace: true as a keyword argument to slot to clear any previous slot content. Use with extreme caution!\n\nFor more control over slot content, you can use the pre_render and post_render hooks. Builders can register hooks to transform slots in specific ways based on their name or context:\n\nclass Builders::BeamMeUpSlotty &lt; SiteBuilder\n  def build\n    hook :slots, :pre_render do |slot|\n      slot.content.upcase! if slot.name == \"upcase_me\"\n    end\n  end\nend\n\n\nWithin the hook, you can call slot.context to access the definition context for that slot (a resource, a layout, etc.).\n\n\n  \n  Both slot and slotted accept an argument instead of a block for content. So you could call &lt;% slot :slotname, \"Here's some content\" %&gt; rather than supplying a block, or even pass in something like front matter data!\n\n\n\n  \n  Don’t let the naming fool you…Bridgetown’s slotted content feature is not related to the concept of slots in custom elements and shadow DOM (aka web components). But there are some surface-level similarities. Many view-related frameworks provide some notion of slots (perhaps called something else like content or layout blocks), as it’s helpful to be able to render named “child” content within “parent” views. If you’re looking for information on using actual HTML slots, check out our new Declarative Shadow DOM documentation.\n\n\nOther HTML Helpers\n\nhtml_attributes\n\nhtml_attributes is a helper provided by Streamlined, but you can use it in any Ruby template type. It allows you to pass a hash and have it converted to a string of HTML attributes:\n&lt;p &lt;%= html_attributes({ class: \"my-class\", id: \"some-id\" }) %&gt;&gt;Hello, World!&lt;/p&gt;\n\n&lt;!-- output: --&gt;\n&lt;p class=\"my-class\" id=\"some-id\"&gt;Hello, World!&lt;/p&gt;\n\nhtml_attributes also allows for any value of the passed hash to itself be a hash. This will result in individual attributes being created from each pair in the hash. When doing this, the key the hash was paired with will be prepended to each attribute name:\n&lt;button &lt;%= html_attributes({ data: { controller: \"clickable\", action: \"click-&gt;clickable#test\" } }) %&gt;&gt;Click Me!&lt;/button&gt;\n\n&lt;!-- output: --&gt;\n&lt;button data-controller=\"clickable\" data-action=\"click-&gt;clickable#test\"&gt;Click Me!&lt;/button&gt;\n\n\ncapture\n\nIf you need to capture a part of your template and store it in a variable for later use, you can use the capture helper.\n\n&lt;% test_capturing = capture do %&gt;\n  This is how &lt;%= \"#{\"cap\"}turing\" %&gt; works!\n&lt;% end %&gt;\n\n&lt;%= test_capturing.reverse %&gt;\n\n\nOne interesting use case for capturing is you could assign the captured text to a layout data variable. Using memoization, you could calculate an expensive bit of template once and then reuse it either in that layout or in a partial.\n\nExample:\n\n&lt;% # add this code to a layout: %&gt;\n&lt;% layout.data[:save_this_for_later] ||= capture do\n  puts \"saving this into the layout!\"\n%&gt;An &lt;%= \"expensive \" + \"routine\" %&gt; to be saved&lt;% end %&gt;\n\nSome text...\n\n&lt;%= partial \"use_the_saved_variable\" %&gt;\n\n\n&lt;% # src/_partials/_use_the_saved_variable.erb %&gt;\nPrint this: &lt;%= layout.data[:save_this_for_later] %&gt;\n\n\nBecause of the use of the ||= operator, you’ll only see “saving this into the layout!” print to the console once when the site builds even if you use the layout on thousands of pages!\n\nCustom Helpers\n\nIf you’d like to add your own custom template helpers, you can use the helper DSL within builder plugins. Read this documentation to learn more.\n\nAlternatively, you could open up the Helpers class and define additional methods:\n\n# plugins/site_builder.rb\n\nBridgetown::TemplateView::Helpers.class_eval do\n  def uppercase_string(input)\n    input.upcase\n  end\nend\n\n\n&lt;%= uppercase_string \"i'm a string\" %&gt;\n\n&lt;!-- output: --&gt;\nI'M A STRING\n\n\nAs a best practice, it would be best to define your helpers as methods of a dedicated Module which could then be used for both Liquid filters and ERB helpers simultaneously. Here’s how you might go about that in your plugin:\n\n# plugins/filters.rb\n\nmodule MyFilters\n  def lowercase_string(input)\n    input.downcase\n  end\nend\n\nLiquid::Template.register_filter MyFilters\nBridgetown::TemplateView::Helpers.include MyFilters\n\n\nAnd at the call site:\n\n&lt;%= lowercase_string \"WAY DOWN LOW\" %&gt;\n\n\n{{ \"WAY DOWN LOW\" | lowercase_string }}\n\n\nEscaping and HTML Safety\n\nThe ERB template engine uses a safe output buffer—the same one used in Rails.\n\nThat means that you’ll sometimes find that if you output a front matter variable or some other string value that contains HTML tags and entities, the string will be “escaped” so that the actual angle brackets and so forth are displayed in the website content (rather than being interpreted as valid HTML tags).\n\nOften that’s the right call for security purposes to avoid XSS attacks or to bypass potential markup errors. However, to explicitly mark a string as safe, you can use the html_safe method. Bridgetown provides the raw or safe helpers as well. You can also use a double-equals sign to bypass escaping entirely.\n\n&lt;%= some_value.html_safe %&gt;\n&lt;!-- or --&gt;\n&lt;%= raw some_value %&gt;\n&lt;!-- or --&gt;\n&lt;%= safe some_value %&gt;\n&lt;-- or --&gt;\n&lt;%== some_value %&gt;\n\n\nNote that using html_safe directly requires the value to be a string already. If you use the raw/safe helpers, it will first perform to_s automatically. Also bear in mind that &lt;%= yield %&gt; or &lt;%= content %&gt; or rendering components/partials won’t perform escaping on the rendered template output. (This is for obvious reasons—otherwise you’d get a visual mess of escaped HTML tags.)\n\nIf you find a particular use case where escaping occurs (or doesn’t occur) in an unexpected manner, please file a bug report in the Bridgetown GitHub repo.\n\nWhen Using Serbea\n\nSerbea only escapes values by default when using the double-braces syntax {{ }}. When using {%= %}, escaping does not occur by default.\n\nstr = \"&lt;p&gt;Escape me!&lt;/p&gt;\"\n\n{{ str }} &lt;!-- output: &amp;lt;p&amp;gt;Escape me!&amp;lt;/p&amp;gt; --&gt;\n{%= str %} &lt;!-- output: &lt;p&gt;Escape me!&lt;/p&gt; --&gt;\n\n\nTo explicitly escape a value when using percent signs, use the escape or h helper. To explicitly mark a value as safe when using double-braces, use the safe or raw filter:\n\nstr = \"&lt;p&gt;Escape me!&lt;/p&gt;\"\n\n{{ str | safe }} &lt;!-- output: &lt;p&gt;Escape me!&lt;/p&gt; --&gt;\n{%= escape(str) %} &lt;!-- output: &amp;lt;p&amp;gt;Escape me!&amp;lt;/p&amp;gt; --&gt;\n\n\nStreamlined\n\nStreamlined is a new Ruby template type introduced in Bridgetown 2.0. It allow you to embed HTML templates in pure Ruby code using “squiggly heredocs” along with a set of helpers to ensure output safety (proper escaping) and easier interplay between HTML markup and Ruby code. And on average it executes at 1.5x the speed of ERB, making it a good performance choice for large builds.\n\nYou can use Streamlined directly in resources saved as pure Ruby (.rb), as well as in Bridgetown components. Here’s an example of what that looks like:\n\nclass SectionComponent &lt; Bridgetown::Component\n  def initialize(variant:, heading:, **options)\n    @variant, @heading, @options = variant, heading, options\n  end\n\n  def heading\n    &lt;&lt;~HTML\n      &lt;h3&gt;#{text -&gt; { @heading }}&lt;/h3&gt;\n    HTML\n  end\n\n  def template\n    html -&gt; { &lt;&lt;~HTML\n      &lt;section #{html_attributes(variant:, **@options)}&gt;\n        #{html -&gt; { heading }}\n        &lt;section-body&gt;\n          #{html -&gt; { content }}\n        &lt;/section-body&gt;\n      &lt;/section&gt;\n    HTML\n    }\n  end\nend\n\n\nA few things going on here:\n\n\n  The template method is a standard part of Bridgetown’s component system, and here it’s being leveraged to render HTML via Streamlined.\n  The html method’s only argument is a stabby lambda (-&gt;) which in term contains a squiggly heredoc. (Isn’t Ruby terminology fun? 😅)\n  Within the heredoc, there’s a use of the html_attributes helper to convert keyword arguments/hashes into HTML attributes, along with additional embeds of Ruby utilizing html.\n  On top of that, we’re able to break our overall template up by defining a “partial” elsewhere (the heading method). Calling out to other Ruby methods to incrementally build up HTML markup is a key feature of Streamlined, and offers a DX reminiscent of JavaScript’s tagged template literals or JSX.\n  The text method escapes all values unless they’ve been explicitly marked as “HTML safe”, whereas html simply outputs values without preemptive escaping. This requires the template author to think clearly about escaping rules. Default to always using text unless you know you’re outputting vetted HTML code.\n\n\nBeyond these patterns, Streamlined has another couple tricks up its sleeve. You can break up template code into multiple render passes and also render external components:\n\ndef template\n  render html -&gt; { &lt;&lt;~HTML\n    &lt;p&gt;I am HTML markup.&lt;/p&gt;\n  HTML\n  }\n\n  render AnotherComponent.new if @should_render_this\n\n  render html -&gt;{ &lt;&lt;~HTML\n    &lt;p&gt;I am more HTML markup.&lt;/p&gt;\n  HTML\n  }\nend\n\n\nYou can even embed rendering logic inside of render blocks:\n\ndef template\n  render html -&gt; { &lt;&lt;~HTML\n    &lt;p&gt;I am HTML markup.&lt;/p&gt;\n  HTML\n  }\n\n  render do\n    render AnotherComponent.new\n\n    render html -&gt;{ &lt;&lt;~HTML\n      &lt;p&gt;I am more HTML markup.&lt;/p&gt;\n    HTML\n    }\n  end if @should_render_more_stuff\nend\n\n\nRendering blocks can be nested as well. It’s all part of allowing your markup generation to become more modular.\n\nLoop over an array or hash within a heredoc with the html_map helper:\n\ndef template\n  html -&gt; { &lt;&lt;~HTML\n    &lt;ul&gt;#{\n      html_map(@items) do |item|\n        &lt;&lt;~HTML\n          &lt;li&gt;#{text -&gt; { item }}&lt;/li&gt;\n        HTML\n      end\n    }&lt;/ul&gt;\n  HTML\n  }\nend\n\n\n\n  \n  While Ruby heredocs can use any uppercase text as delimiters, Streamlined requires you to use HTML. It’s helpful for syntax highlighting in many code editors, and it’s also relevant to linting as explained below.\n\n\nEnforcing Streamlined helpers using Rubocop\n\nStreamlined provides a Rubocop linter to make sure template authors are utilizing the text, html, etc. helpers in HTML heredocs, as well as aid with other aspects of your Bridgetown project’s Ruby code.\n\nWhen you install https://github.com/bridgetownrb/rubocop-bridgetown, it will automatically detect any heredoc starting with &lt;&lt;~HTML and warn you if you aren’t utilizing the Streamlined helpers. This will ensure you don’t accidentally output raw HTML (a potential security risk) unless you really mean it.\n\n\n  \n  Q: Why does Streamlined rely on heredocs which are actually just strings? Why doesn’t Streamlined use a special Ruby DSL for generating HTML similar to other tools like Phlex, Papercraft, or Arbre?\n\nA: Many of us prefer writing HTML syntax—and beyond that, the value of using a template system which is fully compatible with the vast ecosystem of HTML on the web cannot be overstated. Also as mentioned previously, Streamlined represents an effort to approximate JavaScript’s “tagged template literals” in Ruby—an experience already appealing to many frontend developers.\n\n\nUniversal Rendering\n\nNew in Bridgetown 2.1, you have the ability to render partials in template languages other than the one calling the render function. For example, in an ERB page layout you could render a Serbea partial. Or in a Markdown resource with a site configured to use Serbea by default, you could render a pure Ruby partial.\n\nIn addition, you can render both partials and components outside of any view context by calling the render class method of TemplateView. For instance, you could pull up a Bridgetown console and type in the following:\n\nBridgetown::TemplateView.render(\"path/to/partial\", my_var: \"it works!\")\n# or:\nBridgetown::TemplateView.render(MyRubyComponent.new(param1: 123))\n\n\nPartials &amp; components rendered in this manner use a “virtual” resource under-the-hood as part of the view context. If you need to provide front matter data to that resource in order for a partial or component to render as desired, use new_with_data:\n\nBridgetown::TemplateView.new_with_data(title: \"Here's a title!\").render(\"path/to/partial\")\n\n\nYou can also provide a virtual path for use by URL helpers:\n\nBridgetown::TemplateView.new_with_data(\"path/to/page\", title: \"Page title\", description: \"...\")"
        },
        {
          "id": "docs-template-engines-liquid",
          "title": "Liquid",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "template-engines",
          "tags": "",
          "url": "/docs/template-engines/liquid",
          "content": "Bridgetown provides comprehensive support for rich presentation of data and design via the Liquid template language, available within documents as well as layouts and components. If you find that Liquid doesn&#8217;t suit your needs when building your site templates, Bridgetown also allows for using ERB. You can mix and match ERB and Liquid templates freely throughout your site.\n\nGenerally in Liquid you output content using two curly braces e.g.\n{{ variable }} and perform logic statements by\nsurrounding them in a curly brace percentage sign e.g.\n{% if statement %}. To learn more about Liquid, check\nout the official Liquid Documentation.\n\nThe ability to use Liquid within Markdown in posts and pages allows for advanced customization of your content pipeline. For example, you can write custom plugins to supply new Liquid tags and filters and use them throughout your site.\n\nIn addition to Liquid&#8217;s standard suite of filters and tags, Bridgetown provides a number of useful additions to help you build your site:\n\n\n  \n    \n      Filters List\n      \n    \n  \n  \n    \n      Tags List"
        },
        {
          "id": "docs",
          "title": "Getting Started",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "intro",
          "tags": "",
          "url": "/docs",
          "content": "Excited to get started? Woohoo! In case you’re wondering, Bridgetown is a progressive site generator. You add content written in an author-friendly markup language like Markdown, alongside layouts and components using template syntax such as ERB, and Bridgetown will then compile HTML, CSS, and JavaScript to an output website folder. You can tweak exactly how you want the pages to look, what data gets displayed on the site, and more. Bridgetown is powered by the Ruby programming language, as well as Node for JavaScript-based processing of your frontend assets. Bridgetown started life as a Jekyll fork in early 2020, but it has since grown into so much more.\n\nWe’ll explain much more about what Bridgetown is and what it can do for you in the sections ahead. Let’s go!\n\nQuick Instructions\n\n\n  \n  If you like testing the latest and greatest, switch to our edge documentation.\n\n\n\n  \n  Upgrading from an earlier version? Read our upgrade guide here.\n\n\nRead the requirements for more information on what you’ll need to have set up in advance, primarily Ruby and Node. Then:\n\n\n  Install Bridgetown and related gems:\n    gem install bridgetown -N\n    \n  \n  Create a new Bridgetown site at ./mysite.\n    bridgetown new mysite\n    \n  \n  Change into your new directory.\n    cd mysite\n    \n  \n  Build the site and run a live-reload development server:\n    bin/bridgetown start\n    \n  \n  \n    Browse to http://localhost:4000\n  \n  And you’re done! (That’s the goal at least 😊)\n\n\n\n  \n  Detailed installation instructions for macOS, Ubuntu Linux, Fedora Linux and Windows 10 are available here.\n\n\n\n  \n  Read about additional options available via new here.\n\n\n\n  \n  Still stuck? Please reach out to the Bridgetown community for support. What might take you three hours to eventually figure out could take a mere 10 minutes with the right pointers!\n\n\nBridgetown comes with the bridgetown CLI tool as well as a few Rake tasks and NPM scripts,\nso be sure to read up on the command line usage documentation.\n\nMore About the Tech Specs\n\nBridgetown is sometimes called a “static site generator” or a “Jamstack” web framework. We think it’s simpler to think in terms of progressive generation — the idea that the moment at which your over-the-wire HTML &amp; JSON is generated can vary, depending on the method you choose to use on a route-by-route basis as well as the architecture of your frontend. Bridgetown starts off in SSG (Static Site Generation) mode, and you can opt-into SSR (Server-Side Rendering) mode only if and when you need it. And depending on your choice of frontend tooling, you can leverage CSR (Client-Side Rendering) along with hydration techniques to add highly dynamic and interactive experiences—without compromising on base site speed and efficiency.\n\nBridgetown works best as part of a version-controlled repository powered by Git. You’ll likely want to store your repository on a service like Codeberg or GitHub so that you and everyone else working on the website (plus your hosting provider) all have direct, secure access to the latest website content and design files.\n\nDuring the development process, you’ll run Bridgetown from the command line on your local development machine (or perhaps a remote staging server). Once content is ready to publish, you’ll want to commit your website codebase to the Git repository and use an automated build tool to generate and upload the final output to a server or CDN (Content Delivery Network). Statichost.eu and Render are popular services for this, but there are many others. You can also copy the generated files contained in the output folder to any HTTP web server and it should Just Work. 😊\n\nFor more details on how the Bridgetown build process works and what goes into creating a site, continue on to read our Core Concepts guide."
        },
        {
          "id": "docs-core-concepts",
          "title": "Core Concepts",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "core_concepts",
          "tags": "",
          "url": "/docs/core-concepts",
          "content": "We’ve made it as easy as we could to get started with Bridgetown, but it helps to have a basic understanding of a few key aspects of the site build process so you know which tools to use for the right job. Websites using Bridgetown are built and deployed as atomic artifacts, but they can optionally provide dynamic routes via a secondary server process. This means the website your visitors will ultimately engage with was largely produced as a “snapshot in time”—the product of Bridgetown’s build process. How does that process work? Let’s find out!\n\nThe Build Process\n\nThere’s a relatively linear process which occurs every time you run a build command:\n\n\n  First, Bridgetown loads its internal Ruby APIs as well as any Ruby gems specified in the bridgetown-plugins group of your Gemfile.\n  Next, Bridgetown looks for a configuration file in your current working directory and uses that to instantiate a site object.\n  After loading the configuration, Bridgetown prepares the site object for loading the various types of content in your site repository, starting with custom plugins in the plugins folder.\n  Plugins are then granted the ability to generate new content programmatically and define other features such as Liquid tags or Ruby helpers (aka “shortcodes”). This is the point when you’d create blog posts and other resources from data provided by an external API for example.\n  Once plugins (if any) have loaded, Bridgetown starts systematically reading in files from the source folder (typically src):\n    \n      Layouts\n      Components\n      Data files\n      Static files\n      Resources\n      Any gem-based plugins which supply their own layouts, components, and content via Source Manifests.\n    \n  \n  Once all of the data structures for the entire website are in place, Bridgetown renders all relevant content objects to prepare them for final output. This is when Front Matter variables are made available to templates, any Liquid or Ruby templates are processed, formats like Markdown are converted to HTML, resources are placed within layout templates, and generally everything is finalized in its proper output format (HTML, JSON, images, PDFs, etc.).\n  The final step is to write everything to the destination folder (typically output). If all has gone well, that folder will contain a complete, fully-functioning website which can be deployed to any basic HTTP web server.\n\n\nThere’s also a second sort of build process which occurs only in development when you run the server with the bin/bridgetown start command, called fast refresh. This is a new feature in Bridgetown 2.0. Prior to this, every time you would change a file (update a blog post, edit a template, replace an image file, fix a bug in a custom plugin, etc.), that entire build process would be run through again. As you can imagine, for really large sites with thousands of pages, build processes can slow down substantially.\n\nFast refresh uses a pair of techniques called signals and effects to track changes to individuals files and the ways in which data can flow across multiple files. In not all, but in most cases, this results in a much faster rebuild time. If you edit just a single resource file, it’s likely only that one resource will get rebuilt. If you edit a data file referenced on several pages using site.signals, those those several pages will get rebuilt. Fast refresh also tracks access to components within templates. Read our original announcement blog post for a deep dive into this functionality.\n\n\n  \n  If you find you are having issues with fast refresh in development, you can set fast_refresh false in your config/initializers.rb file. We also encourage you to submit a bug report if you can reproduce a particular sequence of events where it&#8217;s not working.\n\n\nThe Frontend Build Process\n\nThere’s one aspect of the build process overlooked above: the compiling,\ncompressing, and bundling of frontend assets like\nJavaScript, CSS, web fonts, and so forth.\n\nWhen using Bridgetown’s built-in start or deploy commands,\nessentially two build processes are kicked off: the frontend build process (using esbuild) and the Bridgetown build process. The two align when something magical happens.\n\n\n  esbuild will conclude its build process by exporting a manifest.json file to the hidden .bridgetown-cache folder. This manifest lists the exact, fingerprinted filenames of the compiled and bundled JS and CSS output files.\n  Bridgetown, using the asset_path Liquid tag/Ruby helper, monitors that manifest, and whenever it detects a change it will regenerate the site to point to those bundled output files.\n  This way, your website frontend and the HTML of your generated static site are always kept in sync.\n\n\nAdding Extra Features to Your Site\n\nIn addition to the work you do yourself to code, design, and publish your website, there are ways you can enhance your site by installing third-party plugins or applying automations. These may provide new features, themes, or software configurations in useful ways. Some examples:\n\n\n  Add instant search to your site with the bridgetown-quick-search plugin\n  Include inline SVG images with the bridgetown-svg-inliner plugin\n\n\nYou can discover links to these and many more in our Plugins directory.\n\nServer-Side Rendering and Dynamic Routes\n\nFor most content-rich websites intended for marketing, educational, or publishing purposes (blogs, etc.), a statically-built and deployed site may be all you need. But there may be times when you need a real backend running for your site, either to provide API endpoints your principal pages can communicate with via JavaScript, or to offer actual routes that are fully SSR’d.\n\nBridgetown provides a full SSR pipeline powered by the Roda web toolkit. Roda, like Rails or Sinatra, takes full advantage of Ruby’s Rack ecosystem and offers a minimalist yet elegant DSL for defining and handling routes via a “routing tree” as well as processing request/response cycles. Accepting form data or JSON payloads is a snap, and there’s even a core plugin you can configure to enable dynamic, file-based routing with all of Bridgetown’s template engines and component rendering at your disposal.\n\nWhat to Learn Next\n\nThere is detailed documentation available about each and every step mentioned\nabove, so feel free to poke around and read up on the topics which interest you the\nmost. And as always, if you get stuck or have follow-up questions, hop in one\nof our community channels and a friendly Bridgetowner will\nendeavor to help you out!"
        },
        {
          "id": "docs-migrating-from",
          "title": "Migrating from…?",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "migrating",
          "tags": "",
          "url": "/docs/migrating-from",
          "content": "Wondering how you can migrate to Bridgetown from another site builder or framework? We’ve begun to fill this page out with various examples…and the first one is none other than Jekyll!\n\nJekyll\n\nBridgetown started out as a fork of Jekyll in early 2020, and since then has evolved rapidly to offer a plethora of new features and capabilities. Nearly 800 commits later from 33 contributors and counting, Bridgetown is powering websites deployed all around the world and has helped usher new developers into the Ruby fold. We are filled with gratitude that Bridgetown is generously sponsored by dozens of individual supporters on GitHub and is ranked 4th in general-purpose Ruby-based site generators by GitHub stars.\n\nAs an example of our progress since the fork, here’s a rundown of 40 features Bridgetown has implemented, with more on the way…\n\nReady to migrate to Bridgetown? Here’s an overview guide of the steps you’ll want to take."
        },
        {
          "id": "docs-philosophy",
          "title": "Our Philosophy",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "philosophy",
          "tags": "",
          "url": "/docs/philosophy",
          "content": "Every successful open source project has a certain “vibe”, those thought patterns and behaviors which ensure meaningful, constant progress forward and the growth of a vibrant developer community. We feel it’s important to lead with a set of basic principles that will help guide the project long into the future.\n\n(This page is lengthy, so feel free to skip over to the next section of documentation!)\n\nTable of Contents\n\n  Core Principles\n  A Brief History of Bridgetown\n\n\nCore Principles\n\n\n  \n    Move fast but try really hard not to break things. Most developer-focused software projects err on a side…either the side of extreme backwards-compatibility with past versions, or the side of evolving quickly and requiring devs to go through multiple rounds of “yak shaving” when upgrading to new versions.\n\n    We hope to strike a thoughtful balance between those two extremes. We don’t want to break stuff or change the setup process just for the heck of it. But we also don’t want to be constrained by past problematic decisions which reduce the quality of the software. History has proven many times over that open source projects which fail to keep pace with the times and new trends in software eventually wither and die. This is a fate we wish to avoid!\n  \n  \n    Embrace the backpack analogy. We recognize Bridgetown can’t be all things to all people. Bloatware isn’t good for anybody. However, we do believe that it’s important to provide a curated “backpack” of tools ready to go that can help you build most websites most of the time. We want Bridgetown to come with everything you need to get started building great websites.\n  \n  \n    Convention over configuration. We strongly believe Bridgetown should encourage powerful defaults and best-practice conventions to give website developments an instant leg up as they start new projects. If you have to go fishing for a bunch of extra plugins and add a slew of extra libraries and reconfigure settings just to complete basic setup tasks, we’re doing it wrong.\n  \n  \n    Be a leader in Jamstack-style technology (without being constrained by it). Bridgetown’s progenitor (Jekyll) played a significant role in kicking off the modern explosion of the “Jamstack” due to its static generation bona fides. In fact, there might not be a Jamstack today if Jekyll’s popularity as the technology powering GitHub Pages hadn’t caught fire in the early 2010s. Our sincere wish is that Bridgetown would play a unique and vital role in the continued expansion of this exciting way of building and deploying websites, while also identifying and correcting ways we feel the Jamstack space has strayed too far from its inaugural mission. (For instance, we’re skeptical of building complex fullstack applications using serverless functions. It’s a solution in search of a problem not everyone has, and it’s often promoted by the very hosting companies who benefit from increased usage of serverless functions because they offer no alternative. Buyer beware!)\n  \n\n\nA Brief History of Bridgetown\n\nBridgetown started life as a fork of the granddaddy of static site generators, Jekyll. Jekyll came to prominence in the early 2010s due to its slick integration with GitHub, powering thousands of websites for developer tools. In the years since it has grown to provide a popular foundation for a wide variety of sites across the web.\n\nBut as the concepts of modern static site generation and the “Jamstack” came to the forefront, a whole new generation of tools rose up, like Hugo, Eleventy, Next.js, and many more. In the face of all this new competition, Jekyll chose to focus on maintaining extensive backwards-compatibility and a paired-down feature set—noble goals for an open source project generally speaking, but ones that were at odds with meaningful portions of the web developer community.\n\nSo in March 2020, Portland-based web studio Whitefusion started on Bridgetown, a fork of Jekyll with a brand new set of project goals and a future roadmap. Whitefusion’s multi-year experience producing and deploying numerous Jekyll-based websites furnishes a seasoned take on the unique needs of web agencies and their clients. Since that time, we’ve seen this strategy pay off in a big way.\n\nBridgetown has grown considerably since its inception, but in many ways, we’re just getting started. We hope you join our community, build something awesome with Bridgetown, and share it with the world!"
        },
        {
          "id": "docs-installation",
          "title": "Installation Guides",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "installation-guides",
          "tags": "",
          "url": "/docs/installation",
          "content": "Bridgetown is software written in Ruby, a friendly programming language that maximizes programmer happiness and makes it easy to build and customize open source projects. You will need to install Bridgetown as a Gem after you set up your Ruby language environment. You will also need to install Node to manage your website&#8217;s frontend assets.\n\n\n  \n  For a quick summary of how to install Bridgetown, read Getting Started. What follows are more in-depth guides to setting up your developer or server environments.\n\n\nRequirements\n\nIf you don&#8217;t have some or all of these tools, our setup guides for macOS,\nUbuntu Linux, and Ubuntu for Windows will help you install them.\n\n\n  \n    GCC and Make\n(which you can check by running gcc -v,g++ -v  and make -v).\n  \n  \n    Ruby version\n3.3 or above (ruby version can be checked by\nrunning ruby -v)\n  \n  \n    Node version 22.21 or\nabove (which you can check by running node -v)\n  \n\n\nGuides\n\nFor detailed installation instructions, take a look at the guide for your operating\nsystem:\n\n\n  macOS\n  Fedora Linux\n  Ubuntu Linux\n  Windows (via Linux Subsystem + Ubuntu)\n\n\nGem Servers\n\nBy default, Bundler will install gems from the RubyGems service. You can configure your Bridgetown project post-install to bundle via the alternative community gem server gem.coop by modifying the source at the top of your project&#8217;s Gemfile:\n\nsource \"https://gem.coop\"\n\n\nIn addition, you also have the option of loading first-party Bridgetown gems directly from our own canonical gem server, gems.bridgetownrb.com. For example:\n\nsource \"https://gems.bridgetownrb.com\" do\n  gem \"bridgetown\"\n  gem \"bridgetown-feed\"\nend\n\n\nThe list of available gems is provided at the link.\n\nUpgrading?\n\nWe now have an official upgrade guide for migrating your Bridgetown website to v2.x."
        },
        {
          "id": "docs-first-steps-for-newbies",
          "title": "First Steps for Newbies",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "first-steps",
          "tags": "",
          "url": "/docs/first-steps-for-newbies",
          "content": "You have just installed Bridgetown by following the Getting Started guide. Great! Now what?\n\nIf you are not familiar with other web frameworks, your brand new site may seem confusing. Head to our explanation to discover what all these files and folders are about!\n\nFirst posts and pages\n\nIf you want a &#8220;typical&#8221;, simple blog, open the src folder and identify:\n\n  the _posts folder, which will result in the posts collection when you add files in the format YYY-MM-DD-title.md. These will follow the layout post.erb contained in the _layouts folder.\n  the index.md, about.md and posts.md pages, which will follow the layout page.erb contained in the _layouts folder.\n\n\nFor now, index.md and about.md are static pages, respectively a home page and an about page. You can edit their content to suit your blog.\n\nposts.md contains an example of how to access the items in a collection.\n\n&lt;ul&gt;\n  &lt;% collections.posts.each do |post| %&gt;\n    &lt;li&gt;\n      &lt;a href=\"&lt;%= post.relative_url %&gt;\"&gt;&lt;%= post.data.title %&gt;&lt;/a&gt;\n    &lt;/li&gt;\n  &lt;% end %&gt;\n&lt;/ul&gt;\n\n\nIn this case, it will list the titles of all your posts, with links to the corresponding posts. You can also access other properties of your posts. For instance, post.data.date will give you the date.\n\nThis metadata is as versatile as you need it to be! You can create custom metadata in the front matter and access it in the same way with post.data.order or post.data.category.\n\n---\norder: 45\ntitle: First Steps for Newbies\ntop_section: Setup\ndescription: If Bridgetown is your first web framework\ncategory: first-steps\n---\n\n\n\n  \n  If all posts need a specific bit of metadata by default (known as front matter defaults), it is best to add it in a _defaults.yml file in your _posts directory.\n\n\nA custom collection\n\nIf you want to create a custom collection (let&#8217;s say &#8220;Docs&#8221; for your documentation), you will need to initialize it in the config/initializers.rb file. (See detailed instructions here.)\n\nBridgetown.configure do |config|\n  collections do\n    docs do\n      output true\n    end\n  end\nend\n\n\nThen, create a _docs folder under the src folder, and add your documentation files in it. You can use an existing layout, or create a custom layout in the layouts folder.\n\nAdd a docs.md file under the src folder to create a collection page similar to the posts.md page. You can create a list of docs, filtered by metadata, by accessing collections.docs.each do |doc|, just like for the posts above.\n\nEdit the components/shared/navbar.erb file to add your new collection to the navigation bar (you can follow the format used for pages and posts).\n\n&lt;nav&gt;\n  &lt;ul&gt;\n    &lt;li&gt;&lt;a href=\"&lt;%= relative_url '/' %&gt;\"&gt;Home&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"&lt;%= relative_url '/about' %&gt;\"&gt;About&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"&lt;%= relative_url '/posts' %&gt;\"&gt;Posts&lt;/a&gt;&lt;/li&gt;\n    &lt;li&gt;&lt;a href=\"&lt;%= relative_url '/docs' %&gt;\"&gt;Docs&lt;/a&gt;&lt;/li&gt;\n  &lt;/ul&gt;\n&lt;/nav&gt;\n\n\nNow, you can add your content, either as a page, a post, or an item in a custom collection. But how will it look?\n\nLooking good\n\nBridgetown comes with a decent default theme. You can edit the CSS directly in frontend/styles/index.css.\n\nYou can also search the web for CSS themes, and either place the contents of the theme&#8217;s CSS file in index.css, or follow the theme&#8217;s installation instructions (for instance pico)\n\nWhat next?\n\nYou now have a basic Bridgetown site set up, and you can focus on what matters most: your content.\n\nTo make your site more accessible on the Web, consider adding the SEO and Feed bundled configurations (instructions in the links).\n\nWhen you&#8217;re ready to publish your site, head over to our deployment guide."
        },
        {
          "id": "docs-bundled-configurations",
          "title": "Bundled Configurations",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "bundledconfigurations",
          "tags": "",
          "url": "/docs/bundled-configurations",
          "content": "Bridgetown bundles a number of automation scripts to set up common project\nconfigurations. You can run these scripts using bin/bridgetown\nconfigure [CONFIGURATION]\n\nThe configurations we include are:\n\n\n  SEO (seo)\n  Feed (RSS-like) (feed)\n  Web Awesome (webawesome)\n  Automated Test Suite using Minitest (minitesting)\n  Open Props (open-props)\n  Lit (lit)\n  Bridgetown recommended PostCSS plugins (bt-postcss)\n  PurgeCSS Post-Build Hook (purgecss)\n  Render YAML Configuration (render)\n  Netlify TOML Configuration (netlify)\n  GitHub Pages Configuration (gh-pages)\n  Cypress (cypress)\n\n\nThe full list of configurations can also be seen by running bridgetown configure without arguments.\n\nBundled configurations can also be run while creating a new Bridgetown project using the --configure= or -c flag and passing in a comma-separated list of configurations.\n\n\n  \n  Looking for Tailwind?\n\nThe bundled configuration for TailwindCSS has been relocated to a separate community-maintained repo. The Bridgetown core team recommends looking into options such as Open Props, Web Awesome, and otherwise &#8220;vanilla&#8221; CSS (perhaps with a bit of help from PostCSS) as a best practice for &#8220;Use the Platform&#8221;, future-compatible frontend development.\n\n\n\n  \n  Looking for Turbo or Stimulus?\n\nThe bundled configurations for Turbo &amp; Stimulus have been relocated to a separate community-maintained repo.\n\n\nConfiguration Setup Details\n\nSEO\n\n🔍 Adds metadata tags for search engines and social networks to better index and display your site’s content. Check out the gem readme for more info and configuration options.\n\n🛠 Configure using:\n\nbin/bridgetown configure seo\n\n\nFeed\n\n🍽️ Generate an Atom (RSS-like) feed of your Bridgetown posts and other collection documents. Check out the gem readme for more info and configuration options.\n\n🛠 Configure using:\n\nbin/bridgetown configure feed\n\n\nWeb Awesome\n\n👑 Installs Web Awesome for an instant design system and UI component library at your fingertips. Use CSS variables and shadow parts to customize the look and feel of Web Awesome components in any way you like. This very website uses Web Awesome for example.\n\nIndividual components can be imported by adding the import statement to the ./frontend/javascript/index.js file. Refer to Web Awesome documentation Importing section for each individual component, and copy the import statement under the “npm” tab.\n\nRead more at Frontend Bundling (CSS/JS/etc.).\n\n🛠 Configure using:\n\nbin/bridgetown configure webawesome\n\n\nAutomated Test Suite using Minitest\n\n⚙️ Adds a test suite using Minitest and Rack::Test which lets you test both static and dynamic routes. Check out our automated testing guide for more information.\n\n🛠 Configure using:\n\nbin/bridgetown configure minitesting\n\n\nOpen Props\n\n🎨 Installs Open Props, a collection of “supercharged CSS variables” and optional normalize stylesheet to help you create your own design system.\n\n🛠 Configure using:\n\nbin/bridgetown configure open-props\n\n\nLit\n\n🔥 Sets up Lit plus the Lit SSR Renderer plugin and adds an example component. Every Lit component is a native web component, with the superpower of interoperability. This makes Lit ideal for building shareable components, design systems, or maintainable, future-ready sites and apps.\n\n🛠 Configure using:\n\nbin/bridgetown configure lit\n\n\nRead our full Lit Components documentation here.\n\nBridgetown recommended PostCSS plugins\n\n⛓️ Installs and configures a set of PostCSS plugins recommended by the Bridgetown community:\n\n\n  postcss-mixins\n  postcss-color-mod-function\n  cssnano\n\n\nIt will also configure postcss-preset-env to polyfill all features at stage 2 and above. If you don’t need certain polyfills for your use case, you can bump up stage to 3 or 4 (for example, custom properties won’t get polyfilled if stage is set to 4). nesting-rules and custom-media-queries are explicitly enabled.\n\nThis configuration will overwrite your postcss.config.js file.\n\n🛠 Configure using:\n\nbin/bridgetown configure bt-postcss\n\n\nIf you’d like to customize your setup further you can find more plugins here.\n\nPurgeCSS Post-Build Hook\n\n🧼 Adds a builder plugin which runs PurgeCSS against the output HTML + frontend JavaScript and produces a much smaller CSS output bundle for sites which use large CSS frameworks. NOTE: do not install this if you are also installing Tailwind, as this plugin and the Tailwind JIT will conflict with one another.\n\n🛠 Configure using:\n\nbin/bridgetown configure purgecss\n\n\nRender YAML Configuration\n\n⚙️ Adds a static site service defined in YAML to your site for use in Render deployments.\n\n🛠 Configure using:\n\nbin/bridgetown configure render\n\n\nNetlify TOML Configuration\n\n⚙️ Adds a basic configuration to your site for use in Netlify deployments.\n\n🛠 Configure using:\n\nbin/bridgetown configure netlify\n\n\nGitHub Pages Configuration\n\n⚙️ Sets up a GitHub Action so you can host your Bridgetown site directly on GitHub.\n\nMake sure you follow the provided instructions after you run this command so your base_path is configured correctly.\n\n🛠 Configure using:\n\nbin/bridgetown configure gh-pages\n\n\nCypress\n\n⚙️ Installs and sets up Cypress for browser based end-to-end testing. Check out our automated testing guide for more info!\n\n🛠 Configure using:\n\nbin/bridgetown configure cypress"
        },
        {
          "id": "docs-command-line-usage",
          "title": "Command Line Usage",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "command-line-usage",
          "tags": "",
          "url": "/docs/command-line-usage",
          "content": "The Bridgetown gem makes the bridgetown executable available to you in your terminal. In a site project, a binstub is provided in the bin folder so you can execute bin/bridgetown and ensure you’re using the correct version of Bridgetown as specified in your Gemfile. (The shorter bin/bt alias is also provided.)\n\nYou can run bin/bridgetown to see a list of available commands as well as Rake tasks which either come with Bridgetown or are located in your Rakefile. See below for information on how to define your own Rake tasks.\n\nThe help &lt;command&gt; command provides more information about the available options for any specific command.\n\nAvailable commands are:\n\n\n  bridgetown new PATH - Creates a new Bridgetown site at the specified path with a default configuration and typical site folder structure.\n    \n      Use the --apply= or -a option to apply an automation to the new site.\n      Use the --configure= or -c option to apply one or more bundled configurations to the new site.\n      Use the -t option to choose Serbea or Liquid templates instead of ERB (aka -t serbea).\n      Use the --use-sass option to configure your project to support Sass.\n    \n  \n  bin/bridgetown start or s - Boots the installed Rack-compliant web server at localhost:4000. In development, you’ll get live reload functionality as long as {% live_reload_dev_js %} or &lt;%= live_reload_dev_js %&gt; is in your HTML head.\n  bin/bridgetown deploy - Ensures that all frontend assets get built alongside the published Bridgetown output. This is the command you’ll want to use for deployment.\n  bin/bridgetown build or b - Performs a single build of your site to the output folder. Add the -w flag to also regenerate the site whenever a source file changes.\n  bin/bridgetown console or c - Opens up an IRB console and lets you inspect your site configuration and content “under the hood” using Bridgetown’s native Ruby API. See below for information on how to add your own console methods.\n    \n      The console command loads up the console configuration context by default. Use the --server-config option (or -s) to load the server context instead (which also instantiates the Roda server application) for troubleshooting. If you’ve added the Rack::Test gem (included automatically via the Minitest bundled configuration), the get/post/etc. DSL is available so you can try out responses right in the console.\n    \n  \n  bin/bridgetown plugins [list|cd] - Display information about installed plugins or allow you to copy content out of gem-based plugins into your site folders.\n  bin/bridgetown apply - Run an automation script for your existing site.\n  bin/bridgetown configure CONFIGURATION - Run a bundled configuration for your existing site. Invoke without arguments to see all available configurations.\n  bin/bridgetown date - Displays the current date and time so you can copy’n’paste it into your front matter.\n  bin/bridgetown help - Shows help, optionally for a given subcommand, e.g. bridgetown help build.\n  bin/bridgetown clean - Removes all generated files: destination folder, metadata file, and Bridgetown caches.\n  bin/bridgetown esbuild ACTION - Allows you to perform actions such as update on your project’s esbuild configuration. Invoke without arguments to see all available actions.\n\n\nTo change Bridgetown’s default build behavior have a look through the configuration options. You’ll also want to read up on how to set your Bridgetown environment for different use cases.\n\nFor deployment, if you need to add an extra step to copy output to a web server or run some script post-build, putting that in the deploy task in your Rakefile is a good way to go.\n\nAlso take a look at the scripts configuration in package.json which provides integration points with the frontend bundler.\n\nRakefile and Rake tasks\n\nRake is a task runner for Ruby applications. Tasks can execute shell commands, run through Ruby logic, or perform automation actions. Some tasks can be written to depend on the execution of prerequisite tasks.\n\nIn the default Rakefile which comes with a new Bridgetown site project, you’ll see a few tasks defined which are used by various built-in commands. For example, when you run the bin/bridgetown start command in a typical development environment, one of the tasks it performs is frontend:dev. You can see that in your Rakefile here:\n\nnamespace :frontend do\n  desc \"Build the frontend with esbuild for deployment\"\n  task :build do\n    sh \"npm run esbuild\"\n  end\n\n  desc \"Watch the frontend with esbuild during development\"\n  task :dev do\n    sh \"npm run esbuild-dev\"\n  rescue Interrupt\n  end\nend\n\n\nYou’re welcome to modify the tasks in your Rakefile as needed. For example, for this website we run a linter which looks for unnecessary &lt;div&gt; and &lt;span&gt; tags in the output HTML. This check is run for each deployment, so the deploy task has been modified to include this step:\n\ndesc \"Build the Bridgetown site for deployment\"\ntask deploy: [\n  :clean,\n  :linthtml, # this has been added to the default deploy task\n  \"frontend:build\",\n] do\n  Bridgetown::Commands::Build.start\nend\n\ntask :linthtml do # this is custom for the website project\n  sh \"npm run lint:html\"\nend\n\n\nAs is shown in comments for the default Rakefile, you can add your own automations directly inside of Rake tasks. In the provided example, you can see that a site object is available, and within an automation block you can call Freyia actions like standard automation scripts:\n\ntask :my_task =&gt; :environment do\n  puts site.root_dir\n  automation do\n    say_status :rake, \"I'm a Rake task =) #{site.config.url}\"\n  end\nend\n\n\nRunning bin/bridgetown my_task would result in printing out the root path of the site as well as executing the say_status Freyia action.\n\n\n  \n  The site variable is lazy-loaded, aka the site doesn&#8217;t initialize before the site variable is accessed. You can add run_initializers to the top of your task block to ensure all site configurations, hooks, etc. have been executed. You can also pass a different initializer context (other than :rake) by providing the context as an argument. For example: run_initializers context: :server\n\n\nConsole Commands\n\nWhen you run bin/bridgetown console or c, you have access to an instantiated site object which you can use to investigate its content and configuration. You can also call collections directly as a shorthand for site.collections, and you can run reload! anytime you want to reset/reload site content and plugins.\n\nBesides those built-in console methods, you can add your own! Define your own ConsoleMethods module and include that in Bridgetown’s standard module.\n\nmodule ConsoleMethods\n  def ruby_rocks\n    \"MINASWAN!\"\n  end\nend\n\nBridgetown::ConsoleMethods.include ConsoleMethods\n\n\nTyping in ruby_rocks and pressing Enter in the console would result in the output: MINASWAN!. (In case you’re wondering, MINASWAN is a fun saying within the Ruby community which stands for Matz Is Nice And So We Are Nice. 😄)\n\nTo see a list of all console methods available, type Bridgetown::ConsoleMethods.instance_methods."
        },
        {
          "id": "docs-structure",
          "title": "Folder Structure",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "structure",
          "tags": "",
          "url": "/docs/structure",
          "content": "The typical folder structure for a Bridgetown site usually looks something like this:\n\n.\n├── config # this is where frontend, initializers, and server defaults are stored\n├── frontend # this is where you put your CSS and JS for esbuild\n│   ├── javascript\n│   │   ├── index.js\n│   │   └── widget.js\n│   ├── styles\n│   │   ├── index.css\n│   └   └── layout.css\n├── server # this is where you can (optionally) add API routes using Roda\n├── src # this is where you put your resources and design templates\n│   ├── _components\n│   │   ├── footer.liquid\n│   │   └── header.liquid\n│   ├── _data\n│   │   ├── members.yml\n│   │   └── site_metadata.yml\n│   ├── _layouts\n│   │   ├── default.erb\n│   │   └── post.serb\n│   ├── _posts\n│   │   ├── 2021-09-18-enjoying-the-celebration.md\n│   │   └── 2022-04-07-checking-out-bridgetown-now.md\n│   ├── images\n│   │   └── logo.svg\n│   ├── 404.html\n│   ├── some_page.md\n│   └── index.html # or index.md\n├── output # this is the generated site after build process\n├── plugins # this is where you can write plugins with the Builder API\n├── config.ru # The Rack server's entrypoint\n├── esbuild.config.js # frontend bundler config\n├── Gemfile\n├── Rakefile\n└── package.json\n\n\nThe location of pages in your source folder structure will by default be mirrored in your output folder, whereas posts are handled in a special way. You can customize these permalinks via front matter and global configuration options.\n\nOverview of Files &amp; Folders\n\n\n  \n    \n      File / Directory\n      Description\n    \n  \n  \n    \n      \n        src/_components\n      \n      \n        \n          A location for all your components which can be referenced by your layouts and resources to comprise a design system and facilitate template reuse. In Liquid, {% render \"card\" %} would insert the _components/card.liquid component. You can create Ruby components as well and save them here for use in Ruby layouts and resource files.\n        \n      \n    \n    \n      \n        src/_data\n      \n      \n        \n          A place for well-formatted structured data. Bridgetown will autoload these files and they will then be accessible via site.data. For example, given members.yml, you can access the contents of that file via site.data.members. Supported formats are: .yml/.yaml, .json, .csv, .tsv, and .rb.\n        \n      \n    \n    \n      \n        src/_layouts\n      \n      \n        \n          These are the templates that wrap resources and even other layouts. Layouts are chosen on a file-by-file basis via the front matter (and you can configure default layouts for different collections or folder paths).\n        \n      \n    \n    \n      \n        src/_posts\n      \n      \n        \n          This is where you add dynamic blog-style content. The naming convention of these files is important, and must follow the format: YEAR-MONTH-DAY-post-title.EXT (aka .md, .html, etc.). The permalink can be customized for each post. Posts are a built-in collection, and you can configure other collections in addition to (or even instead of) posts.\n        \n      \n    \n    \n      \n        src/images\n      \n      \n        \n          You can save images here and reference them in both your markup and CSS (e.g. /images/logo.svg). The name of the images folder is completely arbitrary…feel free to rename it or relocate it under a parent assets folder.\n        \n      \n    \n    \n      \n        src/index.html or src/index.md and other HTML,\n        Markdown, etc. pages\n      \n      \n        \n          Provided that the file has a front matter section, it will be transformed by Bridgetown as a resource. You can create subfolders (and subfolders of subfolders) to organize your pages. You can also locate pages within _pages to line up with _posts, _data, etc.\n        \n      \n    \n    \n      \n        General files/folders in the source folder\n      \n      \n        \n          Every other directory and file except for those listed above—such as downloadable files, favicon.ico, robots.txt, and so forth—will be copied verbatim to the generated site as Static Files.\n        \n      \n    \n    \n      \n        output\n      \n      \n        \n          This is where the generated site will be placed once Bridgetown is done transforming all the content.\n        \n      \n    \n    \n      \n        plugins\n      \n      \n        \n          This is where you'll write custom plugins for your site to use.\n          (Third-party plugins are installed as Gems via Bundler.) Typically\n          there will be one site_builder.rb superclass, and you\n          will add new builder subclasses to the plugins/builders\n          folder. Read all about it in the Plugins\n          documentation.\n        \n      \n    \n    \n      \n        server\n      \n      \n        \n          This contains the base Roda application structure, used by Bridgetown to facilitate both the static files server and SSR/dynamic routes (if present).\n        \n      \n    \n    \n      \n        .bridgetown-cache\n      \n      \n        \n          .bridgetown-cache is used to improve performance over multiple builds by storing the results of expensive operations.\n        \n      \n    \n    \n      \n        bridgetown.config.yml\n      \n      \n        \n          Stores configuration data. A few of these options can be specified from the command line executable but it’s generally preferable to save them here.\n        \n      \n    \n    \n      \n        Gemfile\n      \n      \n        \n          Used by Bundler to install the relevant Ruby gems for your Bridgetown site.\n        \n      \n    \n    \n      \n        package.json\n      \n      \n        \n          Manifest used by NPM to install frontend assets and set up commands you can run to compile your JavaScript, CSS, etc. via esbuild.\n        \n      \n    \n    \n      \n        esbuild.config.js\n      \n      \n        \n          Configuration file used by esbuild to compile frontend assets and save them to the output folder alongside your Bridgetown content."
        },
        {
          "id": "docs-resources",
          "title": "Resources",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "resources",
          "tags": "",
          "url": "/docs/resources",
          "content": "The majority of your text-based content and view templates in Bridgetown are processed as resources. A resource might be an informational page about you or your company, a blog post, an event, a podcast episode, a product.\n\nResource files contain front matter, metadata about the resource which can be used in other layouts and templates. For example, your about page (src/_pages/about.md) might be written like this:\n\n---\nlayout: page\ntitle: About Me\nheadshot: looking-good.jpg\n---\n\nHere's a page all about myself.\n\nHere's what I look like:\n\n![Me, Myself, and I](/images/&lt;%= data.headshot %&gt;)\n\n\nIn this example, the layout of the resource is specified as page, the title is “About Me” (which will be used by the layout and related templates), and a headshot filename is given which can then inform the final URL of the image in the body of the content.\n\nYou can save resources as files within your source tree, and you can also generate resources programatically via a builder plugin—perhaps based on data from a headless CMS or other third-party APIs. In addition, it’s possible to configure the external_sources initializer to pull content files in from outside your source tree.\n\nEvery resource you create is part of a collection. Bridgetown comes with two built-in collections, posts and pages, as well as a no-output data collection. You can easily create custom collections to group related content and facilitate pagination and archiving functionality.\n\nWant to learn more about how to use resources effectively in your website structure and content strategy? Read on!\n\nTable of Contents\n\n  Technical Architecture\n  Accessing Resources in Templates    \n      Loops and Pagination\n    \n  \n  Taxonomies\n  Resource Relations\n  External Content Sources\n  Wikilinks\n  Configuring Permalinks\n  Slotted Content\n  Ruby Front Matter and All-Ruby Templates\n  Resource Extensions\n  Upgrading Legacy Content to Use Resources\n\n\nTechnical Architecture\n\nThe resource is a 1:1 mapping between a unit of content and a URL (remember the acronym Uniform Resource Locator?). A “unit of content” is typically a Markdown or HTML file along with YAML front matter saved somewhere in the src folder.\n\nWhile certain resources don’t actually get written to URLs such as data files (and other resources and/or collections can be marked to avoid output), the concept is sound. Resources encapsulate the logic for how raw data is transformed into final content within the site rendering pipeline.\n\nResources come with a merry band of objects to help them along the way. These are called Origins, Models, Transformers, and Destinations. Here’s a diagram of how it all works.\n\n\n\nLet’s say you add a new blog post by saving src/_posts/2021-05-10-super-cool-blog-post.md. To make the transition from a Markdown file with Liquid or ERB template syntax to a final URL on your website, Bridgetown takes your data through several steps:\n\n\n  It finds the appropriate origin class to load the post. The posts collection file reader uses a special origin ID to identify the file (in this case: repo://posts.collection/_posts/2021-05-10-super-cool-blog-post.md). Other origin classes could handle different protocols to download content from third-party APIs or load in content directly from scripts.\n  Once the origin provides the post’s data it is used to create a model object. The model will be a Bridgetown::Model::Base object by default, but you can create your own subclasses to alter and enhance data, or for use in a Ruby-based CMS environment. For example, class Post &lt; Bridgetown::Model::Base; end will get used automatically for the posts collection (because Bridgetown will use its inflector to map posts to Post). You can save subclasses in your plugins folder, or set up a dedicated models folder to be eager-loaded by Zeitwerk.\n  The model then “emits” a resource object. The resource is provided a clone of the model data which it can then process for use within templates. Resources may also point to other resources through relations, and templates can access resources through various means (looping through collections, referencing resources by source paths, etc.)\n  The resource is transformed by a pipeline to render template code such as ERB or Liquid, convert Markdown to HTML, and any other conversions specified—as well as optionally place the resource output within a converted layout.\n  Finally, a destination object is responsible for determining the resource’s “permalink” based on configured criteria or the presence of permalink front matter. It will then write out to the output folder using a static file name matching the destination permalink.\n\n\nAccessing Resources in Templates\n\nThe simplest way to access resources in your templates is to use the collections variable, available in both Ruby and Liquid templates.\n\n\n  ERB\n  Liquid\n\n  \n\n    Title: &lt;%= collections.genre.metadata.title %&gt;\n\nFirst URL: &lt;%= collections.genre.resources[0].relative_url %&gt;\n    \n\n  \n  \n\n    Title: {{ collections.genre.title }}\n\nFirst URL: {{ collections.genre.resources[0].relative_url }}\n    \n\n  \n\n\nLoops and Pagination\n\nYou can easily loop through collection resources by name, e.g., collections.posts.resources:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% collections.posts.each do |post| %&gt;\n  &lt;article&gt;\n    &lt;a href=\"&lt;%= post.relative_url %&gt;\"&gt;&lt;h2&gt;&lt;%= post.data.title %&gt;&lt;/h2&gt;&lt;/a&gt;\n\n    &lt;p&gt;&lt;%= post.data.description %&gt;&lt;/p&gt;\n  &lt;/article&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% for post in collections.posts.resources %}\n  &lt;article&gt;\n    &lt;a href=\"{{ post.relative_url }}\"&gt;&lt;h2&gt;{{ post.data.title }}&lt;/h2&gt;&lt;/a&gt;\n\n    &lt;p&gt;{{ post.data.description }}&lt;/p&gt;\n  &lt;/article&gt;\n{% endfor %}\n    \n\n  \n\n\nSometimes you’ll likely want to use a paginator:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% paginator.each do |post| %&gt;\n  &lt;article&gt;\n    &lt;a href=\"&lt;%= post.relative_url %&gt;\"&gt;&lt;h2&gt;&lt;%= post.data.title %&gt;&lt;/h2&gt;&lt;/a&gt;\n\n    &lt;p&gt;&lt;%= post.data.description %&gt;&lt;/p&gt;\n  &lt;/article&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% for post in paginator.resources %}\n  &lt;article&gt;\n    &lt;a href=\"{{ post.relative_url }}\"&gt;&lt;h2&gt;{{ post.data.title }}&lt;/h2&gt;&lt;/a&gt;\n\n    &lt;p&gt;{{ post.data.description }}&lt;/p&gt;\n  &lt;/article&gt;\n{% endfor %}\n    \n\n  \n\n\nRead more about how the paginator works here. You can also refer to how collections work and how you can also create your own custom collections.\n\nTaxonomies\n\nBridgetown comes with two builtin taxonomies: category and tag.\n\nCategories are usually used to structure resources in a way that affects their output URLs and easily match up with specialized archive pages. It’s a good way to “group” like-minded resources together.\n\nTags are considered more of a flat “folksonomy” that you can apply to resources which are purely useful for display, searching, or viewing related items.\n\nYou can use a singular front matter key “category / tag” or a plural “categories / tags”. If using the plural form but only providing a string, the categories/tags will be split via a space delimiter. Otherwise provide an array of values, like so:\n\ncategories:\n  - category 1\n  - another category 2\ntags:\n  - blessed\n  - super awesome\n\n\nIn addition to the built-in taxonomies, you can define your own taxonomies in the config. For example, if you were setting up a website all about music, you could create a “genre” taxonomy:\n\n# bridgetown.config.yml\n\ntaxonomies:\n  genre:\n    key: genres\n    title: \"Musical Genre\"\n    other_metadata: \"can go here!\"\n\n\nThen use that front matter key in your resources:\n\ngenres:\n  - Jazz\n  - Big Band\n\n\nYou can access taxonomies for resources in your templates as:\n\n\n  ERB\n  Liquid\n\n  \n\n    Title: &lt;%= site.taxonomy_types.genres.metadata.title %&gt;\n\n&lt;% resource.taxonomies.genres.terms.each do |term| %&gt;\n  Term: &lt;%= term.label %&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    Title: {{ site.taxonomy_types.genres.metadata.title }}\n\n{% for term in resource.taxonomies.genres.terms %}\n  Term: {{ term.label }}\n{% endfor %}\n    \n\n  \n\n\nResource Relations\n\nYou can configure one-to-one, one-to-many, or many-to-many relations between resources in different collections. You can then add the necessary references via front matter or metadata from an API request and access those relations in your templates, plugins, and components.\n\nFor example, given a config of:\n\ncollections:\n  actors:\n    output: true\n    relations:\n      has_many: movies\n  movies:\n    output: true\n    relations:\n      belongs_to:\n        - actors\n        - studio\n  studios:\n    output: true\n    relations:\n      has_many: movies\n\n\nThe following data accessors would be available:\n\n\n  actor.relations.movies\n  movie.relations.actors\n  movie.relations.studio\n  studio.relations.movies\n\n\nThe belongs_to type relations are where you add the resource references in front matter—Bridgetown will use a resource’s slug to perform the search. belongs_to can support solo or multiple relations. For example:\n\n# _movies/_1982/blade-runner.md\nname: Blade Runner\ndescription: A blade runner must pursue and terminate four replicants who stole a ship in space, and have returned to Earth to find their creator.\nyear: 1982\nactors:\n  - harrison-ford # _actors/_h/harrison-ford.md\n  - rutger-howard # _actors/_r/rutger-howard.md\n  - sean-young # _actors/_s/sean-young.md\nstudio: warner-brothers # _studios/warner-brothers.md\n\n\nThus if you were building a layout for the movies collection, it might look something like this:\n\n&lt;h1&gt;&lt;%= resource.data.name %&gt; (&lt;%= resource.data.year %&gt;)&lt;/h1&gt;\n&lt;h2&gt;&lt;%= resource.data.description %&gt;&lt;/h2&gt;\n\n&lt;p&gt;&lt;strong&gt;Starring:&lt;/strong&gt;&lt;/p&gt;\n\n&lt;ul&gt;\n  &lt;% resource.relations.actors.each do |actor| %&gt;\n    &lt;li&gt;&lt;%= link_to actor.name, actor %&gt;&lt;/li&gt;\n  &lt;% end %&gt;\n&lt;/ul&gt;\n\n&lt;p&gt;Released by &lt;%= link_to resource.relations.studio.name, resource.relations.studio %&gt;&lt;/p&gt;\n\n\nThe three types of relations you can configure are:\n\n\n  belongs_to: a single string or an array of strings which are the slugs of the resources you want to reference\n  has_one: a single resource you want to reference will define the slug of the current resource in its front matter\n  has_many: multiple resources you want to reference will define the slug of the current resource in their front matter\n\n\nThe inflections between the various singular and plural relation names are handled by Bridgetown’s inflector automatically. If you need to customize the inflector with words it doesn’t specifically recognize, you can add additional rules in the config/initializers.rb file.\n\nExternal Content Sources\n\nYou can also load resources for your project, such as Markdown files and associated images, from folders outside of a Bridgetown site project. This is ideal for content authored using third-party applications such as Obsidian. Read this documentation to learn more.\n\nWikilinks\n\nYou can add support for [[wikilinks]] style links within your Markdown content. This isn’t enabled by default, so you’ll need to update your config/initializers.rb file and add:\n\ninit :wikilinks\n\n\nBy default, Bridgetown will search all of your resources and find the first matching title. So if you have a resource with title: I am a Resource in its front matter, you can write [[I am a Resource]] to link to it.\n\nYou can also use a custom display title for your link, for example [[I am a Resource|here's a resource]] would render “here’s a resource” as the link text.\n\nIf you need to link to a specific section within a resource (aka anchor), use a # symbol: [[Another Page#the-best-section]]. You may need to inspect the HTML of a resource to ensure you’re linking to the correct anchor within the content.\n\nIf you’d like to opt-out any resource from being processed for wikilinks, add bypass_wikilinks: true to its front matter. To disable multiple resources at once, you can use Front Matter Defaults.\n\nConfiguring Permalinks\n\nBridgetown uses permalink “templates” to determine the default permalink to use for resource destination URLs. You can override a resource permalink on a case-by-case basis by using the permalink front matter key. Otherwise, the permalink is determined as follows (unless you change the site config):\n\n\n  For pages, the permalink matches the path of the file. So src/_pages/i/am/a/page.md will output to “/i/am/a/page/”.\n  For posts, the permalink is derived from the categories, date, and slug (aka filename, but you can change that with a slug front matter key).\n  For all other collections, the permalink matches the path of the file along with a collection prefix. So src/_movies/horror/alien.md will output to /movies/horror/alien/\n  In addition, if multiple site locales are configured, any content not in the “default” locale will be prefixed by the locale key. So a page offering both English and French variations would be output to /page-information and /fr/page-information.\n\n\nRefer to our permalinks documentation for further details on how to configure and custom generate permalinks.\n\nSlotted Content\n\nWhen writing out your resource content and you’re using a Ruby-based template language such as ERB, you can provide extra content in the form of “slots” which won’t be included in the main body of the resource but will be available within layouts and partials. This is perfect for “out of band” content such as extra HTML for the &lt;head&gt; or info to display in a sidebar or footer. Check out the docs here.\n\nRuby Front Matter and All-Ruby Templates\n\nFor advanced use cases where you wish to generate dynamic values for front matter variables, you can use Ruby Front Matter. Read the documentation here.\n\nIn addition, you can add all-Ruby page templates to your site besides the typical Markdown/Liquid/ERB options. Yes, you’re reading that right: put .rb files directly in your src folder! As long as the final statement in your code returns a string or can be converted to a string via to_s, you’re golden. Ruby templates are evaluated in a Bridgetown::ERBView context (even though they aren’t actually ERB), so all the usual Ruby template helpers are available.\n\nFor example, if we were to convert the out-of-the-box about.md page to about.rb, it would look something like this:\n\n###ruby\nfront_matter do\n  layout :page\n  title \"About Us\"\nend\n###\n\noutput = Array(\"This is the basic Bridgetown site template. You can find out more info about customizing your Bridgetown site, as well as basic Bridgetown usage documentation at [bridgetownrb.com](https://bridgetownrb.com/)\")\n\noutput &lt;&lt; \"\"\noutput &lt;&lt; \"You can find the source code for Bridgetown at GitHub:\"\noutput &lt;&lt; \"[bridgetownrb](https://github.com/bridgetownrb) /\"\noutput &lt;&lt; \"[bridgetown](https://github.com/bridgetownrb/bridgetown)\"\n\nmarkdownify output.join(\"\\n\")\n\n\nNow that obviously looks rather cumbersome and error-prone. Wouldn’t it be great if there were a more—shall we say—streamlined approach to pure Ruby templating? Thankfully there is!\n\nResource Extensions\n\nThis API allows you or a third-party gem to augment resources with new methods (both via the Resource Liquid drop as well as the standard Ruby base class). In addition, the summary method is now available for resources. By default the first line of content is returned, but any resource extension can provide a new way to summarize resources by adding summary_extension_output. Check out the resource extension plugin page for more information.\n\nUpgrading Legacy Content to Use Resources\n\nPrior to Bridgetown 1.0, a different content engine based on Jekyll was used which you may be familiar with if you have older Bridgetown sites in production or in progress.\n\n\n  The most obvious differences are what you use in templates (Liquid or ERB). For example, instead of site.posts in Liquid or site.posts.docs in ERB, you’d use collections.posts.resources (in both Liquid and ERB). (site.collection_name_here syntax is no longer available.) Pages are just another collection now so you can iterate through them as well via collections.pages.resources.\n  Front matter data is now accessed in Liquid through the data variable just like in ERB and skipping data is deprecated. Use {{ post.data.description }} instead of {{ post.description }}.\n  In addition, instead of referencing the current “page” through page (aka page.data.title), you can use resource instead: resource.data.title.\n  Resources don’t have a url variable. Your templates/plugins will need to reference either relative_url or absolute_url. Also, the site’s base_path (if configured) is built into both values, so you won’t need to prepend it manually.\n  Permalink formats have changed somewhat, so please refer to the latest permalink docs for how to use the new permalink styles and placeholders.\n  Whereas the id of a document is the relative destination URL, the id of a resource is its origin id. You can define an id in front matter separately however, which would be available as resource.data.id.\n  The paginator items are now accessed via paginator.resources instead of paginator.documents.\n  Instead of pagination:\\n  enabled: true in your front matter for a paginated page, you’ll put the collection name instead. Also you can use the term paginate instead of pagination. So to paginate through posts, add paginate:\\n  collection: posts to your front matter.\n  Prototype pages no longer assume the posts collection by default. Make sure you add a collection key to the prototype front matter.\n  Categories and tags are collated from all collections (even pages!), so if you used category/tag front matter manually before outside of posts, you may get a lot more site-wide category/tag data than expected.\n  Since user-authored pages are no longer loaded as Page objects and everything formerly loaded as Document will now be a Resource::Base, plugins will need to be adapted accordingly. The Page class has been renamed to GeneratedPage to indicate it is only used for specialized content generated by plugins.\n  With the legacy engine, any folder starting with an underscore within a collection would be skipped. With the resource engine, folders can start with underscores but they aren’t included in the final permalink. (Files starting with an underscore are always skipped however.)\n  The YYYY-MM-DD-slug.ext filename format will now work for any collection, not just posts.\n  The doc method in builder plugins has been replaced with add_resource. See the Resource Builder API docs for further details.\n  The resource content engine doesn’t provide a related/similar result set using LSI classification. So there’s no direct replacement for the related_posts feature of the legacy engine. However, anyone can create a gem-based plugin using the new resource extension API which could restore this type of functionality."
        },
        {
          "id": "docs-front-matter",
          "title": "Front Matter",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "front-matter",
          "tags": "",
          "url": "/docs/front-matter",
          "content": "Front matter is a snippet of YAML or Ruby data which sits at the top of a file between special line delimiters. You can think of front matter as a datastore consisting of one or more key-value pairs (aka a Hash in Ruby). You use front matter to add metadata, like a title or a description, to resources as well as site layouts. Front matter can be used in various ways to set configuration options on a per-file basis, and if you need more dynamic handling of variable data, you can write Ruby code for processing as front matter.\n\n\n  \n  Don&#8217;t repeat yourself\n\nIf you&#8217;d like to avoid repeating your frequently used variables over and over, you can define front matter defaults for them and only override them where necessary (or not at all). This works both for predefined and custom variables.\n\n\nTable of Contents\n\n  Using Front Matter\n  Predefined Global Variables\n  Custom Variables\n  Predefined Variables\n  Advanced Front Matter Data Structures\n  The Power of Ruby, in Front Matter\n  Define custom front matter loaders\n\n\nUsing Front Matter\n\nAny file that contains a front matter block will be specially processed by\nBridgetown. Files without front matter are considered static files\nand are copied verbatim from the source folder to destination during the build\nprocess.\n\nThe front matter must be the first thing in the file and must either take the form of valid\nYAML set between triple-dashed lines, or one of several Ruby-based formats (more on that below). Here is a basic example:\n\n---\nlayout: post\ntitle: Blogging Like a Hacker\n---\n\n\nBetween these triple-dashed lines, you can set predefined variables (see below\nfor a reference) or add custom variables of your own. These variables will\nthen be available to you to access using Liquid or Ruby-based template code further down in the\nfile, as well as in any layouts or components that the file in question relies on.\n\n\n  \n  Front matter variables are optional\n\nIf you want to use template code in a file but don’t need anything in your front matter, just leave it empty! The set of triple-dashed lines with nothing in between will still get Bridgetown to process your file. (This is useful for things like RSS feeds.)\n\n\nPredefined Global Variables\n\nThere are a number of predefined global variables that you can set in the\nfront matter of a resource.\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      \n        layout\n      \n      \n        \n\n          If set, this specifies the layout file to use. Use the layout file\n          name without the file extension. Layout files must be placed in the\n          _layouts directory.\n\n        \n        \n          \n            Using null will produce a file without using a layout\n            file. This is overridden if the file is a resource and has a\n            layout defined in the \n            front matter defaults.\n          \n          \n            Using none will produce a file without using a layout file\n            regardless of front matter defaults.\n          \n        \n      \n    \n    \n      \n        template_engine\n      \n      \n        \n          You can change the template engine Bridgetown uses to process the file.\n        \n      \n    \n    \n      \n        permalink\n      \n      \n        \n\n          If you need your URLs to be something other than what is configured by default,\n          (for posts, the default is /category/year/month/day/title/),\n          then you can set this variable and it will be used as the final URL.\n        \n      \n    \n    \n      \n        published\n      \n      \n        \n          Set to false if you don’t want a specific page to show up when the\n          site is generated.\n        \n      \n    \n  \n\n\n\n  \n  Render pages marked as unpublished\nTo preview unpublished pages, run bridgetown start or bridgetown build with\nthe --unpublished switch.\n\n\nCustom Variables\n\nYou can set your own front matter variables which become accessible within page templates or other places where resources are referenced. For instance, if you set a variable called food, you can use that in your template:\n\n\n  ERB\n  Liquid\n\n  \n\n    ---\nfood: Pad Thai\n---\n\n&lt;h1&gt;&lt;%= data.food %&gt;&lt;/h1&gt;\n    \n\n  \n  \n\n    ---\nfood: Pad Thai\n---\n\n&lt;h1&gt;{{ data.food }}&lt;/h1&gt;\n    \n\n  \n\n\n\n  \n  You can use the shorthand data method to access resource data in a template. For example: data.food is the same as resource.data.food, data.slug is the same as resource.data.slug, etc. Note that you&#8217;ll still need to use the resource object directly for predefined methods such as resource.relative_url, resource.summary, and some others.\n\n\nPredefined Variables\n\nThese resource variables are available out-of-the-box:\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      \n        date\n      \n      \n        \n          Specifying a date variable overrides the date from the filename of the resource.\n          This can be used to ensure correct sorting of posts. A date is specified in the\n          format YYYY-MM-DD HH:MM:SS +/-TTTT; hours, minutes, seconds, and\n          timezone offset are optional. You can also use a more human-readable format like this: Wed, 02 Feb 2022 08:55:41 -0800.\n        \n      \n    \n    \n      \n        category\n        categories\n      \n      \n        \n          You can specify one or more categories that the resource belongs to, and then you can\n          use that to filter posts in various ways or use the \"slugified\" version of the\n          category name to adjust the permalink for a post. Categories (plural key) can be\n          specified as a YAML list or a\n          space-separated string.\n        \n      \n    \n    \n      \n        tags\n      \n      \n        \n          Similar to categories, one or multiple tags can be added to a resource as a flexible\n          method of building a content folksonomy.\n          As with categories, tags can be specified as a YAML list or a\n          space-separated string.\n        \n      \n    \n  \n\n\nAdvanced Front Matter Data Structures\n\nYAML allows for pretty sophisticated methods of structuring data, for example representing arrays or hashes (key/value pairs). You can also write out longer multi-line strings like so:\n\n---\ndescription: |\n  I am a multiple line\n  string value that can\n  go on and on.\n---\n\n\nFor reference, here’s a side-by-side comparison of YAML data structures and their equivalents in Ruby.\n\n\n  \n  UTF-8 Character Encoding Warning\n\nIf you use UTF-8 encoding, make sure that no BOM header characters exist in your files or you may encounter build errors.\n\n\nThe Power of Ruby, in Front Matter\n\nFor advanced use cases where you wish to generate dynamic values for front matter variables, you can use Ruby Front Matter (hereafter named rbfm).\n\nAny valid Ruby code is allowed in rbfm as long as it returns a Hash—or an object which respond_to?(:to_h). There are several different ways you can define rbfm at the top of your file. This is so syntax highlighting will work in various different template scenarios.\n\nFor Markdown files, you can use backticks or tildes plus the term ruby to take advantage of GFM (GitHub-flavored Markdown) syntax highlighting.\n\n```ruby\n{\n  layout: :page,\n  title: \"About\"\n}\n```\n\nI'm a **Markdown** file.\n\n\nor\n\n~~~ruby\n{\n  layout: :page,\n  title: \"About\"\n}\n~~~\n\nI'm a **Markdown** file.\n\n\nFor ERB or Serbea files, you can use ---&lt;% / %&gt;--- or ---{% / %}--- delimiters respectively. (You can substitute ~ instead of - if you prefer.)\n\nFor all-Ruby files, you can use ---ruby / --- or ###ruby / ### delimiters.\n\nHowever you define your rbfm, bear in mind that the front matter code is executed prior to any processing of the template file itself and within a different context. (rbfm will be executed initially within either Bridgetown::Model::RepoOrigin or Bridgetown::Layout.)\n\nThankfully, there is a solution for when you want a front matter variable resolved within the execution context of a resource (aka Bridgetown::Resource::Base): use a lambda. Any lambda (or proc in general) will be resolved at the time a resource has been fully initialized. A good use case for this would be to define a custom permalink based on other front matter variables. For example:\n\n~~~ruby\n{\n  layout: :page,\n  segments: [\"custom\", \"permalink\"],\n  title: \"About Us\",\n  permalink: -&gt; { \"#{data.segments.join(\"/\")}/#{Bridgetown::Utils.slugify(data.title)}\" }\n}\n~~~\n\nThis will now show up for the path: /custom/permalink/about-us\n\n\nBesides using a Hash, you can also use the handy front_matter DSL. Any valid method call made directly in the block will translate to a front matter key. Let’s rewrite the above example:\n\n~~~ruby\nfront_matter do\n  layout :page\n\n  url_segments = [\"custom\"]\n  url_segments &lt;&lt; \"permalink\"\n  segments url_segments\n\n  title \"About Us\"\n  permalink -&gt; { \"#{data.segments.join(\"/\")}/#{Bridgetown::Utils.slugify(data.title)}\" }\nend\n~~~\n\nThis will now show up for the path: /custom/permalink/about-us\n\n\nAs you can see, literally any valid Ruby code has the potential to be transformed into front matter. The sky’s the limit!\n\n\n  \n  For security reasons, please do not allow untrusted content into your repository to be executed in an unsafe environment (aka outside of a Docker container or similar). Just like with custom plugins, a malicious content contributor could potentially introduce harmful code into your site and thus any computer system used to build that site. Enable Ruby Front Matter only if you feel confident in your ability to control and monitor all on-going updates to repository files and data.\n\n\nDefine custom front matter loaders\n\nIf you’re moving your site to Bridgetown from another static site generator, you may already have your front matter in another format. To ease the transition, you can define a new front matter loader using the front matter loader API."
        },
        {
          "id": "docs-collections",
          "title": "Collections",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "collections",
          "tags": "",
          "url": "/docs/collections",
          "content": "Collections are the perfect way to group resource-based content, such as blog posts, team members, talks at a conference, products to sell, upcoming events, and so forth. Bridgetown comes with a few built-in collections, and you can add new collections to support all sorts of content structures and hierarchies. All of the documentation pages on this very website are contained within a docs collection for example.\n\nLet’s dive into how you can put collections to work for your content.\n\nTable of Contents\n\n  Builtin Collections\n  Custom Collections\n  Adding Content\n  Accessing Collection Content\n  Output    \n      Permalinks\n    \n  \n  Custom Metadata\n  Collection Attributes\n\n\nBuiltin Collections\n\nBridgetown comes with three collections configured out of the box. These are\n\n\n  data, located in the src/_data folder\n  pages, located in either the src top-level folder or the src/_pages folder\n  posts, located in the src/_posts folder\n\n\nThe data collection doesn’t output to any URL and is used strictly to provide a complete merged dataset via the site.data variable. Learn more about data files here.\n\nPages are for generic, standalone (aka not dated) pages which will output at a URL similar to their file path. So src/i/am/a-page.html will end up with the URL /i/am/a-page/.\n\nPosts are for dated articles which will output at a URL based on the configured permalink style which might include category and date information. Posts are typically saved in a YYYY-MM-DD-slug-goes-here.EXT format which will cause the date to be extracted from the filename prefix. Posts can be saved in an arbitrary folder structure that makes the most sense for your content (as long as they’re contained within src/_posts).\n\nCustom Collections\n\nYou’re by no means limited to the builtin collections. You can create custom collections with any name you choose. By default they will behave similar to standalone pages, but you can configure them to behave in other ways (maybe like posts). For example, you could create an events collection which would function similar to posts, and you could even allow future-dated content to publish (unlike what’s typical for posts).\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    events do\n      output true\n      permalink \"pretty\"\n      future true\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  events:\n    output: true\n    permalink: pretty\n    future: true\n    \n\n  \n\n\nThus an event saved at src/_events/2021-12-15-merry-christmas.md would output to the URL /events/2021/12/15/merry-christmas/.\n\nYou can control the way a collection is sorted by specifying the front matter key (default is either filename or date if present) as well as the direction as either ascending (default) or descending.\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    reverse_ordered do\n      output true\n      sort_by \"order\"\n      sort_direction \"descending\"\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  reverse_ordered:\n    output: true\n    sort_by: order\n    sort_direction: descending\n    \n\n  \n\n\nAdding Content\n\nWhen setting up a custom collection, you’ll want to create a corresponding folder (e.g. src/_staff_members) and then add your resource files (aka files which include front matter). If no front\nmatter is provided in a file, Bridgetown will consider it to be a static file\nand the contents will not undergo further processing. Otherwise, Bridgetown will process the file contents into the expected output.\n\nRegardless of whether front matter exists or not, Bridgetown will write to the destination folder (e.g. output) only if output: true has been set in the collection’s metadata.\n\n\n  \n  Be sure to name your folders correctly\n\nThe folder must be named identically to the collection you defined in the configuration with the addition of the _ prefix.\n\n\nAccessing Collection Content\n\nBridgetown provides the collections object to your templates, with the various collections available as keys. For example, you can iterate over staff_members resources on a page and display the content for each staff member. The main body of the resource is accessed using the content variable:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% collections.staff_members.each do |staff_member| %&gt;\n  &lt;h2&gt;&lt;%= staff_member.data.name %&gt; - &lt;%= staff_member.data.position %&gt;&lt;/h2&gt;\n  &lt;p&gt;&lt;%= markdownify staff_member.content %&gt;&lt;/p&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% for staff_member in collections.staff_members.resources %}\n  &lt;h2&gt;{{ staff_member.data.name }} - {{ staff_member.data.position }}&lt;/h2&gt;\n  &lt;p&gt;{{ staff_member.content | markdownify }}&lt;/p&gt;\n{% endfor %}\n    \n\n  \n\n\nOutput\n\nIf you’d like Bridgetown to create a rendered page for each resource in your collection, make sure the output key is set to true in your collection metadata:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    staff_members do\n      output true\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  staff_members:\n    output: true\n    \n\n  \n\n\nYou can link to the generated resource using the relative_url attribute.\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% staff_member = collections.staff_members.first %&gt;\n\n&lt;a href=\"&lt;%= staff_member.relative_url %&gt;\"&gt;\n  &lt;%= staff_member.data.name %&gt; - &lt;%= staff_member.data.position %&gt;\n&lt;/a&gt;\n    \n\n  \n  \n\n    {% assign staff_member = collections.staff_members.resources[0] %}\n\n&lt;a href=\"{{ staff_member.relative_url }}\"&gt;\n  {{ staff_member.name }} - {{ staff_member.position }}\n&lt;/a&gt;\n    \n\n  \n\n\n\n  \n  If you have a large number of resources in a collection, it&#8217;s likely you&#8217;ll want to use the Pagination feature to make it easy to browse through a limited number of items per page.\n\n\nPermalinks\n\nThere are special permalink variables for collections to help you control the output URL for the collection. Each collection can be configured with its own permalink style. Of course you’re always welcome to override that permalink on a individual resource-by-resource basis, or by using front matter defaults.\n\nCustom Metadata\n\nIt’s also possible to add custom metadata to a collection. You add\nadditional keys to the collection config and they’ll be made available in templates. For example, if you specify this:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  collections do\n    tutorials do\n      output true\n      name \"Terrific Tutorials\"\n    end\n  end\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\ncollections:\n  tutorials:\n    output: true\n    name: Terrific Tutorials\n    \n\n  \n\n\nThen you could access the name value in a template by referencing the collection directly or through a resource:\n\n\n  ERB\n  Liquid\n\n  \n\n    Direct access to collection:\n&lt;%= collections.tutorials.metadata.name %&gt;\n\nVia a resource:\n&lt;%= resource.collection.metadata.name %&gt;\n    \n\n  \n  \n\n    Direct access to collection:\n{{ collections.tutorials.name }}\n\nVia a resource:\n{{ resource.collection.name }}\n    \n\n  \n\n\nCollection Attributes\n\nCollection objects are available under collections with the following information:\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      \n        label\n      \n      \n        \n          The name of your collection, e.g. my_collection.\n        \n      \n    \n    \n      \n        resources\n      \n      \n        \n          An array of resources.\n        \n      \n    \n    \n      \n        static_files / files (Liquid)\n      \n      \n        \n          An array of static files in the collection.\n        \n      \n    \n    \n      \n        relative_directory\n      \n      \n        \n          The path to the collection's source folder, relative to the site\n          source.\n        \n      \n    \n    \n      \n        directory\n      \n      \n        \n          The full path to the collections's source folder.\n        \n      \n    \n    \n      \n        metadata.output / output (Liquid)\n      \n      \n        \n          Whether the collection's documents will be output as individual\n          files.\n        \n      \n    \n  \n\n\n\n  \n  You can relocate your Collections\n\nIt&#8217;s possible to optionally specify a folder to store all your collections in a centralized folder with collections_dir: my_collections.\n\nThen Bridgetown will look in my_collections/_books for the books collection, and\nin my_collections/_recipes for the recipes collection.\n\n\n\n  \n  Be sure to move posts into custom collections folder\n\nIf you specify a folder to store all your collections in the same place with collections_dir: my_collections, then you will need to move your _posts folder to my_collections/_posts. Note that the name of your collections directory cannot start with an underscore (_)."
        },
        {
          "id": "docs-prototype-pages",
          "title": "Prototype Pages",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "prototype-pages",
          "tags": "",
          "url": "/docs/prototype-pages",
          "content": "This feature builds upon the Pagination functionality and\nlets you create automatically generated, paginated archives of your content filtered by\nthe search terms you provide. For instance you could set it up so every category has its\nown page, every tag has its own page, or virtually any other search term.\n\nNote that in order to use pagination, you’ll need to enable it in your site’s initializers.rb or bridgetown.config.yml.\n\nTable of Contents\n\n  Simple Usage\n  Searching in Collections\n  Pulling in Site Data\n  Permalinks\n\n\nSimple Usage\n\nAll you need to do is create a page, say categories/category.html, and add a\nprototype config to the Front Matter:\n\n---\nlayout: default\ntitle: Posts in category :prototype-term\nprototype:\n  collection: posts\n  term: category\n\n\nAnd then all the site’s different categories will have archives pages at this location\n(e.g. categories/awesome-movies, categories/my-cool-vacation, etc.). It will enable\npagination automatically, so you can use paginator.resources to loop through the\nposts like on any normal paginated page. Using :prototype-term in the page title will\nautomatically put each archive page’s term (aka the category name) in the output title.\n\nYou can do the same thing with tags. Use term: tag and create a tags/tag.html\nfile. The exact folder/filename doesn’t actually matter—you could create\nmy-super-awesome-tagged-content/groovy.html and it would still work. (The filename\nalways gets replaced by the search term itself.)\n\nIf you want to “titleize” the search term in the processed title variable, use\n:prototype-term-titleize. Thus given the category “cool-vacation”:\n\n---\ntitle: Posts in category :prototype-term-titleize\nprototype:\n  collection: posts\n  term: category\n\n\nYou’d get Posts in category Cool Vacation as the page title.\n\n\n  \n  Unspecified Class: Symbol Warning\n\nIf you would like :prototype-term or :prototype-term-titleize to appear first in the title, you must wrap the whole title in quotes to avoid a parsing error.\n\n\nIn addition, the search term used for each generated page is placed into a Liquid\nvariable, so you can use that as well in your template: page.data.category, or page.data.tag,\netc.\n\nSearching in Collections\n\nYou can search in any custom collection by including that in the prototype configuration:\n\ntigers/countries/country.html\n---\ntitle: Tigers in country :prototype-term-titleize\nprototype:\n  term: country\n  collection: tigers\n\n\n/_tigers/bengal.md\n---\ntitle: Bengal Tiger\ncountry: India\n\n\nThis would produce a generated tigers/countries/india page that loops through\nall the tigers in India.\n\nPulling in Site Data\n\nPrototype pages can be configured to load in extra data from data files which get matched with the search term for each item in the collection. This is great for common uses like listing out every post by each of the authors in the site.\n\nHere’s an example of how that works:\n\n_posts/2020-04-10-article-by-jared.md\n---\ntitle: I'm an article\nauthor: jared\n---\n\nContent goes here.\n\n\n_data/authors.yml\njared:\n  name: Jared White\n  mastodon: jaredwhite@indieweb.social\n\n\nauthors/author.html\n\n  ERB\n  Liquid\n\n  \n\n    ---\nlayout: default\ntitle: Articles by :prototype-data-label\nprototype:\n  collection: posts\n  term: author\n  data: authors\n  data_label: name\n---\n\n\n&lt;h1&gt;&lt;%= data.title %&gt;&lt;/h1&gt; &lt;-- Articles by Jared White --&gt;\n\n&lt;h2&gt;Mastodon: @&lt;%= data.author_data.mastodon %&gt;&lt;/h2&gt; &lt;!-- Mastodon: @jaredwhite@indieweb.social --&gt;\n\n&lt;!-- posts where author == jared --&gt;\n\n&lt;% paginator.resources.each do |post| %&gt;\n  &lt;%= render \"shared/post\", post: %&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    ---\nlayout: default\ntitle: Articles by :prototype-data-label\nprototype:\n  collection: posts\n  term: author\n  data: authors\n  data_label: name\n---\n\n\n&lt;h1&gt;{{ data.title }}&lt;/h1&gt; &lt;-- Articles by Jared White --&gt;\n\n&lt;h2&gt;Mastodon: @{{ data.author_data.mastodon }}&lt;/h2&gt; &lt;!-- Mastodon: @jaredwhite@indieweb.social --&gt;\n\n&lt;!-- posts where author == jared --&gt;\n\n{% for post in paginator.resources %}\n  {% render \"shared/post\", post: post %}\n{% endfor %}\n    \n\n  \n\n\nPermalinks\n\nYou can also customize the permalinks used in Prototype\npages using :term. For example, using the Tigers example above, you could change the\nURLs that get generated like so:\n\n---\ntitle: Tigers in country :prototype-term-titleize\npermalink: /animals/:term/tigers\nprototype:\n  term: country\n  collection: tigers\n\n\nAnd then you would get pages generated at /animals/india/tigers, /animals/china/tigers, etc."
        },
        {
          "id": "docs-datafiles",
          "title": "Data Files",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "data-files",
          "tags": "",
          "url": "/docs/datafiles",
          "content": "In addition to standard resources, you can specify custom datasets which are accessible via Liquid and Ruby templates as well as plugins.\n\nBridgetown supports loading data from YAML, JSON, CSV, and TSV files located in the src/_data folder. Note that CSV and TSV files must contain a header row.\n\nYou can also save standard Ruby files (.rb) to _data which get automatically evaluated. The return value at the end of the file can either be an array or any object which responds to to_h (and thus returns a Hash).\n\nThis feature allows you to avoid repetition in your templates and set site-specific options. In the case of Ruby data files, you can perform powerful processing tasks to populate your site content.\n\nTable of Contents\n\n  The Data Folder\n  The Metadata File\n  Example: Define a List of members\n  Subfolders\n  Merging Site Data into Resource Data    \n      Example: Accessing a Specific Author\n    \n  \n  Using Signals for Fast Refresh Tracking\n\n\nThe Data Folder\n\nThe _data folder is where you can save YAML, JSON, or CSV files (using either the .yml, .yaml, .json or .csv extension), and they will be accessible via site.data or site.signals (more on that below). Also, any files ending in .rb within the data folder will be evaluated as Ruby code with a Hash formatted output.\n\nThe Metadata File\n\nYou can store site-wide metadata variables in _data/site_metadata.yml so they’ll be easy to access and will regenerate pages when changed. This is a good place to put &lt;head&gt; content like your website title, description, favicon, social media handles, etc. Then you can reference site.metadata.title, etc. in your Liquid and Ruby templates.\n\nWant to switch to using a site_metadata.rb file where you have more programmatic control over the data values, can easily load in ENV variable, etc.? Now you can! For example:\n\n# src/_data/site_metadata.rb\n{\n  title: \"Your Ruby Website\",\n  lang: ENV[\"LANG\"],\n  tagline: \"All we need is Ruby\"\n}\n\n\nExample: Define a List of members\n\nHere is a basic example of using data files to avoid copy-pasting large chunks of code in your Bridgetown templates:\n\nIn _data/members.yml:\n\n- name: Eric Mill\n  github: konklone\n\n- name: Parker Moore\n  github: parkr\n\n- name: Liu Fengyun\n  github: liufengyun\n\n\nOr _data/members.csv:\n\nname,github\nEric Mill,konklone\nParker Moore,parkr\nLiu Fengyun,liufengyun\n\n\nThis data can be accessed via site.data.members (notice that the filename determines the variable name).\n\nYou can now render the list of members in a template:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;ul&gt;\n&lt;% site.data.members.each do |member| %&gt;\n  &lt;li&gt;\n    &lt;a href=\"https://github.com/&lt;%= member.github %&gt;\" rel=\"noopener\"&gt;\n      &lt;%= member.name %&gt;\n    &lt;/a&gt;\n  &lt;/li&gt;\n&lt;% end %&gt;\n&lt;/ul&gt;\n    \n\n  \n  \n\n    &lt;ul&gt;\n{% for member in site.data.members %}\n  &lt;li&gt;\n    &lt;a href=\"https://github.com/{{ member.github }}\" rel=\"noopener\"&gt;\n      {{ member.name }}\n    &lt;/a&gt;\n  &lt;/li&gt;\n{% endfor %}\n&lt;/ul&gt;\n    \n\n  \n\n\nSubfolders\n\nData files can also be placed in subfolders of the _data folder. Each folder level will be added to a variable’s namespace. The example below shows how GitHub organizations could be defined separately in a file under the orgs folder:\n\nIn _data/orgs/bridgetownrb.yml:\n\nusername: bridgetownrb\nname: Bridgetown\nmembers:\n  - name: Jared White\n    github: jaredcwhite\n\n  - name: Gilbert the Cat\n    github: gilbertkitty\n\n\nIn _data/orgs/doeorg.yml:\n\nusername: doeorg\nname: Doe Org\nmembers:\n  - name: John Doe\n    github: jdoe\n\n\nThe organizations can then be accessed via site.data.orgs, followed by the file name:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;ul&gt;\n&lt;% site.data.orgs.each do |_key, org| %&gt;\n  &lt;li&gt;\n    &lt;a href=\"https://github.com/&lt;%= org.username %&gt;\" rel=\"noopener\"&gt;\n      &lt;%= org.name %&gt;\n    &lt;/a&gt;\n    (&lt;%= org.members.count %&gt; members)\n  &lt;/li&gt;\n&lt;% end %&gt;\n&lt;/ul&gt;\n    \n\n  \n  \n\n    &lt;ul&gt;\n{% for org_hash in site.data.orgs %}\n  {% assign org = org_hash[1] %}\n  &lt;li&gt;\n    &lt;a href=\"https://github.com/{{ org.username }}\" rel=\"noopener\"&gt;\n      {{ org.name }}\n    &lt;/a&gt;\n    ({{ org.members | size }} members)\n  &lt;/li&gt;\n{% endfor %}\n&lt;/ul&gt;\n    \n\n  \n\n\nMerging Site Data into Resource Data\n\nFor easier access to data in your templates whether that data comes from the resource directly or from data files, you can use front matter to specify a data path for merging into the resource.\n\nDefine a front matter variable in a resource like so:\n\n---\ntitle: Projects\nprojects: site.data.projects\n---\n\n\nNow in your template you can reference data.projects exactly like data.title or any other front matter variable. You can even use front matter defaults to assign such a data variable to multiple resources at once.\n\nExample: Accessing a Specific Author\n\nYou can access a specific data item from a dataset using a front matter variable. The example below shows how. First, define your dataset:\n\n_data/people.yml:\n\ndave:\n  name: David Smith\n  mastodon: coolpeople.social/@dsmith\n\n\nThat author can then be specified as a variable in a post’s front matter:\n\n\n  ERB\n  Liquid\n\n  \n\n    ---\ntitle: Sample Post\nauthor: dave\npeople: site.data.people\n---\n\n&lt;% author = data.people[data.author] %&gt;\n\n&lt;a rel=\"author\" href=\"https://&lt;%= author.mastodon %&gt;\"&gt;\n  &lt;%= author.name %&gt;\n&lt;/a&gt;\n    \n\n  \n  \n\n    ---\ntitle: Sample Post\nauthor: dave\npeople: site.data.people\n---\n\n{% assign author = data.people[data.author] %}\n\n&lt;a rel=\"author\" href=\"https://{{ author.mastodon }}\"&gt;\n  {{ author.name }}\n&lt;/a&gt;\n    \n\n  \n\n\nUsing Signals for Fast Refresh Tracking\n\nNew in Bridgetown 2.0:  One of the downsides to using the site.data hash is it won’t be tracked by the fast refresh process. This means if you update something in a data file after the development server has booted up, you won’t immediately see any changes appear. You would have to also go resave the template (resource/layout/etc.) which references that data file in order to see that something has been updated.\n\nInstead, you could utilize site.signals. This is a newer construct which works in exactly the same way as site.data but it creates a “tracking subscription” automatically. Now when you access site.signals.stuff.here, anything in the src/_data/stuff.yml file for example which you go and change, you’ll then see fast refresh work on the pages which reference it.\n\n\n  \n  This feature only works in Ruby-based templates. We don&#8217;t offer a site.signals variable within in Liquid templates.\n\n\n\n  \n  In a future version of Bridgetown, we are planning to make site.data itself use signals, and alias site.signals to that, but due to compatibility concerns with existing projects we decided to make it an opt-in feature for now."
        },
        {
          "id": "docs-static-files",
          "title": "Static Files",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "static-files",
          "tags": "",
          "url": "/docs/static-files",
          "content": "A static file is a file that does not contain any front matter. These\ninclude images, PDFs, and other un-rendered content.\n\nYou can save static files in any subfolder or directly within the source folder (src). A common place to save images specifically is the src/images folder. You can reference them from both markup and CSS using a relative URL (for example, /images/logo.svg).\n\n\n  \n  Optionally, you can bundle images through esbuild and reference them with the asset_path helper. Or if you&#8217;re interested in a full-featured image management solution with the ability to resize and optimize your media sizes, check out Cloudinary and the bridgetown-cloudinary plugin.\n\n\nStatic files can be searched and accessed in templates via site.static_files and contain the\nfollowing metadata:\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      file.path\n      \n\n        The relative path to the file, e.g. /assets/img/image.jpg\n\n      \n    \n    \n      file.modified_time\n      \n\n        The time the file was last modified, e.g. 2016-04-01 16:35:26 +0200\n        \n        (also available as file.date)\n\n      \n    \n    \n      file.name\n      \n\n        The string name of the file e.g. image.jpg for image.jpg\n\n      \n    \n    \n      file.basename\n      \n\n        The string basename of the file e.g. image for image.jpg\n\n      \n    \n    \n      file.extname\n      \n\n        The extension name for the file, e.g.\n        .jpg for image.jpg\n\n      \n    \n  \n\n\nNote that in the above table, file represents a variable used in logic such as a for loop—you can name it whatever you wish in your own code.\n\nAdd front matter to static files\n\nAlthough you can’t directly add front matter values to static files, you can set front matter values through the defaults property in your configuration file. When Bridgetown builds the site, it will use the front matter values you set.\n\nHere’s an example:\n\nIn your bridgetown.config.yml file, add the following values to the defaults property:\n\ndefaults:\n  - scope:\n      path: \"images\"\n    values:\n      is_image: true\n\n\nWhen Bridgetown builds the site, it will treat each image as if it had the front matter value of is_image: true.\n\nNow suppose you want to list all your image assets as contained in src/images. You could use this loop to look in the static_files object and get all static files that have this front matter property:\n\n&lt;% site.static_files.select { _1.data.is_image }.each do |myimage| %&gt;\n  &lt;%= myimage.relative_url %&gt;\n&lt;% end %&gt;\n\n\nWhen you build your site, the output will list the relative URL to each file that meets this front matter condition."
        },
        {
          "id": "docs-template-engines",
          "title": "Choose Your Template Engine",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "template-engines",
          "tags": "",
          "url": "/docs/template-engines",
          "content": "Bridgetown’s default configured template language is ERB (Embedded RuBy). If you’re familiar with PHP or other string-based template syntaxes in various programming languages, you should feel right at home.\n\nHowever, you can use a variety of different template engines within Bridgetown by using the appropriate file extension (aka .liquid for Liquid), or by specifying the template engine in your resource’s front matter. Out of the box, Bridgetown provides support for both ERB, Serbea, and Liquid, as well as a pure Ruby template type enhanced by Streamlined.\n\nYou can mix ‘n’ match template types in the same project. For example, Liquid’s simple syntax and safe execution context make it ideal for designer-led template creation, so you could use Liquid for layouts but stick to ERB for code-intensive pages and other resources.\n\nTo configure a new Bridgetown site to use a language other than ERB as the default template engine regardless of file extension, use the -t/--templates option when running bridgetown new.\n\nFor documentation on how to use Ruby or Liquid syntax in Bridgetown content and templates:\n\n\n  \n    \n      ERB, Serbea, &amp; More\n      \n    \n  \n  \n    \n      Liquid\n      \n    \n  \n\n\nPer-file Engine Configuration\n\nWhen the default ERB template engine is configured, Bridgetown processes files through ERB even when they don’t have an .erb extension. For example, posts.json or about.md or authors.html will all get processed through ERB during the build process (assuming front matter is present as is required by all resources).\n\nAs an initial step, you can use a different template engine based on extension alone. For example, authors.liquid would get processed through Liquid and output as authors.html. But there are a couple of drawbacks to that approach. If you wanted posts.liquid to be output as posts.json, you’d have to manually set a permalink in your front matter. In addition, if you wanted to write a document in Markdown but add Liquid tags in as well, you couldn’t do that via file extension alone because the Markdown converter looks for files ending in .md.\n\nSo instead of doing that, you can switch template engines directly. All you need to do is use the template_engine front matter variable. You can do this on any page, document, or layout. For example, you could write posts.json but add template_engine: liquid to the front matter, and then you’d be all set. Write your template using Liquid syntax, get posts.json on output. In the Markdown scenario, you could still author about.md while adding Liquid tags to the content, and it would work exactly as you expect.\n\nFront Matter Defaults\n\nBesides adding template_engine directly in your file’s front matter, you could use front matter defaults to specify a template engine for a folder or folder tree or files which match a particular “glob pattern”. That way you could, say, use ERB for most of the site but use Serbea only for a certain group of files.\n\nSite-wide Configuration\n\nMost likely, however, you’ll want to switch your site wholesale from one engine to another. That’s where config/initializers.rb (or bridgetown.config.yml) comes in. Let’s say you want to default to Serbea. Add template_engine :serbea right in your config, and suddenly everything will get processed through Serbea regardless of file extension. (This will have been done for you if you used the -t option when running bridgetown new.) Liquid works in the same manner: template_engine: liquid. Write HTML, XML, Markdown, JSON, CSV, whatever you like—and still access the full power of your template engine of choice.\n\nIt’s worth noting that by combining Markdown, ERB/Serbea, components, and frontend JavaScript “sprinkles” (or “spices” as we like to say), you can author extremely sophisticated documents which boast stunning performance and SEO scores while at the same time providing impressive interactivity in the browser. This is quickly becoming a “best practice” in the web development industry, and Bridgetown will help get you there.\n\nWhy Did Bridgetown Switch from Liquid?\n\nPrior to Bridgetown 2.0, Liquid was the default template type. Liquid feels more akin to template engines like Mustache, Jinja, Nunjucks, Twig, and so forth—and it was the only option in Bridgetown’s progenitor, Jekyll.\n\nBut most Bridgetown developers will need more power (especially when writing components) or may already be familiar with Ruby and engines such as ERB. And some developers are looking to switch from Middleman which uses ERB by default. Thus it makes sense to standardize around ERB.\n\nIn any case, the ability to “pick your flavor” of template engines on a site-by-site or file-by-file basis is one of Bridgetown’s core strengths as a web framework.\n\nIt’s Up to You\n\nRegardless of which template engine you pick, whether it’s ERB / Serbea / Streamlined, Liquid, or something else, Bridgetown has got you covered. We continue to look for ways to make switching engines easier while reducing the number of “sharp edges” that can arise to differences in how various template engines process content, so please don’t hesitate to let us know if you run in to any issues."
        },
        {
          "id": "docs-layouts",
          "title": "Layouts",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "layouts",
          "tags": "",
          "url": "/docs/layouts",
          "content": "Layouts are templates that wrap around your resource’s content. They allow you to have the source code for your template in one place so you don’t have to repeat things like your navigation and footer on every page.\n\nLayouts live in the _layouts folder. The convention is to have a base template called default.{erb,liquid,etc.} and optionally have other layouts inherit from this as needed.\n\nTable of Contents\n\n  Usage\n  New! Declarative Shadow DOM\n  Inheritance\n  Variables\n\n\nUsage\n\nHere’s an example of a very basic default HTML layout. When using Ruby templates, you typically yield to output rendered resource content, but content is also available as a special variable and is what you use in Liquid. The current resource is available via the resource variable, and there’s also a shortcut to access resource front matter via data.\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;!doctype html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;title&gt;&lt;%= data.seo_title %&gt;&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;nav&gt;\n      &lt;a href=\"/\"&gt;Home&lt;/a&gt;\n      &lt;a href=\"/blog/\"&gt;Blog&lt;/a&gt;\n    &lt;/nav&gt;\n    &lt;h1&gt;&lt;%= data.title %&gt;&lt;/h1&gt;\n    &lt;section&gt;\n      &lt;%= yield %&gt;\n    &lt;/section&gt;\n    &lt;footer&gt;\n      &amp;copy; me\n    &lt;/footer&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    &lt;!doctype html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;title&gt;{{ data.seo_title }}&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;nav&gt;\n      &lt;a href=\"/\"&gt;Home&lt;/a&gt;\n      &lt;a href=\"/blog/\"&gt;Blog&lt;/a&gt;\n    &lt;/nav&gt;\n    &lt;h1&gt;{{ data.title }}&lt;/h1&gt;\n    &lt;section&gt;\n      {{ content }}\n    &lt;/section&gt;\n    &lt;footer&gt;\n      &amp;copy; me\n    &lt;/footer&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n    \n\n  \n\n\nOnce you’ve set up one or more layouts, you can specify what layout you’d like to use in your resource’s front matter.\n\n---\ntitle: My First Page\nlayout: default\n---\n\nThis is the content of my page\n\n\nThe rendered output of this resource then is:\n\n&lt;!doctype html&gt;\n&lt;html lang=\"en\"&gt;\n  &lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;title&gt;First Page&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;nav&gt;\n      &lt;a href=\"/\"&gt;Home&lt;/a&gt;\n      &lt;a href=\"/blog/\"&gt;Blog&lt;/a&gt;\n    &lt;/nav&gt;\n    &lt;h1&gt;My Awesome First Page&lt;/h1&gt;\n    &lt;section&gt;\n      This is the content of my page\n    &lt;/section&gt;\n    &lt;footer&gt;\n      &amp;copy; me\n    &lt;/footer&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n\n\nYou can also use front matter defaults to to avoid having to set a layout explicitly for every resource. Note that if you have defaults in place and you don’t want a certain resource to render in a layout, you can specify layout: none in the resource’s front matter.\n\nNew! Declarative Shadow DOM\n\nAn emerging technology which has the potential to change how we approach development of layout and modular composition on the web is called Declarative Shadow DOM (DSD). Starting in Bridgetown 1.3, you can utilize DSD in your layouts and components for increased separation between presentation logic and content, scoped styles which won’t inadvertently affect other parts of the page (or other templates), and many other benefits. Check out our documentation on DSD for further details.\n\nInheritance\n\nLayout inheritance is useful when you want to add something to an existing layout for a portion of resources on your site. A common example of this is blog posts, you might want a post to display the date and author but otherwise be identical to your base layout.\n\nTo achieve this you need to create another layout which specifies your original layout in front matter. For example this layout will live at _layouts/post.erb:\n\n---\nlayout: default\n---\n&lt;p&gt;&lt;%= resource.date %&gt; - Written by &lt;%= data.author %&gt;&lt;/p&gt;\n\n&lt;%= yield %&gt;\n\n\nNow posts can use this layout while the rest of the resources use the default.\n\nVariables\n\nYou can set front matter in layouts as well. Use the layout variable instead of resource. For example:\n\n---\ncity: San Francisco\n---\n&lt;p&gt;{{ layout.data.city }}&lt;/p&gt;\n\n{{ content }}"
        },
        {
          "id": "docs-components",
          "title": "Components",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "components",
          "tags": "",
          "url": "/docs/components",
          "content": "Thinking of your website design as a collection of loosely-coupled, independent components which can be placed anywhere, nested, and reused, is one of the most exciting developments in the practice of building for the  web.\n\nComponent-based design systems are at the forefront of major leaps forward in the architecture used to enable agile, scalable codebases. Techniques such as “island architecture” and “hydration” have entered into the modern vernacular. We increasingly see the shift from “page-level” to “component-level” thinking as projects unfold.\n\nJust as a web page can be thought of as the interconnected product of the three primary web technologies (HTML, CSS, &amp; JavaScript), components are themselves individual products made out of those three technologies—or for simpler components, perhaps just one or two. Components also carry with them the concept of a “lifecycle”. Understanding the lifecycle of a component on both the backend (SSR / SSG) and the frontend—and perhaps the component’s “children” as well—is crucial in determining which toolset you should use to build the component. This touches on the concept we like to call “progressive generation”. (Read our tech specs intro for additional context.)\n\nBridgetown provides three environments for writing components:\n\nRuby\n\nUse a Ruby-based template engine in conjunction with a dedicated Ruby class to facilitate more comprehensive scenarios and take full advantage of Ruby’s feature set and object-oriented nature.\n\nLiquid\n\nUse the Liquid template engine to write basic components without a lot of custom logic. Liquid components are not recommended when complexity is required for frontend logic.\n\nLit (Web Components)\n\nAfter installing the Lit Renderer plugin, you can write “hybrid” components which support both a backend lifecycle (during SSG &amp; SSR) and a frontend lifecycle (via Hydration). This technique is recommended for components which must support a high degree of interactivity or data timelines.\n\nSo pick your flavor and dive in, or keep reading for more conceptual overview of Bridgetown’s component architecture.\n\nNew! Declarative Shadow DOM\n\nAn emerging technology which has the potential to change how we approach development of layout and modular composition on the web is called Declarative Shadow DOM (DSD). Starting in Bridgetown 1.3, you can utilize DSD in your layouts and components for increased separation between presentation logic and content, scoped styles which won’t inadvertently affect other parts of the page (or other templates), and many other benefits. Check out our documentation on DSD for further details.\n\nThe Subtle Interplay of HTML, CSS, &amp; JavaScript\n\nAs previously mentioned, a component will often encompass styling via CSS and client-side interactivity via JavaScript, alongside the output HTML coming from the component’s logic/template.\n\nIn those cases, where you place your CSS and JS code will vary depending on the environment. For most components, you will write what are called “sidecar” files which live alongside your component classes/templates. In contrast, Lit components fall under the category of Single-File Components. The logic, template, and styling is all part of the same unit of code. However, with a smidge of extra configuration, you do have the option of splitting the CSS of a Lit component out to its own sidecar file if you so choose.\n\nHere’s an example file structure showing all three environments in use:\n\n.\n└── src\n    └── _components\n        ├── blog_entry.liquid\n        ├── products\n        │   ├── buy-now.lit.js\n        │   ├── buying.rb\n        │   ├── product-cart.lit.css\n        │   └── product-cart.lit.js\n        └── shared\n            ├── navbar.erb\n            ├── navbar.js\n            ├── navbar.rb\n            └── navbar.css\n\n\nA rundown of the various component types:\n\n\n  The “blog entry” component is a single .liquid file. Even though it only outputs HTML, we still call this a component because the none of the outside variables of any other template or component can be accessed or mutated. You must pass all necessary data into the component it needs to render content.\n  The buy-now and product-cart components are both Lit-powered web components. The cart component uses a sidecar CSS file. There’s also a Products::Buying Ruby component which serves as a “wrapper” to the buy now component.\n  The Shared::Navbar component is a Ruby component with a sidecar ERB template, a modest bit of JavaScript logic (not a web component), and CSS meant to be included in the global stylesheet bundle.\n\n\nNow let’s talk about the lifecycle of these components.\n\n\n  The Liquid component’s lifecycle is static-only. The HTML is rendered out during the build process and that’s it.\n  The Shared::Navbar Ruby component starts out as static HTML + global CSS, and the lifecycle is then extended on the client by JavaScript code which can perform tasks such as attach event handlers or highlight certain items based on real-time navigational changes.\n  The Lit components offer true hybrid lifecycles. They are written in JavaScript and are initially rendered as part of the build process (and thus present in the output HTML) by the Lit Renderer plugin, using an emerging spec called Declarative Shadow DOM. The components are then “hydrated” on the client-side so they can manage state, offer interactivity, and re-render as needed.\n\n\nRegarding that last item, due to various performance concerns both on the static-build/server-side and the client-side, it should be noted that you likely wouldn’t want pepper pages with dozens (or hundreds!) of Lit component renders. Instead you’d want to create what’s called an “island” within your page, using the lit helper. You can read more about this on the Lit components page.\n\nReady to dive more into a particular component flavor? Let’s go!\n\n\n  \n    \n      Ruby\n      \n    \n  \n  \n    \n      Liquid\n      \n    \n  \n  \n    \n      Lit"
        },
        {
          "id": "docs-frontend-assets",
          "title": "Frontend Bundling (CSS/JS/etc.)",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "frontendassets",
          "tags": "",
          "url": "/docs/frontend-assets",
          "content": "For modern websites, the output of HTML content is only part of the story. You also need a way to manage CSS, JavaScript, fonts, icons, and other frontend assets in a way that’s performant, optimized, and hassle-free.\n\nBridgetown provides a fully-integrated frontend bundling system using esbuild. You can read more about esbuild on its documentation site.\n\nTable of Contents\n\n  Frontend Locations\n  JavaScript\n  CSS    \n      PostCSS\n      Sass\n    \n  \n  Linking to the Output Bundles\n  Additional Bundled Assets (Fonts, Images)    \n      Globbing multiple assets\n    \n  \n  esbuild Setup    \n      Path Aliases\n      Multiple Entry Points\n      Code Splitting\n      Islands Architecture\n    \n  \n\n\nFrontend Locations\n\nFiles to be processed by esbuild are placed in the top-level frontend folder within your site root. This folder is entirely separate from the Bridgetown source folder where your content, templates, plugins, etc. live. However, using relative paths you can reference files in your frontend that live in the src folder (so you can place component-scoped JS/CSS files alongside Liquid or Ruby templates, for example).\n\n\n  \n  Wondering where to save images? Look at the src/images folder. You can reference them from both markup and CSS using a relative URL (for example, /images/logo.svg). Optionally, you can bundle images through esbuild and reference them with the asset_path helper (more information below). If you&#8217;re interested in a full-featured image management solution with the ability to resize and optimize your media sizes, check out Cloudinary and the bridgetown-cloudinary plugin.\n\n\nBridgetown uses NPM to install and manage frontend-based packages and dependencies. Gem-based plugins can instruct Bridgetown to add a related NPM package whenever Bridgetown first loads the gem.\n\nJavaScript\n\nThe starting place for JavaScript code lives at ./frontend/javascript/index.js. Here you can write your custom functionality, use import statements to pull in other modules or external packages, and so forth. This is also where you’d import the CSS entrypoint as well to be processed through esbuild.\n\nJS files placed anywhere inside src/_components are automatically imported and bundled as well.\n\nBecause Bridgetown utilizes standard ES bundler functionality, you can trick out your JavaScript setup with additional language enhancements and libraries like htmx, Lit, Web Awesome, and many others. And for automated installation of the aforementioned libraries in particular, check out our Bundled Configurations.\n\n\n  \n  What about TypeScript?\n\nTypeScript is one of the many transpile-to-JavaScript languages available today. TypeScript code isn&#8217;t directly compatible with native JavaScript environments and always requires a build step. It&#8217;s main selling point is static type-checking. However, it&#8217;s possible to use type-checking and gain the secondary benefits of documentation popups and project navigation using JSDoc in vanilla JavaScript! In fact, by adding // @ts-check to the top of a .js file, VSCode for example will immediately provide TypeScript-like features as you author your code.\n\nBridgetown happily endorses JSDoc-enhanced JavaScript for a 100% ES spec-compatible development environment. You can learn more about this approach on the TypeScript website.\n\n\nCSS\n\nBy default Bridgetown comes with support for PostCSS to allow for cutting-edge/upcoming CSS features which aren’t yet supported in all browsers (such as variable-based media queries and selector nesting).\n\nYou can also choose to use Sass, a pre-processor for CSS. Pass --use-sass to bridgetown new to set up your project to support Sass.\n\nThe starting place for CSS code lives at frontend/styles/index.css. You can add additional stylesheets and @import them into index.css. CSS files placed anywhere inside src/_components are automatically imported.\n\nPostCSS\n\nThe default PostCSS config is largely empty so you can set it up as per your preference. The only two plugins included by default are postcss-flexbugs-fixes and postcss-preset-env.\n\nThere’s also a Bundled Configuration you can run to install recommended PostCSS plugins and set up specific useful features like nesting.\n\n\n  \n  All the stylesheet’s a stage…\n\nBy default, Bridgetown configures the postcss-preset-env stage to be 2, but you may want to change it to 3 or even 4 for a more compact and performant stylesheet which the latest modern browsers can interpret. The lower the stage number, the more transformations/polyfills PostCSS will run in order to build a widely-compatible stylesheet. You can also determine which individual features to polyfill by adding the features option. Read the postcss-preset-env documentation here or browse the list of features here.\n\n\nSass\n\nThe starting place for Sass code lives at frontend/styles/index.scss.\n\nTo import common CSS frameworks such as Bootstrap, Foundation, Bulma and so forth, you can run:\n\nnpm install name-of-css-framework\n\n\nAnd then add:\n\n@use \"~css-framework/css-framework\";\n\n\nto index.scss. For example, to add Bulma which is a modern CSS-only (no JavaScript) framework built around Flexbox, you’d run:\n\nnpm install bulma\n\n\nand then add:\n\n@use \"~bulma/bulma\";\n\n\nto index.scss.\n\nOr if you’d like to add Bootstrap:\n\nnpm install bootstrap\n\n\n@use \"~bootstrap/scss/bootstrap.scss\";\n\n\nLinking to the Output Bundles\n\nBridgetown’s default esbuild configuration is set up to place all compiled output into the _bridgetown folder in your output folder. Bridgetown knows when it regenerates a website not to touch anything in _bridgetown as that comes solely from the frontend bundler. It is recommended you do not use the site source folder to add anything to _bridgetown as that will not get cleaned and updated by Bridgetown’s generation process across multiple builds.\n\nTo reference the compiled JS and CSS files from the frontend bundler in your site template, add the asset_path Liquid tag or Ruby helper to your HTML &lt;head&gt;. For example:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;link rel=\"stylesheet\" href=\"&lt;%= asset_path :css %&gt;\" /&gt;\n&lt;script src=\"&lt;%= asset_path :js %&gt;\" defer&gt;&lt;/script&gt;\n    \n\n  \n  \n\n    &lt;link rel=\"stylesheet\" href=\"{% asset_path css %}\" /&gt;\n&lt;script src=\"{% asset_path js %}\" defer&gt;&lt;/script&gt;\n    \n\n  \n\n\nThis will automatically produce HTML tags that look something like this:\n\n&lt;link rel=\"stylesheet\" href=\"/_bridgetown/static/css/all.6902d0bf80a552c79eaa.css\"/&gt;\n&lt;script src=\"/_bridgetown/static/js/all.a1286aad43064359dbc8.js\" defer&gt;&lt;/script&gt;\n\n\nAdditional Bundled Assets (Fonts, Images)\n\nBoth fonts and images can be bundled through esbuild’s loaders. This means that, in CSS/JS files, you can reference fonts/images saved somewhere in the frontend folder (or even from a package in node_modules) and those will get transformed and copied over to output/_bridgetown with a hashed filename (aka photo.jpg would become photo-31d6cfe0d16ae931b73c59d7e0c089c0.jpg).\n\nThere’s a catch with regard to how this works, because you’ll also want to be able to save files directly within src that are accessible via standard relative URLs (so src/images/photo.jpg is available at /images/photo.jpg within the static output, no frontend bundler processing required).\n\nSo here’s what you’ll want to do:\n\n\n  For any files saved inside of src, use server-relative paths. For example: background: url(/images/photo.jpg) in a frontend CSS file would point to what is saved at src/images/photo.jpg.\n  For any files saved inside of frontend, use filesystem-relative paths. For example: background: url(\"../images/photo.jpg\") in frontend/styles/index.css will look for frontend/images/photo.jpg. If the file can’t be found, esbuild will throw an error.\n\n\nYou can use the asset_path Ruby helper or Liquid tag to reference assets within the frontend folder:\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;img src=\"&lt;%= asset_path 'images/folder/somefile.png' %&gt;\" /&gt;\n    \n\n  \n  \n\n    &lt;img src=\"{% asset_path images/folder/somefile.png %}\" /&gt;\n    \n\n  \n\n\nwill look for frontend/images/folder/somefile.png.\n\nGlobbing multiple assets\n\nIf you need to import an entire folder through the frontend pipeline for example with images, here’s how to do it:\n\n// frontend/javascript/index.js\n\nimport images from '../images/**/*.{jpg,jpeg,png,svg}'\nObject.entries(images).forEach(image =&gt; image)\n\n\nesbuild Setup\n\n\n  \n  If you&#8217;re upgrading from a prior version of Bridgetown and have run bin/bridgetown esbuild update, you&#8217;ll need to modify your frontend/javascript/index.js file so the CSS import is $styles/index.css instead of just index.css.\n\nIf you want to be super thorough, also search and replace bridgetownComponents with $components, as the former is deprecated.\n\nThese path aliases are explained in further detail below.\n\n\nThe default configuration is defined in config/esbuild.defaults.js. However, you should add or override your own config options in the top-level esbuild.config.js file. By modifying the esbuildOptions object (blank by default), it adds to or overrides default config options. This provides a straightforward way of adding esbuild plugins and other esbuild features unique to your frontend.\n\nFor instance, you could add Ruby2JS support and switch to using a .js.rb file for your entrypoint:\n\nconst ruby2js = require(\"@ruby2js/esbuild-plugin\")\n\nconst esbuildOptions = {\n  entryPoints: [\"./frontend/javascript/index.js.rb\"],\n  plugins: [\n    ruby2js(),\n  ]\n}\n\n\nPath Aliases\n\nStarting in Bridgetown 1.3, there are several path aliases defined in the esbuild integration as well as jsconfig.json in your repo root. These are:\n\n$styles: frontend/styles\n$javascript: frontend/javascript\n$components: src/_components\n\n\nThis allows you to conveniently write import statements such as import \"$styles/index.css\". You can add additional path aliases if you’d like to import from other folders you’ve created in your project while maintaining terse paths.\n\nMultiple Entry Points\n\nYou can specify multiple entry points (which result in multiple output bundles) by using a custom entryPoints config. You can then reference the additional entry points using the asset_path Liquid tag or Ruby helper. (Be sure to start with javascript in your relative paths.) For example:\n\n// esbuild.config.js\n\nconst esbuildOptions = {\n  entryPoints: [\n    \"./frontend/javascript/index.js\",\n    \"./frontend/javascript/pages/contact_form.js\"\n  ],\n  format: \"esm\"\n}\n\n\n&lt;script src=\"{% asset_path javascript/pages/contact_form.js %}\"&gt;&lt;/script&gt;\n\n\nBy also adding format: \"esm\", you gain the ability to import code from new entry points directly inside of type=\"module\" scripts in your HTML! Let’s say contact_form.js exports the function contactForm to set up a form dynamically. Instead of using a script src= tag in the HTML head, you could do this (using ERB in this example):\n\n&lt;script type=\"module\"&gt;\n  import { contactForm } from \"&lt;%= asset_path 'javascript/pages/contact_form.js' %&gt;\"\n\n  contactForm(\"contact-form\")\n&lt;/script&gt;\n\n&lt;form id=\"contact-form\"&gt;\n&lt;/form&gt;\n\n\nCode Splitting\n\nAnother option for “breaking up the bundle” is to dynamically import new code within the execution of your JavaScript code at runtime. To enable this, make sure your esbuild config has these two options:\n\nconst esbuildOptions = {\n  format: \"esm\",\n  splitting: true,\n\n  // other options here…\n}\n\n\nThen replace the defer attribute in your HTML head with type=\"module\" to ensure your primary JavaScript bundle is loaded as an ES module by the browser. For example:\n\n&lt;script src=\"&lt;%= asset_path :js %&gt;\" type=\"module\"&gt;&lt;/script&gt;\n\n\nNow you can dynamically and asynchronously import JavaScript code within any function:\n\nconst loadStuff = async () =&gt; {\n  const importantStuff = await import(\"./important_stuff.js\")\n  return importantStuff.default()\n}\n\nconst doStuff = async () =&gt; {\n  const justDoIt = await loadStuff()\n  justDoIt(\"Don't let your dreams be dreams!\")\n}\n\n\nYou can learn more about dynamic imports on MDN.\n\n\n  \n  ES Module imports have been supported in all modern browsers since 2019, but if you wish to preserve backwards compatibility with older browsers, you&#8217;ll need to avoid using this technique.\n\n\nIslands Architecture\n\nStarting in Bridgetown 1.3, there’s an additional “convention over configuration” option for spreading JavaScript modules across various pages and keeping most code out of the main bundle. Check out our dedicated Islands documentation for how to set this up."
        },
        {
          "id": "docs-deployment",
          "title": "Deploy to Production",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "deployment",
          "tags": "",
          "url": "/docs/deployment",
          "content": "Bridgetown generates your site and saves it to the output directory by default. You can\ntransfer the contents of this directory to almost any hosting provider to make\nyour site go live.\n\nBridgetown’s included site template automatically provides a Rake task you can run to build both your frontend bundle and your static website:\n\nbin/bridgetown deploy\n\n\nas part of your deployment process, which will kick off both the frontend:build Rake task and the bridgetown build commands in that order.\n\n\n  \n  You must set the BRIDGETOWN_ENV\nenvironment variable to production on the machine or service that&#8217;s building the\nsite for deployment. Read more about environments here.\n\nThis will also help you if you wish to utilize additional logic within your site templates or plugins to determine what&#8217;s a &#8220;development&#8221; build vs. &#8220;production&#8221; build.\n\n\nTable of Contents\n\n  Automatic Deployment    \n      Statichost.eu\n      Render\n      Netlify\n      Fly.io\n      GitHub Pages\n      GitLab Pages        \n          Enable GZip &amp; Brotli compression for GitLab Pages\n        \n      \n    \n  \n  Manual Deployment    \n      rsync\n      Docker        \n          Static Site\n          Dynamic Site\n        \n      \n      Dokku\n      NGINX\n    \n  \n\n\nAutomatic Deployment\n\nWe recommend setting up an automatic deployment solution at the earliest opportunity. This way every time you push a commit up to your Git repository on a service such as Codeberg, your site is automatically rebuilt and deployed quickly.\n\nSome popular services include:\n\nStatichost.eu\n\nStatichost.eu is a privacy-first, GDPR-compliant static site hosting provider based in Europe, founded on the belief that it’s possible to run a web hosting company without processing any personal data.\n\nTo deploy a Bridgetown site on statichost.eu with a repo hosted on Codeberg (an open-source European Git forge):\n\n\n  Add a statichost.yml config file to specify a Docker image for the build process as well as the build command and public output folder. (See example below.)\n  Copy the repo “clone” URL (ending in .git) from Codeberg for use in creating a new site project on statichost.eu.\n  You’ll need to add a deploy key to your Codeberg repo. When viewing your repo, click Settings, then click Deploy keys in the sidebar.\n  Before completing your site setup, don’t forget to ensure the BRIDGETOWN_ENV=production environment variable is set!\n  Now statichost.eu will attempt a trial build. Assuming all goes well, you will then need to press the Publish button to complete the set (as well as set up your DNS appropriately).\n  Important: make sure you add a “Forgejo”-style webhook so for every push to Codeberg, your site rebuilds! Go to Settings &gt; Webhooks on Codeberg and make sure it will ping https://builder.statichost.eu/YOUR_SITE_NAME.\n\n\nIf you get stuck on some of these steps, Codeberg’s documentation should be able to help.\n\nHere’s an example statichost.yml config file:\n\n# Docker image to use for building\nimage: combos/ruby_node:3_22\n# Build command\ncommand: bundle install &amp;&amp; npm install &amp;&amp; bundle exec bridgetown deploy\n# Public directory\npublic: output\n\n\nRender\n\nRender provides the easiest cloud for all your static sites, APIs, databases, and containers. Render is a unified platform which can build and run apps and websites with free SSL, a global CDN, private networks, and auto deploys from Git. Use Render’s admin dashboard or write an “infrastructure as code” YAML file to configure all your services at once. The choice is yours.\n\nFor easy setup of Bridgetown sites on Render, we’ve provided this bundled configuration.\n\nNetlify\n\nNetlify is a web developer platform which focuses on productivity and global scale without requiring costly infrastructure. Get set up with continuous deployment, lead gen forms, one click HTTPS, and so much more.\n\nFor easy setup of Bridgetown sites on Netlify, we’ve provided this bundled configuration.\n\nFly.io\n\nFly.io is a platform that focuses on container based deployment. Their service transforms containers into micro-VMs that run on hardware all across the globe. The section below on Docker has some examples that can be used with Fly.\n\nGitHub Pages\n\nMuch like with GitLab, you can also deploy static sites to GitHub Pages. You can make use of GitHub Actions to automate building and deploying your site to GitHub Pages.\n\nBridgetown includes a bundled configuration to set up GitHub pages. You can apply it with the following command:\n\nbin/bridgetown configure gh-pages\n\n\nMake sure to update your repo’s GitHub Pages Settings at https://github.com/&lt;your-account&gt;/&lt;your-site&gt;/settings/pages to have the pages Source set to GitHub Actions. You’ll also likely need to set a base_path in your Bridgetown configuration unless you’re setting up a custom domain.\n\nGitLab Pages\n\n\n  \n  Help Needed! This Documentation page is in need of review and possible revision. Can you help us out? We’d greatly appreciate it! 😃👍\n\n\nGitLab pages can host static websites. Create a repository on GitLab,\nwhich we suppose is at https://gitlab.com/bridgetownrb/mysite\nAdd the following .gitlab-ci.yml file to your project, which we shall suppose is called mysite following the documentation setup instructions. The .gitlab-ci.yml file should be in the mysite directory created using bridgetown new mysite and should contain\n\nimage: ruby:2.6\n\ncache:\n  paths:\n  - vendor\n\n.setup:\n  script:\n    - apt-get update -yqqq\n    - curl -sL https://deb.nodesource.com/setup_20.x | bash -\n    - apt update\n    - apt-get install -y nodejs\n    - export GEM_HOME=$PWD/gems\n    - export PATH=$PWD/gems/bin:$PATH\n    - gem install bundler\n    - gem install bridgetown -N\n    - bundle install\n    - npm install\n\ntest:\n  script:\n    - !reference [.setup, script]\n    - bin/bridgetown deploy\n    - bin/bridgetown clean\n  except:\n    - main\n\npages:\n  script:\n    - !reference [.setup, script]\n    - bin/bridgetown deploy\n    - mv output public\n  artifacts:\n    paths:\n      - public\n  only:\n    - main\n\nOnce this file has been created, add it and the other files and folders to the repository, and then push them to GitLab:\n\ngit add .gitlab-ci.yml\ngit remote add origin https://gitlab.com/bridgetownrb/mysite\ngit add .\ngit commit -am \"initial commit\"\ngit push -u origin main\n\n\nAfter the build the site should be live at https://bridgetownrb.gitlab.io/mysite\n\nEnable GZip &amp; Brotli compression for GitLab Pages\n\nMost modern browsers support downloading files in a compressed format. This\nspeeds up downloads by reducing the size of files.\n\nBefore serving an uncompressed file, Gitlab Pages checks if the same file exists\nwith a .br or .gz extension. If it does, and the browser supports receiving\ncompressed files, it serves that version instead of the uncompressed one.\n\nThis can be achieved by including a script: command like this in your\n.gitlab-ci.yml pages job:\n\npages:\n  # Other directives\n  script:\n    # Add this line right after apt update\n    - apt-get install -y brotli\n    # Build the public/ directory first\n    - find public -type f -regex '.*\\.\\(htm\\|html\\|txt\\|text\\|js\\|css\\)$' -exec gzip -f -k {} \\;\n    - find public -type f -regex '.*\\.\\(htm\\|html\\|txt\\|text\\|js\\|css\\)$' -exec brotli -f -k {} \\;\n\n\nFor more details, see the documentation.\n\nManual Deployment\n\nThe most basic method of deployment is transferring the contents of your output folder to any web server. You can use something like scp to securely copy the folder, or you can use a more advanced tool:\n\nrsync\n\nRsync is similar to scp except it can be faster as it will only send changed\nparts of files as opposed to the entire file. You can learn more about using\nrsync in the Digital Ocean tutorial.\n\nDocker\n\nMany modern hosting solutions support deploying with a Dockerfile. To build a Bridgetown site for one of these services, create a Dockerfile in the root directory of your project. See the examples below.\n\nStatic Site\n\nIf you’re looking to deploy a static version of your site:\n\nFROM combos/ruby_node:3_22 AS builder\nENV BRIDGETOWN_ENV=production\n\nWORKDIR /opt/src\nCOPY . .\n\nRUN bundle install &amp;&amp; npm install &amp;&amp; bundle exec bridgetown deploy\n\nFROM lipanski/docker-static-website:latest\nCOPY --from=builder /opt/src/output .\nCMD [\"/busybox-httpd\", \"-f\", \"-v\", \"-p\", \"4000\"]\n\n\nDynamic Site\n\nIf you want to use the installed Rack-compliant web server directly for Dynamic Routes &amp; SSR support (it’s recommended you set up a caching layer in front for static assets like images, CSS, JS, etc.):\n\nFalcon\n\nFROM combos/ruby_node:3_22\nENV BRIDGETOWN_ENV=production\n\nWORKDIR /opt/src\nCOPY . .\n\nRUN bundle install &amp;&amp; npm install &amp;&amp; bundle exec bridgetown deploy\n\nEXPOSE 4000\nCMD bundle exec falcon host config/falcon.rb\n\n\nPuma\n\nFROM combos/ruby_node:3_22\nENV BRIDGETOWN_ENV=production\n\nWORKDIR /opt/src\nCOPY . .\n\nRUN bundle install &amp;&amp; npm install &amp;&amp; bundle exec bridgetown deploy\n\nEXPOSE 4000\nCMD bundle exec puma\n\n\nDokku\n\nDokku is great if you either want Heroku-style\ndeployments on a budget or you want more control over your server stack.\n\nThis guide assumes you’ve got a fully-functioning Dokku server up and running\nand created an app we’ll conveniently call bridgetown.\n\nFirst, add the following environment variables to your app on the server:\n\ndokku config:set bridgetown BRIDGETOWN_ENV=production NGINX_ROOT=output\n\n\nNext, create a file called .buildpacks at the root of your local project with\nthe following contents to tell Dokku about the app’s requirements:\n\nhttps://github.com/heroku/heroku-buildpack-ruby\nhttps://github.com/heroku/heroku-buildpack-nodejs\nhttps://github.com/dokku/buildpack-nginx\n\n\nAlso, create an empty file called .static in the same location. This file will\ntell dokku to run the app as a static website using Nginx.\n\nFinally, add the following line to the scripts section in your package.json:\n\n{\n  // ...\n  \"scripts\": {\n    // ...\n    \"heroku-postbuild\": \"bin/bridgetown deploy\",\n    // ...\n  },\n  // ...\n}\n\n\nThe nodejs buildpack will automatically run npm run heroku-postbuild at the right\ntime during the deployment process, so there is nothing left to do. You can now\nsafely deploy your application:\n\ngit push dokku\n\n\n… and watch your site being built on the server.\n\nNGINX\n\nUpload the output folder to somewhere accessible by NGINX and configure your server. Below is an example of conf file:\n\nserver {\n  server_name bridgetown.example.com;\n  index index.html;\n  root /var/www/bridgetown/output;\n\n  location / {\n    rewrite ^(.+)/+$ $1 permanent;\n    try_files $uri $uri/index.html $uri.html /index.html;\n    access_log /var/www/bridgetown/shared/log/nginx.access.log;\n    error_log /var/www/bridgetown/shared/log/nginx.error.log;\n  }\n\n  location ^~ /_bridgetown/ {\n    gzip_static on;\n    expires max;\n    add_header Cache-Control public;\n  }\n\n  listen 443 ssl;\n  # You can get a free SSL in https://freessl.cn or using let's encrypt certbot\n  ssl_certificate /etc/ssl/certs/bridgetown.example.com.pem;\n  ssl_certificate_key /etc/ssl/private/bridgetown.example.com.key;\n}\n\nserver {\n  if ($host = bridgetown.example.com) {\n      return 301 https://$host$request_uri;\n  }\n\n  listen 80;\n  server_name bridgetown.example.com;\n  return 404;\n}"
        },
        {
          "id": "docs-testing",
          "title": "Automated Testing",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "testing",
          "tags": "",
          "url": "/docs/testing",
          "content": "Running an automated test suite after your Bridgetown site has been built is a great way to ensure important content is available and formatted as you expect, and that some recent change hasn’t broken anything critical within your build process.\n\nTable of Contents\n\n  Use Ruby and Minitest to Test HTML Directly\n  Headless Browser Testing with Cypress\n\n\nUse Ruby and Minitest to Test HTML Directly\n\nBridgetown provides a bundled configuration to add gems for Minitest and [RackTest](https://github.com/rack/rack-test) and set up the test environment in the test folder. Once this is installed, you can also access `RackTest` DSL directly from the console.\n\nTo install, run the following command:\n\nbin/bridgetown configure minitesting\n\n\nYou can write tests to verify the output of both static and dynamic routes. Right when the test suite first runs, the Bridgetown site will be built (via the test environment) so that static pages are available. Then, the Roda server application will boot up in memory and you can make direct requests much as if you were using a full HTTP server.\n\nThe html and json helpers let you parse responses, either as a Nokolexbor document in the case of an HTML response, or JSON.parse in the case of a JSON response.\n\nHere’s an example of such a test:\n\nrequire \"minitest_helper\"\n\nclass TestBlog &lt; Bridgetown::Test\n  describe \"authors\" do\n    it \"shows the right avatar\" do\n      html get \"/blog\"\n\n      expect(document.query_selector_all(\".box .author img\").last.outer_html)\n        .must_equal('&lt;img src=\"/images/khristi-jamil-avatar.jpg\" alt=\"Khristi Jamil\" class=\"avatar\"&gt;')\n    end\n  end\nend\n\n\nThere are get, post, and delete methods available for testing various server routes. For more information, read the Rack::Test documentation. You can also access the Bridgetown site object loaded in memory via the site helper. For example, site.metadata.title would return your site’s title as defined in _data/site_metadata.yml.\n\nYou can create as many test files as you want to handle various parts of the site. Be advised that these tests are run via the server initialization context, so it’s possible something may not have run as you would expect under a static initialization context. But since the static site is already built prior to your tests being executed, it’s probably best for you to test static use cases via the output HTML.\n\nYou can add additional tests via test_* methods, and you can create as many test files as you want to handle various parts of the site. Be advised that these tests are run via the server initialization context, so it’s possible something may not have run as you would expect under a static initialization context. But since the static site is already built prior to your tests being executed, it’s probably best for you to test static use cases via the output HTML.\n\nThe Bridgetown::Test class includes support for spec-style blocks (describe, it, etc.) and expectations (expect(x).must_equal(y), etc.), or you can write test_* methods and assertions (assert_equal). Use whatever style you feel comfortable with when writing your tests. Bridgetown also provides a set of extensions to Minitest’s built-in Expectation class called Intuitive Expectations which lets you use more concise operators and “Rubyish” syntax.\n\n\n  Learn more about Minitest expectations and Intuitive Expectations.\n  Learn more about Minitest assertions.\n\n\nMethods you can override in a Bridgetown::Test subclass:\n\n\n  roda_app_class - default return value is RodaApp\n  roda_log_level - default return value is Logger::WARN (if you want to see all server logs when running tests, return Logger::INFO or Logger::DEBUG instead)\n\n\nHeadless Browser Testing with Cypress\n\nYou can install Cypress using a bundled configuration:\n\nbin/bridgetown configure cypress\n\n\nThe above command will add a cypress/ directory to your project. Within this directory you can see the integration/navbar.spec.js file as an example of how to write your tests.\n\nThe test suite can be run using:\n\nbin/bridgetown cy:test:ci\n\n\nA number of other useful commands are also installed along with Cypress:\n\n# Opens the Cypress test runner.\nbin/bridgetown cy:open\n\n# Starts the Bridgetown server and opens the Cypress test runner.\nbin/bridgetown cy:test\n\n# Runs the Cypress tests headlessly in the Electron browser.\nbin/bridgetown cy:run"
        },
        {
          "id": "docs-configuration",
          "title": "Customize Your Site",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "configuration",
          "tags": "",
          "url": "/docs/configuration",
          "content": "There are three ways you can configure your Bridgetown site and customize aspects of its build process or server infrastructure.\n\n\n  Using command line options (via the CLI)\n  Using the config/initializers.rb file, which is the most expressive way and provides deterministic support for loading in gem-based plugins.\n  Using the bridgetown.config.yml YAML config file (legacy setup)\n\n\nCLI: When you use a command line option, it looks something like this:\n\nbin/bridgetown build --future\n\n\nThis tells the build process to include posts and other resources which are future-dated.\n\nYou can read Bridgetown&#8217;s command line usage documentation here.\n\nInitializers: When you use the config/initializers.rb file, it looks something like this:\n\nBridgetown.configure do |config|\n  init :dotenv\n\n  config.autoload_paths &lt;&lt; \"jobs\"\n  url \"https://www.bridgetownrb.com\"\n  permalink \"pretty\"\n  timezone \"America/Los_Angeles\"\n  template_engine \"serbea\"\n\n  collections do\n    docs do\n      output true\n      permalink \"/:collection/:path.*\"\n      name \"Documentation\"\n    end\n  end\n\n  if Bridgetown.env.development?\n    unpublished true\n  end\n\n  only :server do\n    init :mail, password: ENV[\"SENDGRID_API_KEY\"]\n  end\nend\n\n\nThe initializer-style config is the most powerful, because you can configure different options for different contexts (static, server, console, rake), as well as interact with environment variables and other system features via full Ruby code. You can also initialize gem-based plugins and configure them in a single pass. And you can write your own initializers which may be called from the main configure block.\n\nYAML: When you use the bridgetown.config.yml file, it looks something like this:\n\nurl: \"https://www.bridgetownrb.com\"\npermalink: simple\ntimezone: America/Los_Angeles\ntemplate_engine: serbea\n\ncollections:\n  docs:\n    output: true\n    permalink: \"/:collection/:path.*\"\n    sort_by: order\n    name: Documentation\n\npagination:\n  enabled: true\n\n# Environment-specific settings\ndevelopment:\n  unpublished: true\n\n\nYou can learn more about the various configuration options in the links below.\n\n\n  \n  Processing Order\n\nValues from bridgetown.config.yml are processed first, then config/initializers.rb, command line arguments are processed last.\n\n\nTake a Deep Dive\n\n\n  Initializers\n  Configuration Options\n  Environments\n  External Content Sources\n  Markdown Options\n  Liquid Options\n  Web Server Configuration\n\n\nBeyond configuration, the way you&#8217;ll enhance and extend your site is through writing your own custom plugins. Continue reading for information on how to get started writing your first plugin or installing third-party plugins."
        },
        {
          "id": "docs-plugins",
          "title": "Extend with Plugins",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "plugins",
          "tags": "",
          "url": "/docs/plugins",
          "content": "Plugins allow you to extend Bridgetown’s behavior to fit your needs. You can write plugins yourself directly in your website codebase, or install gem-based plugins and themes for a limitless source of new features and capabilities.\n\nBe sure to check out our growing list of official and third-party plugins for ways to jazz up your website.\n\nWhenever you need more information about the plugins installed on your site and what they’re doing, you can use the bridgetown plugins list command. You can also copy content out of gem-based plugins with the bridgetown plugins cd command. Read the command reference for further details.\n\n\n  \n  Turn Your Plugins into Gems\n\nIf you&#8217;d like to maintain plugin separation from your site source code, share functionality across multiple projects, and manage dependencies, you can create a Ruby gem for private or public distribution. This is also how you&#8217;d create a Bridgetown theme.\n\nRead further instructions below on how to create and publish a gem.\n\n\nTable of Contents\n\n  Our Ruby API\n  Setup\n  Introduction to the Builder API    \n      Local Custom Plugins\n      Gem-based Plugins\n    \n  \n  Plugin Categories    \n      Helpers\n      Tags\n      Filters\n      HTTP Requests and the Resource Builder\n      Hooks\n      HTML &amp; XML Inspectors\n      Generators\n      Permalink Placeholders\n      Resource Extensions\n      Front Matter Loaders\n      Commands\n      Converters        \n          Priority Flag\n        \n      \n    \n  \n  Cache API\n  Zeitwerk and Autoloading\n  Creating a Gem\n\n\nOur Ruby API\n\nWhen writing a plugin for Bridgetown, you will be interacting with its Ruby API: objects like Bridgetown::Site, Bridgetown::Resource::Base, Bridgetown::GeneratedPage, etc. Other times you may be interacting with Liquid Drops, which are “safe” representations of the internal Ruby API for use in Liquid templates.\n\nDocumentation for Bridgetown’s class hierarchy is available on our API website.\n\nThe simplest way to debug the code you write is to run bridgetown console and interact with the API there. You can then copy working code into your plugin, or test out new ideas before committing them to your plugin code. You can also write binding.irb at any point in your code, and you’ll be dropped into a console when execution pauses at that point.\n\nNew in Bridgetown 2.0: we now offer a Foundation gem as part of the Bridgetown API with some handy helpers for strings, hashes, class hierarchies, and more. Read more about Bridgetown Foundation here.\n\nSetup\n\nThere are three methods of adding plugins to your site build.\n\n\n  \n    Within your site’s root folder, there’s a plugins folder. Write your custom plugins and save them here. Any file ending in .rb inside this folder will be loaded automatically before Bridgetown generates your site. Most plugins you write will likely be using the Builder API, so you can add them in plugins/builders.\n  \n  Add gem-based plugins to your Gemfile by running a command such as:\n    bundle add bridgetown-feed\n    \n    and then adding an init statement to your config/initializers.rb file (such as init :\"bridgetown-feed\").\n  \n  Run an automation which will install one or more gems along with other set up and configuration:\n    bin/bridgetown apply https://github.com/bridgetownrb/bridgetown-cloudinary\n    \n  \n\n\nIntroduction to the Builder API\n\nThe Builder API (with its various DSLs) is typically the approach you’ll use to write Bridgetown plugins.\n\nLocal Custom Plugins\n\nThe SiteBuilder class in your plugins folder provides the a superclass you can inherit from to create a new builder. In plugins/builders, you can create one or more subclasses of SiteBuilder and write your plugin code within the build method which is called automatically by Bridgetown early on in the build process (specifically during the pre_read event before content has been loaded from the file system).\n\n# plugins/builders/add_some_tags.rb\nclass Builders::AddSomeTags &lt; SiteBuilder\n  def build\n    liquid_tag \"cool_stuff\", :cool_tag\n  end\n\n  def cool_tag(attributes, tag)\n    \"This is so cool!\"\n  end\nend\n\n\nBuilders provide a couple of instance methods you can use to reference important data during the build process: site and config.\n\nSo for example you could add data with a generator:\n\nclass Builders::AddNewData &lt; SiteBuilder\n  def build\n    generator do\n      site.data.new_data = { new: \"New stuff\" }\n    end\n  end\nend\n\n\nAnd then reference that data in any template:\n\n{{ site.data.new_data.new }}\n\n   output: New stuff\n\n\nGem-based Plugins\n\nFor a gem-based plugin, all you have to do is subclass directly from Bridgetown::Builder, then define it within your plugin initializer (along with any other configuration set up).\n\n# lib/my_nifty_plugin/builder.rb\nmodule MyNiftyPlugin\n  class Builder &lt; Bridgetown::Builder\n    def build\n      this_goes_to = config.my_nifty_plugin.this_goes_to_11\n      # do other groovy things\n    end\n  end\nend\n\n# lib/my_nifty_plugin.rb\nBridgetown.initializer :my_nifty_plugin do |config, api_key: ''|\n  config.my_nifty_plugin ||= {}\n  config.my_nifty_plugin.this_goes_to_11 ||= 11\n  config.my_nifty_plugin.api_key = api_key\n\n  config.builder MyNiftyPlugin::Builder\nend\n\n\nAccepting keyword arguments is optional. The above example shows how you can use a keyword parameter to allow users to pass information from their initializers.rb file into your plugin. This example allows users to provide a api_key parameter from their initializer.rb file, and for this example it defaults to an empty string.\n\nBelow shows how a user could set the api_key parameter from within their initializers.rb.\nRefer to the initializers documentation for more about initializers..\n\n# config/initializers.rb\nBridgetown.configure do |config|\n  init :my_nifty_plugin do\n    api_key \"some-api-key\"\n  end\nend\n\n\n\nRead further instructions below on how to create and publish a gem.\n\n\n  \n  As a shortcut, your plugin can also define an inline builder directly within its initializer by passing a symbol and block to config.builder. Read this documentation to learn more.\n\n\nPlugin Categories\n\nThere are several categories of functionality you can add to your Bridgetown plugin:\n\nHelpers\n\nFor Ruby-based templates such as ERB, Serbea, etc., you can provide custom helpers which can be called from your content and design templates.\n\nTags\n\nFor Liquid-based templates, you can provide tags (aka “shortcodes”) which can be called from your content and design templates.\n\nFilters\n\nYou can provide custom Liquid filters to help transform data and content.\n\nHTTP Requests and the Resource Builder\n\nEasily pull data in from external APIs, and use a special DSL to build resources out of that data.\n\nHooks\n\nHooks provide fine-grained control to trigger custom functionality at various points in the build process.\n\nHTML &amp; XML Inspectors\n\nPost-process the HTML or XML output of resources using the Nokogiri Ruby gem and its DOM-like API.\n\nGenerators\n\nGenerators allow you to automate the creating or updating of content in your site using Bridgetown’s internal Ruby APIs.\n\nPermalink Placeholders\n\nDefine lambdas which will be run for any matching placeholders within a permalink.\n\nResource Extensions\n\nAdd new functionality to the resource objects in your site build.\n\nFront Matter Loaders\n\nAdd new types of front matter to the resource objects and layouts in your site.\n\nCommands\n\nCommands extend the bridgetown executable using the Samovar CLI toolkit.\n\nConverters\n\nConverters change a markup language from one format to another.\n\nPriority Flag\n\nYou can configure a plugin (builders, converters, etc.) with a specific priority flag. This flag determines what order the plugin is loaded in.\n\nThe default priority is :normal. Valid values are:\n\n:lowest, :low, :normal, :high, and :highest.\nHighest priority plugins are run first, lowest priority are run last.\n\nExamples of specifying this flag:\n\nclass Builders::DoImportantStuff &lt; SiteBuilder\n  priority :highest\n\n  def build\n    # do really important stuff here\n  end\nend\n\nclass Builders::CanWaitUntilLater &lt; SiteBuilder\n  priority :low\n\n  def build\n    # stuff that'll get run later (after the really important stuff)\n  end\nend\n\n\nCache API\n\nBridgetown features a Caching API which is used both internally as well as exposed for plugins and components. It can be used to cache the output of deterministic functions to speed up site generation.\n\nZeitwerk and Autoloading\n\nBridgetown uses an autoloading mechanism provided by Zeitwerk, the same code loader used by Rails and many other Ruby-based projects. Zeitwerk uses a specific naming convention so the paths of your Ruby files and the namespaces/modules/classes of your Ruby code are aligned. For example:\n\nplugins/my_plugin.rb         -&gt; MyPlugin\nplugins/my_plugin/foo.rb     -&gt; MyPlugin::Foo\nplugins/my_plugin/bar_baz.rb -&gt; MyPlugin::BarBaz\nplugins/my_plugin/woo/zoo.rb -&gt; MyPlugin::Woo::Zoo\n\n\nYou can read more about Zeitwerk’s file conventions here.\n\nIn addition to the plugins folder provided by default, you can add your own folders with autoloading support! Add to the autoload_paths setting in your config:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do |config|\n  config.autoload_paths &lt;&lt; \"loadme\"\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\nautoload_paths:\n  - loadme\n    \n\n  \n\n\nNow any Ruby file in your project’s ./loadme folder will be autoloaded. By default, files in your custom folders not “eager loaded”, meaning that the Ruby code isn’t actually processed unless/until you access the class or module name of the file somewhere in your code elsewhere. This can improve performance in certain cases. However, if you need to rely on the fact that your Ruby code is always loaded when the site is instantiated, set eager to true in your config:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do |config|\n  config.autoload_paths &lt;&lt; {\n    path: \"loadme\",\n    eager: true\n  }\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\nautoload_paths:\n  - path: loadme\n    eager: true\n    \n\n  \n\n\nThere may be times when you want to bypass Zeitwerk’s default folder-based namespacing. For example, if you wanted something like this:\n\nplugins/builders/tags.rb   -&gt; Builders::Tags\nplugins/helpers/hashify.rb -&gt; Hashify\n\n\nwhere the files in builders use a Builders namespace, but the files in helpers don’t use a Helpers namespace, you can use the autoloader_collapsed_paths setting:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do |config|\n  config.autoloader_collapsed_paths &lt;&lt; \"plugins/helpers\"\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\nautoloader_collapsed_paths:\n  - plugins/helpers\n    \n\n  \n\n\nAnd if you don’t want namespacing for any subfolders, you can use a glob pattern:\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    # config/initializers.rb\nBridgetown.configure do\n  autoloader_collapsed_paths &lt;&lt; \"top_level/*\"\nend\n    \n\n  \n  \n\n    # bridgetown.config.yml\nautoloader_collapsed_paths:\n  - top_level/*\n    \n\n  \n\n\nThus no files directly in top_level as well as any of its immediate subfolders will be namespaced (that is, no TopLevel module will be implied).\n\nCreating a Gem\n\nThe bridgetown plugins new NAME command will create an entire gem scaffold for you to customize and publish to the RubyGems.org\n(and for frontend assets: NPM) registries. This is a great way to provide themes, builders, helpers, and other sorts of add-on functionality to Bridgetown websites. You’ll want to make sure you update the gemspec, package.json, README.md, and CHANGELOG.md files as you work on your plugin to ensure all the necessary metadata and user documentation is present and accounted for.\n\n\n  \n  Starting with Bridgetown 1.2, it&#8217;s a preferred convention to use underscores for your plugin name, aka my_plugin rather than my-plugin. While some of the existing plugins in the ecosystem start with a bridgetown- prefix (such as bridgetown-seo-tag), going forward underscores are preferred as they are easy to specify in configuration files as symbols.\n\n\nBridgetown plugins should provide an initializer so that they can be easily required and configured via the user’s configuration block within config/initializers.rb. For greater backward compatibility, you might ensure at least simple configuration options can alternatively be provided using YAML in bridgetown.config.yml.\n\nMake sure you follow these instructions to integrate your plugin’s frontend code (if any) with the users’ esbuild setup. Also read up on Source Manifests if you have layouts, components, resources, static files, and other content you would like your plugin to provide.\n\nYou can also provide an automation via your plugin’s GitHub repository by adding bridgetown.automation.rb to the root of your repo. This is a great way to provide advanced and interactive setup for your plugin. More information on automations here.\n\n🚀 When you’re ready, publish your plugin gem to the RubyGems.org (and optionally NPM) registries. There are instructions on how to do so in the sample README that is present in your new plugin folder under the heading Releasing.\n\n📂 To include your new plugin in our Plugin Directory for discovery by Bridgetown site owners and to solicit feedback and improvements in the form of open source code collaboration &amp; discussion, you will need to submit a Pull Request to the Bridgetown repository. The PR should include the addition of a new Markdown file in the bridgetown-website/src/_plugins folder, using a subfolder naming scheme matching your forge username &amp; your project name (aka yourusername/your_plugin). The format of the Markdown file can be referenced by viewing other plugins listed there (here is an example).\n\n💬 Help is available! If you have any questions or need support in creating your plugin, check out our community resources. And for further benefits including 1:1 mentorship from the Bridgetown core team, learn more about the Bridgetown Center program.\n\n\n  \n  Testing Your Plugin\n\nAs you author your plugin, you&#8217;ll need a way to use the gem within a live Bridgetown site. The easiest way to do that is to use a relative local path in the test site&#8217;s Gemfile.\n\ngem \"my_plugin\", :path =&gt; \"../my_plugin\"\n\n\nYou would do something similar in your test site&#8217;s package.json as well (be sure to run npm link so NPM knows not to install your local path into node_modules):\n\n\"dependencies\": {\n  \"random-js-package\": \"2.4.6\",\n  \"my_plugin\": \"../my_plugin\"\n}\n\n\nYou may need to restart your server at times to pick up changes you make to your plugin (unfortunately hot-reload doesn&#8217;t always work with gem-based plugins).\n\nFinally, you should try writing some tests in the test folder of your plugin. These tests could ensure your content and APIs are working as expected and won&#8217;t break in the future as code gets updated."
        },
        {
          "id": "docs-themes",
          "title": "Themes",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "themes",
          "tags": "",
          "url": "/docs/themes",
          "content": "Themes are plugins you can add to your Bridgetown website which may provide layouts, content, components, and frontend assets, as well as perform other tasks to enhance the functionality of your site.\n\n\n  \n  You may be interested in Willamette, our first-party theme. Create blogs, portfolios, documentation sites, galleries, business dashboards, and more!\n\n\nYou install a theme the same way you’d install any plugin, either by running a command such as:\n\nbundle add really-cool-theme\n\n\nand adding init :\"really-cool-theme\" to your initializers file, or by applying an automation:\n\nbin/bridgetown apply https://github.com/super-great-themes/theme-one\n\n\nThe theme creator will typically provide instructions on how to use the provided theme files and enhancements. Perhaps you’ll use some stylesheets or JavaScript modules provided by the theme. Perhaps the theme will include components such as navbars or slideshows or ways to display new content types you can add to your site templates. Or perhaps the theme will come with layouts you can assign to your content such as posts or collection documents.\n\nSometimes you might want to copy files out of a theme and into your site repo directly. The bridgetown plugins cd command will help you do exactly that.\n\n\n  \n  Check out our plugins directory for a growing collection of themes\nand other useful plugins.\n\n\nCreating a Theme\n\nTo design a theme to distribute to others, create a standard gem-based plugin using the bridgetown plugins new NAME command. Follow that link for more on live testing strategies and how to release and publish your theme.\n\nYou’ll need to use a Source Manifest to instruct the Bridgetown build process where to find your theme files.\n\nTo provide frontend assets via esbuild, follow these instructions.\n\nTo aid your users in installing your plugin and setting up configuration options and so forth, add a bridgetown.automation.rb automation script to your theme repo.\n\nOur Willamette theme can help provide you a leg up…we’re more than happy if you base parts of your new theme on how Willamette sets up things!\n\nAnd as always, if you have any questions or need support in creating your theme, check out our community resources."
        },
        {
          "id": "docs-automations",
          "title": "Automations",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "automations",
          "tags": "",
          "url": "/docs/automations",
          "content": "You can write automation scripts which act on new or existing sites to perform\ntasks such as adding gems, updating configuration, inserting code, copying\nfiles, and much more.\n\nAutomations are similar in concept to Gatsby Recipes or Rails App Templates.\nThey’re uniquely powerful when combined with plugins, as an\nautomation can install and configure one or more plugins from a single script.\n\nYou could also write an automation to run multiple additional automations, and\napply that to a brand-new site to set everything up exactly how you want it in a\nrepeatable and automatic fashion.\n\nAutomations can be loaded from a local path, or they can be loaded from remote\nURLs including repositories. You can also run automation scripts from within Rake tasks, and the exact same automation DSL can be used directly within custom commands.\n\n\n  \n  As with any other case where you are executing code downloaded from the Internet, this is a potential security risk! Make sure you apply automations from only those sources you are able to trust (and verify)!\n\n\nRunning Automations\n\nFor a new site, you can apply an automation as part of the creation process\nusing --apply= or -a:\n\nbridgetown new mysite --apply=/path/to/automation.rb\n\n\nFor existing sites, you can use the apply command:\n\nbin/bridgetown apply /path/to/automation.rb\n\n\nIf you don’t supply any filename or URL to apply, it will look for\nbridgetown.automation.rb in the current working directory\n\nvim bridgetown.automation.rb # save an automation script\n\nbin/bridgetown apply\n\n\nRemote URLs to automation scripts are also supported, and GitHub repo or gist\nURLs, GitLab repos, and Codeberg repos are automatically transformed to locate the right file from their servers:\n\n# Install and configure the bridgetown-cloudinary gem\nbin/bridgetown apply https://github.com/bridgetownrb/bridgetown-cloudinary\n\n\nYou can also load a file other than bridgetown.automation.rb from a repo:\n\n# Set up a default configuration for Netlify hosting\nbin/bridgetown apply https://github.com/bridgetownrb/automations/netlify.rb\n\n\nWriting Automations\n\nAn automation script is nothing more than a Ruby code file run in the context\nof an instance of Bridgetown::Commands::Apply. Available to you are all the\nautomation tasks provided by Freyia, such\nas run to run a CLI executable, or ask to prompt the user for details, or\nsay_status to provide helpful messages in the terminal.\n\nYou can read documentation on file tasks, shell tasks, and Bridgetown-specific tasks.\n\nHere’s an example of an automation which creates a new file in a\nsite repo:\n\ncreate_file \"netlify.toml\" do\n  &lt;&lt;~NETLIFY\n    [build]\n      command = \"bin/bridgetown deploy\"\n      publish = \"output\"\n    [build.environment]\n      NODE_VERSION = \"22\"\n    [context.production.environment]\n      BRIDGETOWN_ENV = \"production\"\n  NETLIFY\nend\n\n\nBridgetown provides tasks which are useful specifically for working in the context\nof website projects.\n\nHere’s an example of a plugin’s bridgetown.automation.rb which adds itself\nas a gem to a site and updates configuration based on user input:\n\nsay_status \"Cloudinary\", \"Installing the bridgetown-cloudinary plugin...\"\n\ncloud_name = ask(\"What's your Cloudinary cloud name?\")\n\nadd_bridgetown_plugin \"bridgetown-cloudinary\"\n\nappend_to_file \"bridgetown.config.yml\" do\n  &lt;&lt;~YAML\n\n    cloudinary:\n      cloud_name: #{cloud_name}\n  YAML\nend\n\n\nThere is a whole variety of possible tasks at your disposal:\n\nadd_bridgetown_plugin \"my-plugin\" # bundle add…\nadd_npm_for_gem \"my-plugin\"  # npm install (looks up npm metadata in plugin gemspec)\nadd_npm_package \"-D some-package-name\"\n\n# add another gem, but still continue if there's a Bundler error\nrun 'bundle add some-other-gem --version \"&gt;= 4.1.0, &lt; 4.3.0\"', abort_on_failure: false\n\ncreate_builder \"my_nifty_builder.rb\" do # adds file in plugins/builders\n  &lt;&lt;~RUBY\n    class MyNeatBuilder &lt; SiteBuilder\n      def build\n        puts MyPlugin.hello\n      end\n    end\n  RUBY\nend\n\njavascript_import do # updates frontend/javascript/index.js\n  &lt;&lt;~JS\n    import { MyPlugin } from \"my-plugin\"\n\n    const myPlugin = MyPlugin.setup({\n      // configuration options\n    })\n  JS\nend\n\ncreate_file \"src/_data/plugin_data.yml\" do\n  &lt;&lt;~YAML\n    data:\n      goes:\n        here\n  YAML\nend\n\ncolor = ask(\"What's your favorite color?\")\n\nappend_to_file \"bridgetown.config.yml\" do\n  &lt;&lt;~YAML\n\n    my_plugin:\n      favorite_color: #{color}\n  YAML\nend\n\n\nIn summary, automations are a fantastic method of saving repeatable setup\nsteps for you to reuse later in new projects, or you can share scripts with\nthe world at large. Use them for plugins, themes, or quick one-off\nscripts."
        },
        {
          "id": "docs-internationalization",
          "title": "Internationalization (I18n)",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "i18n",
          "tags": "",
          "url": "/docs/internationalization",
          "content": "Internationalization, or i18n as it’s commonly known, is the process of defining content and templates for your site in such a way as to support multiple languages or locales. Whether that’s French vs. Chinese, or American English vs. British English, it gives you the ability reach more diverse audiences and constituencies.\n\nStarting in Bridgetown 1.1, you can configure multiple locales for your website and set which particular locale should be considered “the default”. There are a couple of different routing options available:\n\n\n  The default locale URLs start at the root of the site (/), and other locales start at a locale-specific prefix (/es, /de), etc.\n  All locales start at a locale-specific prefix, and the root (/) redirects to the “default” locale at its designated prefix.\n\n\nRouting options not currently supported by Bridgetown at present are the subdomain config (www.mysite.com, es.mysite.com, zh.mysite.com, etc.) and the international domains config (www.mysite.com, www.mysite.co.uk, etc.). However, if you’re able to work within a prefix-style config (MDN is a good example of this type of approach out in the wild), Bridgetown is here for you.\n\n\n  \n  Bridgetown uses the Ruby I18n gem to aid in storing and accessing translations, the same library used by Ruby on Rails. Thus many of the same conventions will apply if you&#8217;re already familiar with i18n in Rails.\n\n\nTable of Contents\n\n  Setup &amp; Translations    \n      Variable Interpolation\n      Relative page keys\n      Automatic HTML key safety\n    \n  \n  Localizing dates\n  Localizing Your Resources and Templates    \n      Separate Files\n      Multi-Locale Files\n      Switching Between Locales\n      Creating Localized Paths &amp; Filtering Collections\n      Pagination and Prototype Pages\n      Updating &lt;head&gt; and other places\n    \n  \n  We Value Your Feedback\n\n\nSetup &amp; Translations\n\nFirst, you’ll want to define your locales in config/initializers.rb or bridgetown.config.yml. There are three configuration options:\n\n\n  available_locales: This is an array of locales you wish to support on your site. They can be simple language codes (es for Spanish, th for Thai, etc.), or they can also include regional differences known as subtags (pt-BR for Brazilian Portuguese, pt-PT for Portuguese as spoken in Portugal, etc.). You can look up various languages and subtags here. An example value for English, French, and German would be: [en, fr, de].\n  default_locale: This the locale you wish to consider the “default” for your site (aka the locale a visitor would first encounter before specifically choosing a locale). The default is English: en.\n  prefix_default_locale: As mentioned above, you can either have default locale URLs live within the root of your site, or you can set this to true to have the root direct to the default locale’s prefix.\n\n\n\n  Ruby\n  YAML (Legacy)\n\n  \n\n    Bridgetown.configure do |config|\n  available_locales [:en, :fr, :de]\n  default_locale :en\n  prefix_default_locale false\nend\n    \n\n  \n  \n\n    available_locales: [en]\ndefault_locale: en\nprefix_default_locale: false\n    \n\n  \n\n\nOnce you’ve completed your initial configuration, create a src/_locales folder and add files in YAML, JSON, or Ruby hash format for your locale translations. The first key of the data structure should be the locale, with various hierarchies of subkeys as you deem fit. Here’s an example of a en.yml file:\n\nen:\n  site:\n    title: Local Listings\n    tagline: The best homes you'll find anywhere in the area.\n  welcome:\n    intro: Welcome to Local Listings! Enjoy our fine selection.\n\n\nAnd here’s a example of a es.rb file:\n\n{\n  es: {\n    site: {\n      title: \"Listados Locales\",\n      tagline: \"Las mejores casas que encontrará en cualquier lugar de la zona.\"\n    },\n    welcome: {\n      intro: \"¡Bienvenido a los listados locales! Disfruta de nuestra fina selección.\"\n    }\n  }\n}\n\n\nWithin your templates, you’ll now be able to use the t Liquid tag or filter, or the t Ruby helper to reference these keys. For example, in Liquid:\n\n{% t site.title %} &lt;!-- tag style --&gt;\n{{ \"site.tagline\" | t }} &lt;!-- filter style --&gt;\n\n\nand in ERB:\n\n&lt;%= t(\"welcome.intro\") %&gt;\n\n\nThe Ruby helper in particular also supports some additional functionality.\n\nVariable Interpolation\n\nIf you store a translation like this:\n\nen:\n  products:\n    price: \"$%{price}\"\n\n\nThen you can pass that variable to the t helper:\n\n&lt;%= t(\"products.price\", price: resource.data.price) %&gt;\n\n\nRelative page keys\n\nIf you start your translation key starts with a period, we’ll automatically scope the key to the page. For example, if the page is about.html:\n\n&lt;%= t(\".foo\") %&gt;\n\n\nWill retrieve the key from:\n\nen:\n  about:\n    foo: Foo\n\n\nOr if the page is contact/about.html:\n\nen:\n  contact:\n    about:\n      foo: Foo\n\n\nAutomatic HTML key safety\n\nIf your translation key ends with _html or is html, it will automatically be marked html_safe.\n\nen:\n  products:\n    tagline_html: The &lt;strong&gt;best&lt;/strong&gt; product!\n\n\nThere are many other useful features of the i18n gem, so feel free to peruse the Rails Guide to Internationalization for additional documentation.\n\n\n  \n  In Ruby, the t helper is shorthand for I18n.t, so if you find yourself in a context where t is not available—perhaps in a plugin—you can write I18n.t directly.\n\n\nLocalizing dates\n\nTo ensure dates are displayed using the active locale, you’ll need to localize them using I18n.l, or use the provided shortcut: l.\n\nBy adding gem \"rails-i18n\" to your Gemfile, you’ll get localized month and day names, along with standard date and time formats in each locale supported by that gem.\nYou can also define your own localization for date and time.\n\n\n  \n  In Ruby, the l helper is shorthand for I18n.l, so if you find yourself in a context where l is not available—perhaps in a plugin—you can write I18n.l directly.\n\n\nLocalizing Your Resources and Templates\n\nBeyond using simple translated strings, you will want to ensure you have translated variants of your content and that you can switch freely between the locale variants. There are two ways to do this:\n\nSeparate Files\n\nCreate a separate resource file for each locale. You can either use the locale front matter key to set the locale of a file, or you can include the locale in the filename itself. For example: about.en.md, about.fr.md, about.it.md, etc. You can do this for any type resource, whether it’s a page, blog post, or some other custom collection.\n\nMulti-Locale Files\n\nIf the same resource should be available to multiple locales, use a single resource file in “multi locale” mode and use special front matter and template syntax to include translated content. You can switch to this mode by setting locale: multi in your front matter or using the .multi extension within your file name. For example: about.multi.md. This will generate resources in all of your site.config.available_locales.\n\nIf you want to only output a limited set of locales, then use the locales front matter key to include only the locales that should be written.\n\n---\ntitle: My Title\nlocale: multi\nlocales:\n  - en\n  - es\n  - de\n---\n\n\nIf you want to override front matter values per-locale, then you use the locale_overrides front matter key to include keys which will overwrite the default keys for all locales other than the default. Here’s an example:\n\n---\ntitle: My Title\nlocale_overrides:\n  es:\n    title: Mi Título\n  de:\n    title: Mein Titel\n---\n\n\nThen in the body of the resource, you can use conditional template syntax to check the value of the site.locale variable. (And of course this works in any layout template as well.) Using ERB syntax:\n\n&lt;% if site.locale == :en %&gt;\n\nHere's my content in **English**.\n\n&lt;% elsif site.locale == :es %&gt;\n\nAquí está mi contenido en **Español**.\n\n&lt;% elseif site.locale == :de %&gt;\n\nHier sind meine Inhalte auf **Deutsch**.\n\n&lt;% end %&gt;\n\n\nSwitching Between Locales\n\nYou can use a resource’s all_locales method to get a list of all matching translated resources. This is perfect for a section of your navbar or site footer which could allow the reader to switch to their preferred locale.\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% resource.all_locales.each do |local_resource| %&gt;\n  &lt;a href=\"&lt;%= local_resource.relative_url %&gt;\"&gt;&lt;%= t(local_resource.data.locale) %&gt;&lt;/a&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% for local_resource in resource.all_locales %}\n  &lt;a href=\"{{ local_resource.relative_url }}\"&gt;{{ local_resource.data.locale | t }}&lt;/a&gt;\n{% endfor %}\n    \n\n  \n\n\nCreating Localized Paths &amp; Filtering Collections\n\nThe in_locale filter/helper can help you link to another part of the site within the currently rendering locale, such as in navbars, sidebars, footers, etc.\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;a href=\"&lt;%= relative_url in_locale('/posts') %&gt;\"&gt;&lt;%= t \"nav.posts\" %&gt;&lt;/a&gt;\n    \n\n  \n  \n\n    &lt;a href=\"{{ '/posts' | in_locale | relative_url }}\"&gt;{% t nav.posts %}&lt;/a&gt;\n    \n\n  \n\n\nIn addition, if you’re accessing and looping through a collection directly, you can use the in_locale filter/helper there as well to filter out those resources not in the current locale.\n\n\n  ERB\n  Liquid\n\n  \n\n    &lt;% posts = in_locale(collections.posts.resources) %&gt;\n&lt;% posts.each do |post| %&gt;\n  &lt;li&gt;\n    &lt;a href=\"&lt;%= post.relative_url %&gt;\"&gt;&lt;%= post.data.title %&gt;&lt;/a&gt;\n  &lt;/li&gt;\n&lt;% end %&gt;\n    \n\n  \n  \n\n    {% assign posts = collections.posts.resources | in_locale %}\n{% for post in posts %}\n  &lt;li&gt;\n    &lt;a href=\"{{ post.relative_url }}\"&gt;{{ post.data.title }}&lt;/a&gt;\n  &lt;/li&gt;\n{% endfor %}\n    \n\n  \n\n\nPagination and Prototype Pages\n\nWhether you use one-file-per-locale or multi-locale files technique, your paginated pages and prototype pages will similarly filter out any resources not in the current locale whenever you access paginator.resources.\n\nUpdating &lt;head&gt; and other places\n\nLocalize any other string with t there as well, such as the site title or tagline.\n\nWe Value Your Feedback\n\nI18n is hard to get right, and there can be confusing or unexpected edge cases to work through. We value your questions and your suggestions on how to make Bridgetown a great platform for multi-locale websites and apps."
        },
        {
          "id": "docs-variables",
          "title": "Variables",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "variables",
          "tags": "",
          "url": "/docs/variables",
          "content": "Bridgetown makes a variety of data available to templates. Files with front matter are subject to processing during the static generation process, and you can also use many of the same objects in dynamic routes as well.\n\nThe following is an overview of commonly-available data. We also have a Ruby API Reference available.\n\nGlobal Variables\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      site\n      Site wide information + configuration settings. See below for details.\n    \n    \n      resource\n      Resource front matter and other content. Custom variables set via the front matter will be available here. See below for details.\n    \n    \n      layout\n      Layout specific information + the front matter. Custom variables set via front matter in layouts will be available here.\n    \n    \n      yield (Ruby), content (Liquid)\n      In layout files, the rendered content of the resource being wrapped. Not defined in resource files themselves.\n    \n    \n      environment / env\n      The deployment environment (development, production, etc.) is obtained from the Bridgetown instance Bridgetown.environment (or Bridgetown.env) in Ruby templating and bridgetown.environment in Liquid.\n    \n  \n\n\nSite Variables\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      site.time\n      The current time (when you run the bridgetown command).\n    \n    \n      site.resources\n      A list of all resources (from pages, posts and other collections).\n    \n    \n      site.static_files\n      A list of all static files (i.e. files not processed by Bridgetown's converters or the Liquid renderer). Each file has five properties: path, modified_time, name, basename and extname.\n    \n    \n      site.collections\n      A list of all the collections (including posts).\n    \n    \n      site.data\n      A list containing the data loaded from the YAML files located in the _data directory.\n    \n    \n      site.categories.CATEGORY\n      The list of all resources in category CATEGORY.\n    \n    \n      site.tags.TAG\n      The list of all resources with tag TAG.\n    \n    \n      site.config.url (Ruby) / site.url (Liquid)\n      Contains the url of your site as configured (for example, url \"http://mysite.com\"). For the development environment there is an exception: site.config.url will be set to the value of your localhost, as if you had manually configured url \"http://localhost:4000\".\n    \n    \n      site.metadata\n      You can put metadata variables in _data/site_metadata.yml so they'll be easy to access and will regenerate pages when changed. This is a good place to put &lt;head&gt; content like site title, description, icon, social media handles, etc. Then you can reference site.metadata.title, etc. in your templates.\n    \n    \n      site.config.[KEY] (Ruby) / site.[KEY] (Liquid)\n      All the variables set via the command line and your configuration are available through the site.config variable. For example, if you have foo \"bar\" in your initializer, then it will be accessible site.config.foo (or site.foo in Liquid). Bridgetown does not parse changes to config files during live reload—you must restart Bridgetown to see changes to variables.\n    \n  \n\n\nResource Variables\n\n\n  \n    \n      Variable\n      Description\n    \n  \n  \n    \n      resource.content\n      The content of the resource (Markdown, HTML, etc.).\n    \n    \n      resource.summary\n      An excerpt of the resource from the configured summary service.\n    \n    \n      resource.relative_url\n      The URL of the resource without the domain, but with a leading slash, e.g. /2008/12/14/my-post/\n    \n    \n      resource.date\n      The Date assigned to the resource. This can be overridden in front matter by specifying a new date/time in the format YYYY-MM-DD HH:MM:SS (assuming UTC), or YYYY-MM-DD HH:MM:SS +/-TTTT (to specify a time zone using an offset from UTC. e.g. 2008-12-14 10:30:00 +0900).\n    \n    \n      resource.id\n      An identifier unique to the resource and its collection (useful in RSS feeds). e.g. repo://posts.collection/2021-10-05-my-post.md\n    \n    \n      resource.data.categories\n      The list of categories to which the resource belongs, which can be specified in the front matter.\n    \n    \n      resource.data.tags\n      The list of tags to which the resource belongs. These can be specified in the front matter.\n    \n    \n      resource.taxonomies\n      Access a more comprehensive list of taxonomy objects including custom taxonomies.\n    \n    \n      resource.relations\n      Other resources related to this one if you have relations set up.\n    \n    \n      resource.collection\n      The collection to which this resource belongs. Information on collection variables here.\n    \n    \n      resource.relative_path\n      The path of the resource relative to the source folder.\n    \n    \n      resource.next\n      The next resource relative to the position of the current resource in its collection. Returns nil for the last entry.\n    \n    \n      resource.previous\n      The previous resource relative to the position of the current resource in its collection. Returns nil for the first entry.\n    \n  \n\n\n\n  \n  Using Custom Front Matter\n\nAny custom front matter that you specify will be available under\nresource. For example, if you specify custom_css: true\nin a resource’s front matter, that value will be available as resource.data.custom_css.\n\nIf you specify front matter in a layout, access that via layout.\nFor example, if you specify class: full_page in a layout’s front matter,\nthat value will be available as layout.data.class in the layout."
        },
        {
          "id": "docs-routes",
          "title": "Server-Rendered Routes",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "routes",
          "tags": "",
          "url": "/docs/routes",
          "content": "Bridgetown comes with a production-ready Rack-compliant web server installed. The default is Falcon, but you can choose Puma if you wish: bridgetown new --server puma.\n\nTo serve traffic, we use Roda, a refreshingly fast &amp; lightweight web routing toolkit created by Jeremy Evans. On a basic level, it handles serving of all statically-built site files you access when you run bin/bridgetown start.\n\nBridgetown lets you create your own Roda-based API routes in the server/routes folder. An example ships in each new Bridgetown project for you to examine (server/routes/hello.rb.sample). These routes provide the standard features you may be accustomed to if you’ve used Roda standalone.\n\nHowever, to take full advantage of all the Bridgetown has to offer, we recommend you load up our SSR and Dynamic Routes plugins. Add to your configuration in config/initializers.rb:\n\ninit :ssr # add `sessions: true` if you want to save session data, use flash, etc.\n# or:\ninit :\"bridgetown-routes\" # inits ssr automatically\n\n\n\n  \n  For more information on setup, read our documentation on configuring Roda and on configuring the web server.\n\n\nTable of Contents\n\n  Bridgetown SSR via Roda    \n      Priority Flag\n      Accessing the Current Site, Collections, and Resources\n    \n  \n  Rendering Viewable Components\n  File-based Dynamic Routes    \n      Route Template Delimiters, Front Matter Syntax\n      Routes in Islands Architecture\n      Adding Additional Route Paths\n    \n  \n  URL Helpers\n  Callable Objects for Rendering within Blocks\n  Building Web Applications With Roda\n\n\nBridgetown SSR via Roda\n\nServer-Side Rendering, known as SSR, has made its peace with SSG (Static Site Generation), and we are increasingly seeing an SSG/SSR “hybrid” architecture emerge in tooling throughout the web dev industry.\n\nBridgetown takes advantage of this evolving paradigm by providing a streamlined path for booting a site up in-memory. This means you can write a server-side API to render content whenever it is requested. Here’s an example of what that looks like:\n\n# ./server/routes/preview.rb\n\nclass Routes::Preview &lt; Bridgetown::Rack::Routes\n  route do |r|\n    r.on \"preview\" do\n      # Our special rendering pathway to preview a page\n      # route: /preview/:collection/:path\n      r.get String, String do |collection, path|\n        item = Bridgetown::Model::Base.find(\"repo://#{collection}/#{path}\")\n\n        if item.content.empty?\n          response.status = 404\n          next Bridgetown::Model::Base.find(\"repo://pages/_pages/404.html\")\n        end\n\n        item\n      end\n    end\n  end\nend\n\n\nThis route handles any /preview/:collection/:path URLs which are accessed like any other statically-generated resource. It will find a content item via a repo origin ID and render that item as HTML. For example: /preview/posts/_posts%2F2022-01-10-hello-world.md would SSR the Markdown content located in src/_posts/2022-01-10-hello-world.md.\n\n\n  \n  If you&#8217;re wondering &#8220;but, uh, where&#8217;s the HTML rendering part?!&#8221;, the Bridgetown Roda configuration automatically handles the rendering of any models or resources which are returned in a route block. This is based on a &#8220;callable&#8221; interface which you can also use for your own custom objects! More on that down below.\n\n\nSSR is great for generating preview content on-the-fly, but you can use it for any number of instances where it’s not feasible to pre-build your content. In addition, you can use SSR to “refresh” stale content…for example, you could pre-build all your product pages statically, but then request a newer version of the page (or better yet, just a fragment of it) whenever the static page is viewed which would then contain the up-to-date pricing (perhaps coming from a PostgreSQL database or some other external data source). And if you cache that data using Redis in, say, 10-minute increments, you’ve built yourself an extremely performant e-commerce solution. This is only a single example!\n\nPriority Flag\n\nYou can configure a Routes class with a specific priority flag. This flag determines what order the router is loaded in relative to other routers.\n\nThe default priority is :normal. Valid values are:\n\n:lowest, :low, :normal, :high, and :highest.\nHighest priority plugins are run first, lowest priority are run last.\n\nExamples of specifying this flag:\n\nclass Routes::InitialSetup &lt; Bridgetown::Rack::Routes\n  priority :highest\n\n  route do |r|\n    r.session[:adding_this] ||= \"value\"\n  end\nend\n\nclass Routes::LaterOn &lt; Bridgetown::Rack::Routes\n  route do |r|\n    r.get \"later\" do\n      { session_value: r.session[:adding_this] } # :session_value =&gt; \"value\"\n    end\n  end\nend\n\n\nAccessing the Current Site, Collections, and Resources\n\nYou can use the site helper (also aliased bridgetown_site) in your Roda code to access the current site object. From there, you can access data, collections, and resources for aid in rendering. For example, if you knew of a particular resource by title you wanted to find, you could write:\n\nsite.collections.posts.find { _1.data.title == \"My Post\" }\n\n\nYou can return a resource at the end of any Roda block to have it render out automatically, or you could pass it along as data to some other resource, or use some resource data within a return string or hash value (which autoconverts to JSON).\n\n\n  \n  Performance considerations around loaded content\n\nBy default, all available collections are read in when the Roda server boots up. This might not be a big deal in production since it&#8217;s a one-time procedure, but bear in mind that on large sites, having all that data loaded in memory could prove costly. In addition, in development, any time you make a change to a file and the site rebuilds, resources are re-read into memory.\n\nYou can configure collections, including even the built-in pages and posts collections, to be skipped when your site&#8217;s running in SSR mode. Set skip_for_ssr to true for collection metadata in your config file. For example, to skip reading posts in config/initializers.rb:\n\nBridgetown.configure do\n  # other configuration here\n\n  collections do\n    posts do\n      skip_for_ssr true\n    end\n  end\nend\n\n\nMost of the time though, on modestly-sized sites, this shouldn&#8217;t prove to be a major issue.\n\n\nRendering Viewable Components\n\nFor a traditional “VC” part of the MVC (Model-View-Controller) programming paradigm, Bridgetown provides a Viewable mixin for components. This lets you offload the rendering of a view to a component, keeping your Roda route very clean.\n\n# ./server/routes/products.rb\n\nclass Routes::Products &lt; Bridgetown::Rack::Routes\n  route do |r|\n    r.on \"products\" do\n      # route: /products/:sku\n      r.get String do |sku|\n        # Tip: check out bridgetown_sequel plugin for database connectivity!\n        Views::Product.new product: Product.find(sku:)\n      end\n    end\n  end\nend\n\n# ./src/_components/views/product.rb\n\nclass Views::Product &lt; Bridgetown::Component\n  include Bridgetown::Viewable\n\n  def initialize(product:)\n    @product = product\n\n    data.title = @product.title\n  end\n\n  # @param app [Roda] this is the instance of the Roda application\n  def call(app)\n    render_with(app) do\n      layout :page\n      page_class \"product\"\n    end\n  end\nend\n\n# ./src/_components/views/product.erb is an exercise left to the reader\n\n\nRead more about the callable objects pattern below.\n\nFile-based Dynamic Routes\n\nBut wait, there’s more! We also provide a plugin called bridgetown-routes which gives you the ability to write file-based dynamic routes with integrated view templates right inside your source folder.\n\nTo opt-into the bridgetown-routes gem, make sure it’s enabled in your Gemfile:\n\ngem \"bridgetown-routes\"\n\n\nand added in config/initializers.rb:\n\ninit :\"bridgetown-routes\"\n\n\nA file-based route is comprised of two parts:\n\n\n  A Roda block at the top, contained within special delimiters. This block is processed initially when the route URL is accessed, and before any template rendering has begun. You can include Ruby front matter here, just like you would with static resource content.\n  A view template underneath the Roda block, which is rendered in the correct format based on the file extension (ERB, etc.) and has access to front matter and local variables from the Roda block.\n\n\nA Roda block can contain a single route handler via r.get, or you can add additional handling of HTTP methods (r.post, etc.) or even sub-routes—though it’s recommended to stay simple and use individual file-based routes as much as possible. Note that if even if you define multiple route types in your Roda block, you can only have a single template per-route.\n\nLet’s take a look at how this all works. First, an example of a route saved to src/_routes/items/index.erb. It provides the /items URL which shows a list of item links:\n\n---&lt;%\n# route: /items\nr.get do\n  # sample data:\n  items = [\n    { number: 1, slug: \"123-abc\" },\n    { number: 2, slug: \"456-def\" },\n    { number: 3, slug: \"789-xyz\" },\n  ]\n\n  render_with do\n    layout :page\n    title \"Dynamic Items\"\n  end\nend\n%&gt;---\n\n&lt;ul&gt;\n  &lt;% items.each do |item| %&gt;\n    &lt;li&gt;&lt;a href=\"/items/&lt;%= item[:slug] %&gt;\"&gt;Item #&lt;%= item[:number] %&gt;&lt;/a&gt;&lt;/li&gt;\n  &lt;% end %&gt;\n&lt;/ul&gt;\n\n\nSince all the data in the above example is created and rendered by the server in real-time, there’s no way to know ahead of time which routes should be accessible via /items/:slug. That’s why bridgetown-routes supports routing placeholders at the filesystem level! Let’s go ahead and define our item-specific route in src/_routes/items/[slug].erb:\n\n---&lt;%\n# route: /items/:slug\nr.get do\n  item_id, *item_sku = r.params[:slug].split(\"-\")\n  item_sku = item_sku.join(\"-\")\n\n  render_with do\n    layout :page\n    title \"Item Page\"\n  end\nend\n%&gt;---\n\n&lt;p&gt;&lt;strong&gt;Item ID:&lt;/strong&gt; &lt;%= item_id %&gt;&lt;/p&gt;\n\n&lt;p&gt;&lt;strong&gt;Item SKU:&lt;/strong&gt; &lt;%= item_sku %&gt;&lt;/p&gt;\n\n\n\nThis is a contrived example of course, but you can easily imagine loading a specific item from a data source based on the incoming parameter(s) and providing that item data to the view, all within a single file.\n\nYou can even use placeholders in folder names! A route saved to src/_routes/books/[id]/chapter/[chapter_id].erb would match to something like /books/234259/chapter/5 and let you access r.params[:id] and r.params[:chapter_id]. Pretty nifty.\n\n\n\nRoute Template Delimiters, Front Matter Syntax\n\nBridgetown lets you use a few different delimiters for the Roda block at the top, depending on your template format. For example, ---&lt;% and %&gt;--- would work well for an .erb file, but ###ruby and ### would be ideal for an .rb file.\n\nSee Ruby Front Matter for additional details.\n\nThe Roda block also excepts a couple of different styles of specifying front matter. You can use render_with do ... end as in the examples above, but you can also use a data hash instead:\n\nhsh = { layout: :page, title: \"I'm a Page!\" } \n\nrender_with(data: hsh)\n\n\nNote that if you use that syntax, additional local variables will not be copied down to the view template.\n\nFinally, if you only need to use local variables within your front matter, you can omit render_with entirely:\n\n---&lt;%\nreferrer = params[:ref]\nreferrer = \"=)\" unless AllowedValidator.valid?(referrer) # just a demo\n\ntitle = \"Thank You!\"\n%&gt;---\n\n&lt;h1&gt;&lt;%= title %&gt;&lt;/h1&gt;\n\n&lt;p&gt;We appreciate your business, &lt;%= referrer %&gt;&lt;/h1&gt;\n\n\nRoutes in Islands Architecture\n\nYou can add routes folders inside of one or more islands. For example, you could add a route file at src/_islands/paradise/routes/dreamy.erb, and the URL would then resolve to the island name plus the route name (/paradise/dreamy). If you name your route file index.(ext), then the route path would be only the island name (/paradise).\n\nFor more information about islands, read our Islands Architecture documentation.\n\nAdding Additional Route Paths\n\nIf you’d like to add any arbitrary path as a location for route files—even outside of the project root—you can do so in your config/initializers.rb:\n\nBridgetown.configure do |config|\n  # configuration here\n\n  init :\"bridgetown-routes\", additional_source_paths: File.expand_path(\"more_routes\", \"#{root_dir}/..\")\nend\n\n\nURL Helpers\n\nYou can use the relative_url and absolute_url helpers within your Roda code any time you need to reference a particular URL, to ensure any base path or locale prefix gets added automatically. It also will work with any object which responds to a method like relative_url or url. For example:\n\nr.redirect relative_url(\"/path/to/page\")\n\nr.redirect relative_url(obj)\n\n\nCallable Objects for Rendering within Blocks\n\nWhen authoring Roda blocks, you have the option of returning resources to be rendered out as HTML or other text-based formats.\n\nBut it’s also possible to return any object which includes the Bridgetown::RodaCallable mixin and defines a call method which accepts the Roda application as the argument. For example, if you wrote an object which generates an RSS feed, you could use it like so:\n\nclass MyRssFeed\n  include Bridgetown::RodaCallable\n\n  def call(app)\n    app =&gt; { request:, response: } # now you have those as local variables\n\n    feed_xml = generate_the_feed # an exercise left to the reader\n\n    response[\"Content-Type\"] = \"application/rss+xml\" # set the correct content type\n    feed_xml # return XML string\n  end  \nend\n\n\n# Use the object directly in a Roda block:\nr.get \"/my-feed.xml\" do\n  MyRssFeed.new\nend\n\n\nFor the Roda-curious, we’ve enabled this behavior via our own custom handler for the custom_block_results Roda plugin.\n\nAnd as mentioned previously, the Viewable component mixin is a wrapper around RodaCallable to add some extra smarts to Ruby components:\n\n\n  You can access the data hash from within your component to add and retrieve front matter for the view.\n  You can call front_matter with a block to define Ruby Front Matter for the view.\n  From your call(app) method, you can call render_in_layout(app) to render the component template within the layout defined via your front matter.\n  Or for a shorthand, call render_with(app) do ... end to specify Ruby Front Matter and render the template in one pass.\n\n\nYou can even cascade multiple callable objects, if you really want a full object-oriented MVC experience:\n\n# ./server/routes/products.rb\n\nclass Routes::Reports &lt; Bridgetown::Rack::Routes\n  route do |r|\n    r.on \"reports\" do\n      # route: /reports/:id\n      r.get Integer do |id|\n        Controllers::Reports::Show.new(id:)\n      end\n    end\n  end\nend\n\n# ./server/controllers/reports/show.rb\n\nclass Controllers::Reports::Show\n  include Bridgetown::RodaCallable\n\n  def initialize(id:)\n    @id = id\n  end\n\n  def call(app)\n    app =&gt; { request:, response: }\n\n    report = Report[@id]\n\n    # do other bits of controller-y logic here\n\n    # render a Viewable component\n    Views::Reports::Show.new(report:)\n  end\nend\n\n\n\n\nBuilding Web Applications With Roda\n\nBesides the features that Bridgetown uniquely provides, described thus far, many of the features you’ll use in the typical course of building out an application are directly supplied by Roda.\n\nBridgetown comes with what we like to call an “opinionated distribution” of Roda. Unlike a first install of Roda where no plugins have yet to be configured, Bridgetown configures a number of plugins right out of the box for enhanced developer experience.\n\nRead our Roda reference guide for more on this base configuration, as well as some of the helpers and utilities made available."
        },
        {
          "id": "docs-roda",
          "title": "Roda Reference Guide",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "roda",
          "tags": "",
          "url": "/docs/roda",
          "content": "Bridgetown comes with what we like to call an &#8220;opinionated distribution&#8221; of the Roda web toolkit, meaning we&#8217;ve configured a number of plugins right out of the box for enhanced developer experience.\n\n\n  \n  For a general overview of how to author server-side code, see Server-Rendered Routes.\n\n\nThis base configuration is itself provided by the bridgetown_server plugin. On a fresh install of a Bridgetown site, you&#8217;ll get a server/roda_app.rb file with the following:\n\nclass RodaApp &lt; Roda\n  plugin :bridgetown_server\n\n  route do |r|\n    r.bridgetown\n  end\nend\n\n\nThe r.bridgetown method call spins up Bridgetown&#8217;s own routing system which is comprised of subclasses of Bridgetown::Rack::Routesand if the bridgetown-routes plugin is active, file-based routing (in src/_routes) as well.\n\nThe bridgetown_server plugin configures the following Roda plugins:\n\n\n  common_logger - connects Roda up with Bridgetown&#8217;s logger\n  json - allows arrays or hashes returned from a route block to be converted to JSON output automatically, along with setting application/json as the content type for the response\n  json_parser - parses incoming JSON data and provides it via r.POST and also r.params\n\n\nBridgetown also sets up the not_found, exception_page, and error_handler plugins to deal with errors that may arise when processing a request.\n\nWe also load our custom ssg plugin which is loosely based on Roda&#8217;s public plugin and provides serving of static assets using &#8220;pretty URLs&#8221;, aka:\n\n\n  /path/to/page -&gt; /path/to/page.html or /path/to/page/index.html\n  /path/to/page/ -&gt; /path/to/page/index.html\n\n\nIf you add init :ssr to your Initializers config, the bridgetown_ssr plugin is loaded which configures these additional plugins:\n\n\n  all_verbs - adds routing methods for additional HTTP verbs like PUT, PATCH, DELETE, etc.\n  cookies - adds response methods for setting or deleting cookies, default path is root (/)\n  indifferent_params - lets you access request params using symbols in addition to strings, and also provides a params instance method (no need to use r.)\n  route_csrf - this helps protect against cross-site request forgery in form submissions\n  custom_block_results - lets Roda route blocks return arbitrary objects which can be processed with custom handlers. We use this to enable our RodaCallable functionality\n  method_override - this Bridgetown-supplied plugin looks for the presence of a _method form param and will use that to override the incoming HTTP request method. Thus even if a form comes in as POST, if _method equals PUT the request method will be PUT.\n\n\nIf you pass sessions: true to the ssr initializer in your config, you&#8217;ll get these plugins added:\n\n\n  sessions - adds support for cookie-based session storage (aka a small amount of key-val data storage which persists across requests for a single client). You&#8217;ll need to have RODA_SECRET_KEY defined in your environment. To make this easy in local development, you should set up the Dotenv gem. Setting up a secret key is a matter of running bin/bridgetown secret and then copying the key to RODA_SECRET_KEY.\n  flash - provides a flash object you can access from both routes and view templates.\n\n\nThe following flash methods are available:\n\n\n  flash.info = \"...\" / flash.info - set an informational message in a route which can then be read once after a redirect.\n  flash.alert = \"...\" / flash.alert - set an alert message in a route which can then be read once after a redirect.\n  flash.now - lets you set and retrieve flash messages (both .info and .alert) for the current request/response cycle.\n\n\nBridgetown Route Classes\n\nIf you&#8217;ve come to Bridgetown already familiar with Roda, you may be wondering what Bridgetown::Rack::Routes is and how it works.\n\nBecause a traditional Roda application isn&#8217;t oriented towards an architecture which plays well with Zeitwerk-based reloading in development, we decided to eschew Roda&#8217;s recommended solutions for creating multiple route files (such as the hash_branches plugin) in favor of a class-based solution.\n\nDuring the r.bridgetown routing process, every loaded Bridgetown::Rack::Routes class is evaluated in turn (sorted by priority) until a route handler has been found (or in lieu of that, a generic 404 response is returned). Route handlers are provided via the route class method, and the block you provide is evaluated in an instance of that class (not the Roda application itself).\n\n\n  \n  You can still access methods of the Roda application from within a route block because Bridgetown::Rack::Routes defines method_missing and delegates calls accordingly. So for example if you were to call flash in your route block, that call would be passed along to the Roda application. However, if for some reason you were to write def flash to create an instance method, you&#8217;d no longer have access to Roda&#8217;s flash. So it&#8217;s recommended that if you do write custom instance methods, you avoid using names which interfere with typical Roda app methods."
        },
        {
          "id": "docs-islands",
          "title": "Islands Architecture",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "islands",
          "tags": "",
          "url": "/docs/islands",
          "content": "The term Islands Architecture was coined a few years ago by frontend architect Katie Sylor-Miller and further popularized by Preact creator Jason Miller. It describes a way of architecting website frontends around independent component trees, all rendered server-side initially as HTML but then “hydrated” on the frontend independently of one another.\n\nIn Bridgetown, we’ve brought islands architecture to you with a seamless integration between our view components and our esbuild frontend bundling system. And for even more flexibility, you can orient your Roda routes around “islands” for a truly modular, full-stack approach to web development.\n\nIslands architecture is constantly evolving, so your feedback is crucial as we increasingly align our best practices with the latest improvements across the industry.\n\nTable of Contents\n\n  Creating Your First Island\n  Installing the Island\n  Rendering Island Components in Ruby\n  Scoped Styling via Declarative Shadow DOM (DSD)\n  Routes in Islands\n\n\nCreating Your First Island\n\nIf you’ve just created a new Bridgetown project, you’re ready to take it to the next level. If you’re upgrading from Bridgetown 1.2 or previous, you’ll need to use esbuild (not Webpack) and update your base esbuild configuration:\n\nbin/bridgetown esbuild update\n\n\nSee the esbuild setup documentation for further upgrade instructions.\n\n\n  \n  You may also need to update your esbuild.config.js file so it includes the following configuration option:\n\n  globOptions: {\n    excludeFilter: /\\.(dsd|lit)\\.css$/\n  }\n\n\n\nNext, in the src/_islands folder (please create one if it’s not already present), add your first island “entrypoint” which we’ll call breezy.js:\n\nclass BreezyDay extends HTMLElement {\n  static {\n    customElements.define(\"breezy-day\", this)\n  }\n\n  connectedCallback() {\n    this.textContent = \"Welcome to your first island.\"\n  }\n}\n\n\nThis JavaScript file, and anything else you import through it, is contained to a single island, meaning it won’t be bundled in with your main JavaScript bundle (aka frontend/javascript/index.js). Great! But how do you load an island?\n\nWith &lt;is-land&gt;, of course!\n\nInstalling the Island\n\nBridgetown comes with a bundled configuration to set up the &lt;is-land&gt; web component, a tiny package which lets you lazy-load islands when they become visible on-screen or are interacted with (aka clicked).\n\nRun this command to add it to your main JavaScript bundle:\n\nbin/bridgetown configure is-land\n\n\nNow let’s add the island to a page. Pick one of the pages on your site (it needs to be ERB for the purposes of this example) and add this bit of code:\n\n&lt;is-land on:visible import=\"&lt;%= asset_path 'islands/breezy.js' %&gt;\"&gt;\n  &lt;breezy-day&gt;\n    Approaching the island. . .\n  &lt;/breezy-day&gt;\n&lt;/is-land&gt;\n\n\nLet’s break this down:\n\n\n  The &lt;is-land&gt; tag gets set up to import the new breezy.js island you’ve created once it’s visible to the reader.\n  At first the &lt;breezy-day&gt; element will be undefined, but once the island loads, it will be upgraded/hydrated.\n  Once that moment occurs, the “Approaching…” text will get replaced with “Welcome…”. That’s a simple example. You’d likely want to write your island JavaScript to set up event handlers, or display more up-to-the-second data pulled from an API, etc.\n\n\nRendering Island Components in Ruby\n\nGenerally we wouldn’t recommend directly adding your island markup inside of &lt;is-land&gt; on any given page, but rather render a component tree of one or more components you define. For this purpose, we’ve included src/_islands as a folder which can load Ruby components. And by using folder-based namespaces, you can easily keep island-specific components separated.\n\nLet’s move &lt;breezy-day&gt; into its own Ruby component. We’ll create a new src/_islands/breezy_day folder and add the namespaced Element class to element.rb:\n\nclass BreezyDay::Element &lt; Bridgetown::Component\n  attr_reader :fallback\n\n  def initialize(fallback: \"Approaching the island. . .\")\n    @fallback = fallback\n  end\nend\n\n\nAnd then define our element.erb template:\n\n&lt;breezy-day&gt;\n  &lt;%= fallback %&gt;\n&lt;/breezy-day&gt;\n\n\nNow in our original page template code, we can render the component and that’s that.\n\n&lt;is-land on:visible import=\"&lt;%= asset_path 'islands/breezy.js' %&gt;\"&gt;\n  &lt;%= render BreezyDay::Element.new %&gt;\n&lt;/is-land&gt;\n\n\nYou can of course render shared components from src/_components from  your island component as well, along with other island-specific components you may define.\n\nScoped Styling via Declarative Shadow DOM (DSD)\n\nFor many (most?) islands, you’ll want to provide styles as part of your markup. We strongly recommend using shadow DOM inside your web components, and our native DSD support makes this a breeze! (Pardon the pun!)\n\nLet’s add some styling to our &lt;breezy-day&gt; component via DSD. Edit your element.erb as so:\n\n&lt;breezy-day&gt;\n  &lt;%= dsd do %&gt;\n    &lt;slot&gt;&lt;/slot&gt;\n    &lt;%= dsd_style %&gt;\n  &lt;% end %&gt;\n  &lt;%= fallback %&gt;\n&lt;/breezy-day&gt;\n\n\nAnd add a stylesheet as element.dsd.css:\n\n:host {\n  display: block;\n  color: LightSeaGreen;\n  font-weight: bold;\n}\n\n\nNow when you view the page, your island element will appear with the styled color and bold text.\n\nWhat’s great about this approach is (a) only this element is affected by your stylesheet and nothing else on your page, and (b) these styles aren’t loaded until the page containing your island is viewed, keeping your main CSS bundle size low. For more general information on how to use and style your HTML templates and components using shadow DOM, check out our documentation on DSD.\n\n\n  \n  Sidecar CSS files processed through the dsd_style helper do not get run through PostCSS—aka they must be 100% “vanilla” CSS. Don’t be surprised if you try using a feature that’s uniquely enabled by your PostCSS config and it’s not available within the DSD template.\n\n\nRoutes in Islands\n\nWhen using the bridgetown-routes plugin for server rendering, you can put file-based routes inside of islands. Add a routes folder inside of an island folder (like the src/_islands/breezy_day example above), and then any routes inside will resolve to a URL of the island name plus the route name. Read our Routes documentation for more information."
        },
        {
          "id": "docs-cookbook",
          "title": "Cookbook",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "cookbook",
          "tags": "",
          "url": "/docs/cookbook",
          "content": "Helpers\n\nGeneral Helpers\n\nThe cache_busting_url from the doc using the configured url of the project, and the multiply_and_optionally_add example, in a single file.\n\nhelpers_example recipe\n\nDate Helpers\n\nIn your project you might for example have a frequently used strftime format. Or you may run into issues with ruby having Time, Date and DateTime classes, as well as strings containing date/time values. This helper provides two methods: standardize_date to convert to date objects and date_format to format the date.\n\nhelpers_date recipe\n\nPagination\n\nKeeping a Pagination Page in a Collection\n\nPagination pages don&#8217;t appear in collections. If you need your pagination page to be in a collection (like you have a menu that iterates a collection), have two pages. The first page can show the same number of entries and have a link for the other archive page in place of pagination.\n\nArchives by Date Period\n\ncollection_by_years does not use pagination at all to create a time period grouped index. Using this approach avoids the paginator.\n\ncollection_by_years recipe\n\nmonthly_archives uses prototypes, which rely on the paginator.\n\nmonthly_archives recipe"
        },
        {
          "id": "docs-cookbook-collection_by_years",
          "title": "Cookbook: Collection By Years",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "cookbook",
          "tags": "",
          "url": "/docs/cookbook/collection_by_years",
          "content": "_components/years.rb\n\nclass Years &lt; Bridgetown::Component\n  def initialize(collection:)\n    @archive = []\n    current_year = nil\n    current_year_data = []\n\n    collection.resources.each do |resource|\n      year = resource.date.year\n      if year != current_year\n        # Save the previous year's data if it exists\n        @archive &lt;&lt; { year: current_year, pages: current_year_data } if current_year\n        # Start a new year\n        current_year = year\n        current_year_data = [resource]\n      else\n        current_year_data &lt;&lt; resource\n      end\n    end\n\n    # Don't forget to add the last year's data\n    return unless current_year\n\n    @archive &lt;&lt; { year: current_year, pages: current_year_data }\n  end\nend\n\n\n_components/years.erb\n\n&lt;!-- If you're not using Bootstrap define the d-none style or\nswitch to your framework's equivalent class.\n&lt;style&gt;\n.d-none { display: none; }\n&lt;/style&gt;\n--&gt;\n&lt;% classlist = '' %&gt;\n&lt;% @archive.each do |yeargroup| %&gt;\n  &lt;h2 &gt;&lt;%= yeargroup[:year] %&gt; &lt;/h2&gt;\n  &lt;div &lt;%= raw classlist %&gt; &gt;\n  &lt;% classlist = 'class=\"d-none\"' %&gt;\n  &lt;% pages = yeargroup[:pages] %&gt;\n\n  &lt;ul&gt;\n  &lt;% pages.each do |page| %&gt;\n    &lt;li&gt;&lt;%= page.data.date.strftime('%B %e') %&gt;\n      &lt;a href=\"&lt;%= page.relative_url %&gt;\"&gt;&lt;%= page.data.title %&gt;&lt;/a&gt;&lt;/li&gt;\n  &lt;% end %&gt;\n  &lt;/ul&gt;\n  &lt;/div&gt;\n&lt;% end %&gt;\n\n&lt;!-- make sure jquery loads before this script --&gt;\n&lt;script&gt;\n$(document).ready(function() {\n  $('h2').css('cursor', 'pointer');\n  $('h2').on('click', function() {\n    $('h2').not(this).next('div').addClass('d-none');\n    $(this).next('div').removeClass('d-none');\n  });\n});\n&lt;/script&gt;\n\n\nyears.md in one of your collections\n\n---\ntitle: Collection Pages Grouped by Year\n# Do not paginate this page.\n---\n&lt;%= render Years.new(collection: collections.posts ) %&gt;"
        },
        {
          "id": "docs-cookbook-monthly_archives",
          "title": "Cookbook: Monthly Archive",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "cookbook",
          "tags": "",
          "url": "/docs/cookbook/monthly_archives",
          "content": "plugins/builders/monthly_archives.rb\n\nclass Builders::MonthlyArchives &lt; SiteBuilder\n  using Bridgetown::Refinements\n\n  priority :high\n\n  COLLECTIONS = %w(posts)\n\n  def build\n    hook :resources, :post_read do |resource|\n      add_monthly_data(resource) if resource.collection.label.within?(COLLECTIONS)\n    end\n\n    helper :monthly_archive_list do\n      site.data\n        .monthly_archives\n        .map { _1.split(\"|\") } # split 2010-05|May 2010\n        .sort_by { _1[0] }\n        .map { _1[1] }\n        .reverse\n    end\n  end\n\n  def add_monthly_data(resource)\n    resource.data.monthly = resource.date.strftime(\"%B %Y\") # May 2010\n    site.data.monthly_archives ||= Set.new\n    site.data.monthly_archives &lt;&lt; \"#{resource.date.strftime(\"%Y-%m\")}|#{resource.data.monthly}\"\n  end\nend\n\n\nyour pagination page front matter\n\ntitle: \":prototype-term-titleize Archive\"\nexclude_from_search: true\nprototype:\n  collection: posts\n  term: monthly"
        },
        {
          "id": "docs-cookbook-helpers_date",
          "title": "Cookbook: Date Helpers",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "cookbook",
          "tags": "",
          "url": "/docs/cookbook/helpers_date",
          "content": "plugins/builders/datehelpers.rb\nrequire_relative '../../lib/date_helpers'\n\nclass Builders::Datehelpers &lt; SiteBuilder\n  def build\n    helper :standardize_date do |dt|\n      DateHelpers.standardize_date(dt)\n    end\n    helper :display_date do |dt|\n      DateHelpers.standardize_date(dt).strftime('%A %e %B %Y')\n    end\n  end\nend\n\nlib/date_helpers.rb\nmodule DateHelpers\n  def self.standardize_date(date)\n    case date\n    when String\n      begin\n        Date.parse(date)\n      rescue ArgumentError\n        nil\n      end\n    when Date, Time, DateTime\n      date.to_date\n    else\n      nil\n    end\n  end\nend"
        },
        {
          "id": "docs-cookbook-helpers_example",
          "title": "Cookbook: Helpers Example",
          "collection": {
            "label": "docs",
            "name": "Documentation"
          },
          "categories": "cookbook",
          "tags": "",
          "url": "/docs/cookbook/helpers_example",
          "content": "plugins/builders/helpers.rb\nclass Builders::Helpers &lt; SiteBuilder\n  def build\n    helper :cache_busting_url do |path|\n      \"#{Bridgetown::Current.site.config.url}/#{path}?#{Time.now.to_i}\"\n    end\n    helper :multiply_and_optionally_add do |input, multiply_by, add_by = nil|\n      value = input * multiply_by\n      add_by ? value + add_by : value\n    end\n  end\nend\n\n\nconfig/initializers.rb\nBridgetown.configure do |config|\n# ...\n  if Bridgetown.env.production?\n    url 'https://bridgtownrb.org'\n  end\n\n  if Bridgetown.env.development?\n    url 'http://localhost:4000'\n    unpublished true\n    future true\n  end\n\n  if Bridgetown.env.stage?\n    url https://stage.bridgtownrb.org'\n  end\n# ...\nend"
        },
        {
          "id": "",
          "title": "bridgetown_directus",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Use Directus as headless CMS for Bridgetown"
        },
        {
          "id": "",
          "title": "bridgetown-docs-template",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown + Tailwind CSS theme for your documentation site"
        },
        {
          "id": "",
          "title": "bridgetown_markdown_lazylinks",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Support for lazy markdown reference links"
        },
        {
          "id": "",
          "title": "Content Security Policy",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin to add a Content Security Policy in a meta tag"
        },
        {
          "id": "",
          "title": "Sitemap",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin to generate a sitemap.xml"
        },
        {
          "id": "",
          "title": "SVG Inliner",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Liquid and ERB helper for Bridgetown to inline SVG files in HTML"
        },
        {
          "id": "",
          "title": "bridgetown-image-pipeline",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Responsive images and image processing for Bridgetown with a simple helper-based pipeline."
        },
        {
          "id": "",
          "title": "bridgetown-rougify",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Rougify your syntax highlighting in Bridgetown markdown code blocks"
        },
        {
          "id": "",
          "title": "bridgetown-cloudinary",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Embed or access images with transformations using Cloudinary and Bridgetown"
        },
        {
          "id": "",
          "title": "Feed",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin to generate an Atom feed of your Bridgetown posts"
        },
        {
          "id": "",
          "title": "Lit Renderer",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Simple pipeline for SSR + hydration of Lit components."
        },
        {
          "id": "",
          "title": "bridgetown-quick-search",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A component for Bridgetown sites which performs search queries with Lunr.js."
        },
        {
          "id": "",
          "title": "SEO Tag",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin to add metadata tags for search engines and social networks to better index and display your site&#8217;s content."
        },
        {
          "id": "",
          "title": "bridgetown-view-component",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Add support for GitHub&#8217;s ViewComponent library to your Bridgetown sites."
        },
        {
          "id": "",
          "title": "Sequel Integration",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Bridgetown plugin for integrating the Sequel database gem"
        },
        {
          "id": "",
          "title": "Willamette",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Modern theme for blogs, portfolios, documentation, and publishing. Built on top of Web Awesome"
        },
        {
          "id": "",
          "title": "bridgetown-minify-html",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Minify your HTML on your Bridgetown site"
        },
        {
          "id": "",
          "title": "bridgetown-plausible",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Plausible Analytics Bridgetown Plugin"
        },
        {
          "id": "",
          "title": "bridgetown_reveal",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A reveal.js plugin for the bridgetown static site generator"
        },
        {
          "id": "",
          "title": "bridgetown_notion",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin that allows you to use Notion as a CMS."
        },
        {
          "id": "",
          "title": "bridgetown-notable",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Bridgetown plugin to host Notable pages"
        },
        {
          "id": "",
          "title": "bridgetown-automation-lazyload",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "An automation for adding vanilla-lazyload to a project"
        },
        {
          "id": "",
          "title": "bridgetown-automation-swiperjs",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": ""
        },
        {
          "id": "",
          "title": "bridgetown-media-transformation",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": ""
        },
        {
          "id": "",
          "title": "bridgetown-deploy_hook",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin that adds a webhook for performing actions after a deploy"
        },
        {
          "id": "",
          "title": "bridgetown-webfinger",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin that gives your site the ability to answer Webfinger requests"
        },
        {
          "id": "",
          "title": "bridgetown-related-posts",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "This plugin calculates and adds related posts to your Bridgetown site using TF-IDF and cosine similarity."
        },
        {
          "id": "",
          "title": "bridgetown-obsidian",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A Bridgetown plugin to convert Obsidian notes to Bridgetown posts."
        },
        {
          "id": "",
          "title": "bridgetown_theme_single_page_opt_in",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "A template for setting up a new landing/sales page with opt-in and a timer"
        },
        {
          "id": "",
          "title": "Jim",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Superior image processing and responsive HTML5 outputs for Jekyll and Bridgetown, 100% flexible. Using a very efficient and persistent cache, user-defined filenames, templates, watermarks, additional CSS/HTML attributes, SVG forwarding/inlining, raw data and more."
        },
        {
          "id": "",
          "title": "bridgetown_credentials",
          "collection": {
            "label": "plugins",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": "Rails-like encrypted credentials for Bridgetown"
        },
        {
          "id": "release-everyone-deserves-a-wiki-bridgetown-2-2",
          "title": "Everyone Deserves a Wiki: Bridgetown 2.2 is Here",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/everyone-deserves-a-wiki-bridgetown-2-2/",
          "content": "Photography by Jared White\n\n\nSpringtime in Portland is always a true delight, and the perfect backdrop for our newest release of Bridgetown, version 2.2.\n\nWhen it came time to decide on the codename for the release, we just couldn’t bear the thought of moving away from our beloved “River City” moniker. Mere months after our massive version 2 launched, we released 2.1 during the 2025 holiday season and called it “Festive River City”. And we knew we needed to keep this tradition alive just a little bit longer.\n\nSo we turned to our crack marketing team, loaded them up with local apple cider and Oregon Marionberry scones, locked them in a chill café adjacent to a food cart pod, and let them work their special brand of magic. After many, many long hours of heightened debate, they solemnly informed us they had arrived at a winner. And winner it is indeed.\n\nIntroducing Verdant River City, the latest iteration of the Bridgetown Ruby Web Framework.\n\nRead the 2.2 release notes here. To upgrade, make sure you’re running at least Ruby 3.3 and Node 22, then modify your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 2.2.0\"\ngem \"bridgetown-routes\", \"~&gt; 2.2.0\" # if applicable\n\n\nand run bundle update. More upgrade instructions here. Or you can run gem install bridgetown -N -v 2.2.0 and then create a new site.\n\nHyperlink with Aplomb\n\nThe marquee new feature of Bridgetown 2.2 “Verdant River City” is support for wikilinks. It’s a syntax which makes it easy to add links when authoring any Markdown file. No longer do you need to keep track of filenames or URLs. Just type [[Page Title Goes Here]] and Bridgetown will automatically locate a resource with title: Page Title Goes Here in its front matter. You can of course add additional syntax to customize the text of the link if the title isn’t suitable, as well as link to a specific section/anchor within the resource.\n\nWikilinks pairs well with our previous release’s support for external content sources. It’s now possible to author a file-based “wiki” entirely with outside applications, point to those files from a Bridgetown project, and publish that wiki as a public (or internal!) website—perfect for knowledge bases, digital gardens, academic archives, and so much more.\n\nSoar with Falcon\n\nIn Bridgetown 2.2, we have migrated a new default server, Falcon. (Don’t worry: existing projects still on Puma will still run without issue*. You can migrate, or not, at your leisure!)\n\nFalcon is a new highly concurrent Ruby web application server. While Puma serves a request per thread, Falcon uses fibers which are an order of magnitude cheaper to create. Falcon also supports HTTP/2 natively. This combined with its highly concurrent architecture means it’s viable to serve internet traffic using Falcon itself instead of another webserver such as Caddy in front of it.\n\nFalcon has been battle-tested at Shopify and used to serve Black Friday traffic, and as such we believe it’s the future for the Ruby ecosystem. It also paves the way for Bridgetown to support a deployment story where the website is served using Ruby code end-to-end.\n\nIf you’d like to try out Falcon in your existing Bridgetown project, remove gem \"puma\" from your Gemfile and add gem \"falcon\". After a bundle install, you can use Bridgetown exactly as before, only now it will detect you have Falcon installed and use that instead of Puma!\n\n* Due to underlying server layer refactoring, you may occasionally see a Puma “thread error” notice when pressing Ctrl+C to terminate the dev server. It’s harmless, but any unwanted noise in your terminal isn’t OK with us, so we’ll track that down and include a patch in the next point release.\n\nBridgetown Sites in the Wild\n\nIt’s been a busy time for the Bridgetown ecosystem! Rubyists everywhere are turning to Bridgetown to serve their publishing needs, including:\n\n\n  Sidekiq\n  RuboCop\n  Rocky Mountain Ruby\n\n\nWe’re also a featured framework now within the Ruby Users Forum! Read the full announcement here.\n\nNew Sponsorship Opportunity!\n\nWe are slowly moving off of promoting GitHub Sponsors as the canonical way of supporting Bridgetown financially. We will incrementally investigate systems &amp; opportunities to ensure the continued sustainability of the ecosystem, and in a first step to that end, you can now become a sponsor through Liberapay.\n\nOur first goal: $400/month. We actually exceeded this goal once before using GitHub Sponsors, but, well, the last couple of years have been hard on everyone in independent open source. But we believe this goal is achievable again, and once we reach it…we’ll aim for the next goal! 💪\n\nBut That’s Not All Folks…\n\nThere is in fact another major announcement we’re making at this juncture beyond the release of 2.2, and that’s the launch of the Bridgetown Center plugin program. But we’ll save that for another blog post. =)\n\n\n\nThank you for your interest in Bridgetown 2.2! In addition to the features mentioned previously, we’ve been fixing bugs, improving performance, and generally making the experience of using Bridgetown even better. A huge shoutout to all the contributors who made this release possible.\n\nIf you run into any issues, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us on Bluesky and the fediverse to stay current on the latest news.\n\nAdditional credit to Ayush for assistance in the writeup regarding Falcon."
        },
        {
          "id": "release-bridgetown-2-1-has-come-to-town-happy-holidays",
          "title": "Bridgetown 2.1 Has Come to Town! Happy Holidays 🥳",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/bridgetown-2-1-has-come-to-town-happy-holidays/",
          "content": "You better watch out (I’m telling you why): the latest release of our merry little Ruby framework is coming to town! Yes indeed, Bridgetown 2.1 “Festive River City” is here, with a marquee feature enabling you to load in wiki-style content &amp; “digital gardens” managed by external applications, and a slew of other improvements. And you’d better believe we’re ready for the imminent Christmas release of Ruby 4.0!\n\nYou can read more about the release details here in our initial beta post, and there are a couple additional details we need to mention:\n\n\n  We now have a brand-new Matrix chat room! Embrace open protocols and chat with fellow Bridgetowners on Matrix. (We recommend you install Element X for iOS &amp; Android so you can access Matrix via mobile platforms.)\n  We fully embrace Bridgetown projects configured to bundle gems from alternative community servers like gem.coop as well as our own canonical gems.bridgetownrb.com server. Read the details here.\n\n\nRead the 2.1 release notes here. To upgrade, make sure you’re running at least Ruby 3.2 and Node 22, then modify your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 2.1.0\"\ngem \"bridgetown-routes\", \"~&gt; 2.1.0\" # if applicable\n\n\nand run bundle update. Upgrade instructions here. Or you can run gem install bridgetown -N -v 2.1.0 and then create a new site.\n\nAnd of course we continue to push for awareness of “indie web” and sustainable alternatives to Big Tech solutions. Our documentation now includes information on how to deploy static Bridgetown sites to statichost.eu, and our Automations feature can load automation scripts from Codeberg and GitLab repositories in addition to GitHub.\n\nIf you run into any issues trying out Bridgetown 2.1, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us on Bluesky and the fediverse to stay current on the latest news.\n\nHappy Holidays! See y’all in the New Year! 🎉 🎆"
        },
        {
          "id": "release-bridgetown-2-1-beta-1-festive-river-city",
          "title": "Let’s Get Festive! ’Tis the Season to Be Jolly with Bridgetown 2.1 Beta 1",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/bridgetown-2-1-beta-1-festive-river-city/",
          "content": "A mere three months after the release of Bridgetown 2.0 and we’re already onto 2.1?! What can I say…when you’re on a roll, you’re on a roll. And things are definitely rolling here in Bridgetown as we celebrate our “Festive River City 🥳” (we just couldn’t part ways with the River City branding just yet, hence our “Snow Leopard” style code name!).\n\nWith the end-of-year holidays upon us, we’re excited to be presenting several exciting new features and yet more under-the-hood refactoring &amp; infrastructure work which sets us up well for future feature development in 2026. While there are possibly some breaking changes in 2.1 (which is why we opted for a beta cycle), we hope to mitigate those as much as possible or offer clear instructions on how to correct swiftly. Your feedback during the beta is most appreciated!\n\nRead the release notes here. To test your sites with the 2.1 beta, modify your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 2.1.0.beta1\"\ngem \"bridgetown-routes\", \"~&gt; 2.1.0.beta1\" # if applicable\n\n\nand run bundle update. Or you can run gem install bridgetown -N -v 2.1.0.beta1 to create a new site.\n\nExternal Content Sources (Markdown Apps, Digital Gardens, Wikis, etc.)\n\nHave you ever thought to yourself: “Self, wouldn’t it be awesome if I could just point Bridgetown at a folder full of Markdown files and it would just render??”\n\nYour wish is our command. ✨\n\nIn Bridgetown 2.1, you can now add one or more “external content” sources via an initializer which can be literally any folder on the filesystem, even the parent folder! Yes, Bridgetown now supports “folder inversion” as an alternative installation approach, which means in addition to placing Markdown files and related assets in src, you can write Markdown in the folder above where Bridgetown is installed (and in various subfolders thereof). Not only that, but front matter is completely optional! This is perfect for “digital gardens”, wikis, and other file-based content systems managed by third-party Markdown apps such as Obsidian. (And you’d better believe we have a whole bunch of wiki-themed improvements in mind for future releases.) 😎 Read our docs for more information.\n\nUniversal Rendering for Partials &amp; Components\n\nA limitation of how our rendering system worked for partials in the past was that you could only use partials within the same template engine. So if you were writing an ERB template, you couldn’t use a Serbea partial. Or if you were in a Serbea template, you couldn’t use a pure Ruby (.rb) partial.\n\nThat has now been fixed! Any template engine can render any partial whether ERB, Serbea, or pure Ruby. On top of that, you can now render both partials &amp; components to strings from any context (not just within a template)—including the console! Read our docs for more information.\n\nSpeaking of the console, you now have access to the Rack::Test DSL (if the gem is installed, which happenes automatically if you use the minitesting bundled configuration). This is great for testing both static &amp; dynamic routes directly within the REPL.\n\nSamovar, Freyia, and Custom Commands\n\nIn Bridgetown 2.1, we’ve migrated away from using Thor for our command-line interface (CLI) and are now using Samovar, created by Samuel Williams. It provides a straightforward, modern, and elegant way to write commands, and as a bonus, we’ve made it much easier for you to extend Bridgetown’s CLI with your own commands in any project! Just create a config/custom_commands.rb file and add your own Bridgetown::Command subclasses. We believe in most cases this is a more powerful &amp; flexible solution than authoring new Rake tasks. Read our docs for more information.\n\nNote that for the 2.1 release cycle, we have provided a Thor “shim” so existing sites and plugins which provide Thor commands should continue to work as before. In a future release, we will be removing the shim so please update your commands accordingly. In the meantime, if you run into any compatibility issues with the shim please report them and let us know!\n\nAnother of our prior uses of Thor was for the Automations functionality (which also powers our Bundled Configurations). Because this functionality is so important, and also near-impossible to replicate verbatim using an alternative library, we have extracted the “actions” &amp; “shell” portions of Thor as a hard fork out to a new gem called Freyia. We will be actively developing Freyia as its own independent project going forward, refactoring and adding new features as needed.\n\nRemoval of Active Support\n\nAs part of our pledge to ensure Bridgetown is free of dependencies directly managed by 37signals, we have finialized the removal of the Active Support gem. If you have written your own code which assumes the availability of Active Support, you may need to bundle add activesupport and require pieces of it yourself. For example, if you want to use .blank?, .present?, etc., add the gem and include the following in your config/initializers.rb file:\n\nrequire \"active_support/core_ext/object/blank\"\n\n\nDocumentation on Active Support is available here.\n\nSupporting Alternatives to Big Tech\n\nWe continue to push for awareness of “indie web” and sustainable alternatives to Big Tech solutions. Our documentation now includes information on how to deploy static Bridgetown sites to statichost.eu, and our Automations feature can load automation scripts from Codeberg and GitLab repositories in addition to GitHub.\n\n\n\nAs always if you run into any issues trying out Bridgetown 2.1, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us on Bluesky and the fediverse to stay current on the latest news."
        },
        {
          "id": "news-welcome-felipe-vogel-to-our-core-team",
          "title": "Welcome Our Newest Core Team Member: Felipe Vogel",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/welcome-felipe-vogel-to-our-core-team/",
          "content": "We’ve very excited to announce that Felipe Vogel has joined the Bridgetown Core Team! Felipe is a developer based in Lexington, Kentucky, and originally hails from Brazil. He has been an enthusiastic user of Bridgetown for a long time now. In fact…\n\n\n…When Felipe was first learning Ruby, Bridgetown was the first Ruby web framework that he picked up. Eventually he got into Rails and other tools as well, but that’s quite an amazing story. Here are a few other fun facts about Felipe from his website:\n\n\n  “I used to be a humanities teacher in a former career that took me all the way to West Africa.”\n  “I grew up in Brazil in southern Amazonia, at times living with the Jarawara indigenous people, in a village that you can see on Google Maps.”\n  “I’m a big Latin nerd. In grad school I took most of my classes in Latin and lived in the unofficial domus Latina (Latin house), where we spoke only Latin except when we had guests… which, as you might guess, was rare 😂”\n\n\nYou’ll start to see Felipe a bit more in our Discord chat, taking a peek at new PRs, and in other community endeavors. You can also find Felipe in the fediverse: @fpsvogel@ruby.social. We’re glad to have Felipe aboard the USSG Bridgetown! 🫡"
        },
        {
          "id": "release-bridgetown-v2-river-city-released",
          "title": "Good Times in River City: Bridgetown 2.0 is Here!",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/bridgetown-v2-river-city-released/",
          "content": "Graphic Design by Adrian Valenzuela\n\n\nIntroducing the only Ruby web framework which bridges the gap between static Markdown sites and fullstack database-driven application deployments: Bridgetown 2.0 “River City” has been released! 🎉 This version has long been in the hopper, and it’s chock full of major quality-of-life improvements (check out the release notes!). Start your first Bridgetown site with our installation guide, or upgrade your site by bumping the version in your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 2.0\"\ngem \"bridgetown-routes\", \"~&gt; 2.0\" # if applicable\n\n\nand running bundle update. More upgrade details here, including switching from Yarn to NPM and from CommonJS to ESM for a modern, streamlined frontend build system. If you run into any issues, hop in our Discord chat and let us know how we can help you!\n\n\n  \n  A huge thank you to our many contributors! 🎉 ayushn21, KonnorRogers, michaelherold, erickguan, MaxLap, jeremyevans, brainbuz, akarzim, surrim, mlitwiniuk, jaredcwhite, and everyone who submitted bug reports and feedback. We couldn’t have done this without you!\n\nAlso, if you’re brand-new to the Bridgetown 2.0 release cycle, read our Road to Bridgetown 2.0 series here.\n\n\nNow without further ado, let’s talk about all the new features in Bridgetown v2 “River City”.\n\nNew Defaults\n\nWhen you create a new Bridgetown site, you will now get ERB templates installed automatically (rather than the previous default of Liquid). We believe this is much more ergonomic for Ruby developers, but if you work with designers who are more familiar with Liquid, that is still officially supported. (For existing sites based on Liquid, add template_engine :liquid to your config/initializers.rb file or template_engine: liquid to bridgetown.config.yml.)\n\nBridgetown now requires a minimum version of Ruby 3.1.4 and Node 20.6. This unlocks a variety of syntax improvements both for our internal Ruby APIs and the Ruby code you write in your projects. And with the Node upgrade, we can now switch our esbuild config from the older CommonJS syntax (aka require) to modern ESM (aka import) which is the industry standard and much more expected for frontend JavaScript developers. In addition, Yarn is no longer required as a JS package manager—you can switch to using NPM directly which is a far better tool now than it once was. Or if you prefer, pnpm which is also supported! (Just make sure you update the :frontend tasks in your project’s Rakefile to use your preferred package manager.)\n\nNew sites will default to including just the newer Ruby initializers configuration (config/initializers.rb), but the legacy YAML config (bridgetown.config.yml) is still supported and may still be required for certain plugins.\n\nFinally, webpack is no longer supported. Bridgetown is all in on esbuild as the “last frontend bundler you’ll ever need”. Make sure you search-and-replace webpack_path to asset_path in your templates!\n\nFast Refresh\n\nYou know how frustrating it is when you fix a simple typo on a large site and then you have to wait 20 seconds for a rebuild? Well I don’t, because I’ve been rocking the betas for months! 😂 But in all seriousness, this is a fantastic new feature in Bridgetown 2.0, based on a pair of techniques called signals and effects to track changes to individuals files and the ways in which data can flow across multiple files.\n\nIn not all, but in most cases, this results in a much faster rebuild time. If you edit just a single resource file, it’s likely only that one resource will get rebuilt. Using the new site.signals API, you can edit data files and only the pages loading in that data will get rebuilt. Fast refresh also tracks access to components within templates. Read our original announcement blog post for a deep dive into this functionality.\n\nSuperior Roda Integration\n\nBridgetown provides a full SSR pipeline built on top of the Roda web toolkit. You can handle form data or JSON payloads with ease, or power up our dynamic, file-based routing with all of Bridgetown’s template engines and component rendering at your disposal.\n\nThis was all true prior to 2.0, but what’s new is we’ve added a lot more smarts to Roda for building object-oriented backend APIs out of modular building blocks. You can keep the Roda routing tree lean-and-mean and rely on “controller” and “view” style objects for a familiar pattern to fullstack application development. Want to access a database like PostgreSQL? Our new bridgetown_sequel plugin is the answer you seek.\n\nCheck out our Roda reference guide as well as our updated Routes guide to get up to speed.\n\nStreamlined\n\nThis is a new library installed with Bridgetown for embedding HTML templates in pure Ruby code using “squiggly heredocs” along with a set of helpers to ensure output safety (proper escaping) and easier interplay between HTML markup and Ruby code. And it’s fast: roughly 50% faster than ERB!\n\nYou can use Streamlined directly in resources saved as pure Ruby (.rb), as well as in Bridgetown components. Here’s an example of what that looks like:\n\nclass SectionComponent &lt; Bridgetown::Component\n  def initialize(variant:, heading:, **options)\n    @variant, @heading, @options = variant, heading, options\n  end\n\n  def heading\n    &lt;&lt;~HTML\n      &lt;h3&gt;#{text -&gt; { @heading }}&lt;/h3&gt;\n    HTML\n  end\n\n  def template\n    html -&gt; { &lt;&lt;~HTML\n      &lt;section #{html_attributes(variant:, **@options)}&gt;\n        #{html -&gt; { heading }}\n        &lt;section-body&gt;\n          #{html -&gt; { content }}\n        &lt;/section-body&gt;\n      &lt;/section&gt;\n    HTML\n    }\n  end\nend\n\n\nYou may still prefer to author HTML in a “markup first” manner with embedded Ruby, rather than Ruby with embedded HTML (and sometimes I do), but for components with complex interpolations, Streamlined is a win. Read the new documentation here.\n\nFoundation Gem\n\nIn Bridgetown 2.0, we have started an ongoing process of reducing our reliance on the Active Support gem (in the hopes we can eventually remove it as a dependency). The Foundation gem marks our effort in this department, and not only that but we’ll be increasingly migrating other useful utility-like Ruby features there from internal Bridgetown APIs so that they’re more decoupled and easily accessible to non-Bridgetown Ruby applications. Foundation gem docs are available here.\n\nEcosystem Update (Hello Codeberg!)\n\nWe’ve had a goal in mind to embark on a major revamp of our Plugins directory as well as offer a program to help new plugin authors get started and eventually featured in official Bridgetown marketing. While those ideas were brewing, a huge shakeup has started in the open source community. Due to Microsoft’s major “vibe shift” with how it stewards GitHub—essentially embarking on a “pivot to AI” and folding the company into its own AI &amp; cloud departments, many open source developers are looking elsewhere for a true libre alternative. (It’s odd when you stop and think about it that a proprietary, closed-source platform would be the defacto home for open source projects!)\n\nThe destination folks are heading for more and more is Codeberg, a European organization which runs a forge built on top of the fully open source Forgejo software (itself a fork of Gitea). Codeberg has really seemed to reach escape velocity this year, and I (Jared) have already migrated a number of projects there, including several low-level Bridgetown dependencies.\n\nWe don’t feel comfortable with the idea of relocating the Bridgetown monorepo itself at this time, out of concern that it will be cumbersome for existing and potential contributors. But the Bridgetown core team is committed to keeping a close watch on how this movement unfolds. One of the most exiting future developments will be federation, the idea that multiple forges run by individuals and organizations alike could all communicate with each other, sharing issues and PRs as if it were all a single platform (just like Mastodon instances!). We believe this will be a seismic event in the evolution of code forges and signal the beginning of the end of GitHub’s dominance in the open source community. As they say, stay tuned.\n\nEt Cetera and So Forth\n\nThere’s much more in the release notes showing a large number of bug fixes and refactors for performance and DX, and while software is of course never “done”, we are extremely proud of the Bridgetown 2.0 release and consider its full feature set to be the culmination of several years of effort. It’s likely we’ll continue to chip away at the margins of smaller fixes and enhancements for a long while yet in the v2 era, but the bottom line is that Bridgetown is a mature and stable foundation on which to build the next generation of static sites and modest web applications, always with HTML-first and “vanilla web” principles in mind.\n\nIf you run into any issues trying out Bridgetown 2.0, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us now on Bluesky as well as in the fediverse to stay current on the latest news."
        },
        {
          "id": "news-bridgetown-turns-five-today",
          "title": "Bridgetown, the Hybrid Ruby Web Framework, Turns Five Today",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/bridgetown-turns-five-today/",
          "content": "Happy Birthday Bridgetown! Five years ago today, a project was born to create a new kind of web framework—certainly one unlike any previously seen in the Ruby community. (More on that in a moment.) I will confess, I’m as astonished as anyone we’re still going strong five years later!\n\nThis time last year I reminisced quite a bit and also looked forward to all-systems-go development of Bridgetown 2.0, and today we are preparing what might be the last beta of the 2.0 cycle before its final release.\n\nBut beyond a fist pump in the air and a dash of nostalgia, I’d like to reflect on the astonishing truth that Bridgetown is, well, still the only game in (our) town. I included in the title of this post “the Hybrid Ruby Web Framework” because that’s our not-so-secret sauce that for some reason continues to escape notice for a lot of people.\n\nBridgetown isn’t just a static site generator. Bridgetown isn’t just a dynamic application server. It’s both. And as time passes, the lines between those two worlds—pre-built and built-on-demand—will blur, and we’ll continue to press forward on solutions which allow your static pages to refresh portions of the page on-demand with live content. We’re super-thrilled with the amount of progress we’ve made on polishing our server architecture, as well as some companion projects simmering on the back burner to make interactive feature development feel unlike anything you’ve experienced in Ruby.\n\nAt the same time, we see Bridgetown as playing a significant role in elevating the exposure of Ruby web frameworks in general past a certain train-themed option some people think is the only Ruby framework available. There’s a lot of educational work to be done to show that Ruby has range and we can forge new paths. Along those lines, we want to show how Bridgetown can be used alongside other frameworks, most notably Hanami. While Bridgetown is itself a complete framework, there’s no reason you couldn’t use Hanami for your application server code and Bridgetown for a static portion such as a company blog. Could those live side-by-side in the same repo, perhaps even sharing some common assets? Stay tuned.\n\nAnyway, back to work on getting v2.0 out the door. Once again, thank you to all who support this project and contribute to make it better. Excelsior!"
        },
        {
          "id": "release-countdown-to-production-release",
          "title": "Countdown to Production! Bridgetown 2.0 Beta 4 is Here",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/countdown-to-production-release/",
          "content": "The Bridgetown progressive web framework is racing to the 2.0 finish line. While we don’t have a specific process for offering “release candidates”, we consider this Beta 4 release to be essentially an RC. Feature development is now frozen, and the only additional updates we anticipate will be major bug fixes only.\n\nRead the release notes, review our “edge” documentation, and take a look at our 2.0 upgrade guide if you’re coming from Bridgetown 1.x. If you’re new to the Bridgetown 2.0 release cycle, read our Road to Bridgetown 2.0 series.\n\nSome of the goodies in Beta 4:\n\n\n  We continue to make progress in improving full build performance. While our Fast Refresh feature makes development an order of magnitude faster in most cases, we also want our complete build cycles to go more quickly.\n  In previous releases, our primary goal was to ensure Bridgetown could “scale up” to large, ambitious application architectures. Now we’re also looking at opportunities to “scale down”. In the latest beta, we support Bridgetown running in a folder with only a Gemfile and some source files. That’s it. No config folder, no Rakefile, no config.ru…you get the picture. And in a follow-up feature in Bridgetown 2.1, we’ll support source folders located elsewhere on the file system as well as Markdown files with no front matter present. If that makes you think wiki!, well, you’re exactly right. ☺️\n  The first beta of Bridgetown 2.0 introduced the RodaCallable mixin for allowing any PORO (Plain Old Ruby Object) to respond to a Roda route. Now, we’ve introduced the Viewable mixin for allowing any Ruby view component to provide HTML rendering for a route. Daisy-chain callable &amp; viewable objects and you essentially have the “View-Controller” part of MVC solved. We believe this will unlock opportunities to build ever larger and more robust web applications with Bridgetown.\n  We’ve introduced an entirely new Bundled Configuration to add minitest to your projects, and the tests will be equally adept at validating the output of statically-generated content as well as server-side routes. Yes that’s right: use the exact same testing infrastructure to test static and dynamic endpoints equally—complete with an elegant DSL for making requests and parsing either HTML or JSON.\n\n\nWe’re also plugging away at new documentation around all of these features and more besides, and going forward the bulk of our efforts to get a final 2.0 release out the door will be the docs.\n\nAs always, if you run into any issues trying out Bridgetown 2.0 beta, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us on Bluesky as well as in the fediverse to stay current on the latest news."
        },
        {
          "id": "news-2024-year-in-review",
          "title": "Bridgetown 2024: Year in Review",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/2024-year-in-review/",
          "content": "This year has been a pivotal one for the Bridgetown web framework. We started off 2024 on some conceptual shaky ground, uncertain about the direction Bridgetown should head in next, but over the spring and summer we really hit our stride and are finishing out the year in a strong position.\n\nAnother meaty beta release of Bridgetown 2.0 is just around the corner, and we expect the final Bridgetown v2 production release to land in early Q1 2025—unlocking a new wave of innovation in the Ruby-powered web development space.\n\nOur message for 2025 is a simple one: Bridgetown is ready to be your #1 go-to framework for ambitious small-to-midsize web applications. We’re not just for blogs. We’re not just for marketing pages. We’re for powerful, database-driven, interactive software across a variety of industries and use cases. We’re flipping the script: instead of asking “why Bridgetown?” you’ll be asking “why not?”\n\nSome stats for Bridgetown in 2024:\n\n\n  6 releases, 3 in the 2.0 beta line\n  15 contributors across all the releases\n  Grew by roughly 200 stars on GitHub\n  574 members in Discord\n  409 followers on Mastodon\n  And now we’re on Bluesky too!\n\n\nWe’ll have a slew of exciting announcements to make about the broader Bridgetown ecosystem in 2025 as v2 heads out the door, so be sure to subscribe and join in all the right places. If you’ve been thinking to yourself “somebody really needs to shake up the Ruby landscape with a fresh vision for how web applications are architected and deployed—and how to strengthen the open source community with safety and inclusiveness” — don’t worry, we’ve got some good stuff cooking.\n\nIn the meantime, we thank you for your continued interest and support of the Bridgetown project and want to wish you a very Happy New Year! Go Ruby!"
        },
        {
          "id": "release-bridgetown-2.0-beta-3-with-better-performance",
          "title": "Performance Boost with Bridgetown 2.0 Beta 3",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/bridgetown-2.0-beta-3-with-better-performance/",
          "content": "The third beta of Bridgetown 2.0 is here! Read the release notes, review our “edge” documentation, and you will likely want to take a look at our 2.0 upgrade guide if you’re coming from Bridgetown 1.x. If you’re new to the Bridgetown 2.0 release cycle, read our Road to Bridgetown 2.0 series.\n\nSome of the goodies in Beta 3:\n\nIt’s not often we’re able to see a major performance gain with a single fix. Thankfully, with this PR in place, we’ve seen full build performance gains of 15-25%…possibly even higher. Huge thanks to Maxime Lapointe for the awesome detective work.\n\nA bug resulting in Liquid templates not working as expected with the new fast refresh feature has been resolved.\n\nSites using multiple locales (aka i18n) are now better supported by fast refresh. If you encounter any additional issues seeing updated content after making changes to your content files, please file an issue and let us know.\n\nYou can now switch to ESModules (ESM) and away from CommonJS for your projects! Run bin/bridgetown esbuild update to install the latest frontend configuration. You may need to edit some of your JS files manually in order to remove outdated require statements and use import instead.\n\nFinally, we’ve been doing a lot of behind-the-scenes work to improve API-level documentation as well as guide-level documentation. For example, you can read up on our new all-Ruby syntax for HTML templates: Streamlined.\n\nAs always, if you run into any issues trying out Bridgetown 2.0 beta, please hop into our community channels and let us know how we can help. We welcome your feedback and ideas! In addition, you can follow us now on Bluesky as well as in the fediverse to stay current on the latest news."
        },
        {
          "id": "release-its-here-bridgetown-2.0-beta-1",
          "title": "It’s Finally Here! Bridgetown 2.0 Beta 1",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/its-here-bridgetown-2.0-beta-1/",
          "content": "I’m pleased to announce the first beta release of Bridgetown 2.0, the premier static site generator 🤝 full-stack web framework powered by Ruby. Many thanks to everyone who contributed! And if you’re wondering hey, where’s the new code name? …that is forthcoming. We’re going to have a little fun playing with the branding on this one, so stay tuned. 😎\n\nIn the meantime, Bridgetown 2.0 is really all about two major initiatives: removing many deprecations from the 1.x line all while tightening up internals and providing a high quality &amp; stable API, and raising baselines with improved defaults to enable a clear vision for the next several years of Bridgetown development.\n\nWe’ve written about many of these initiatives previously on the blog—see these posts for reference:\n\n\n  Part 1 (Stuck in Burnout Marsh)\n  Part 2 (New Baselines)\n  Happy 4th Birthday, Bridgetown!\n  Part 3 (Fast Refresh)\n\n\nWhat follows is a simplified list of updates as noted in our changelog. (You can also browse a 1.3…2.0 diff if you’re really curious what’s going on.)\n\nOur “edge” documentation is available here, and you will likely want to take a look at our 2.0 upgrade guide if you’re an existing user of Bridgetown. For example, there are new minimum versions of both Ruby and Node for the v2 release cycle.\n\n(Note that some of the new features/enhancements would benefit from detailed documentation which is still underway. We’ll have things more fleshed out by the final 2.0 release.)\n\nWhat’s Been Added\n\nWe’ve made it possible for Roda routes to render “callable” objects (docs) in a very straightforward manner. In MVP parlance, you can now designate your own “controller” objects to handle any requests, or utilize “view” objects to provide particular kinds of output (and out of the box, resources themselves can be returned directly as responses).\n\nPlus, we’ve simplified front matter data access using new syntax across the system (in Ruby-based templates you can omit data. in many cases, e.g. title instead of data.title), and for Roda rendering in particular it’s now much cleaner to pass data along to templates.\n\nLive reloading in development is dramatically faster on average now with the Fast Refresh feature (see the referenced post above for more on how that works).\n\nThere’s now a go-to inflector to handle various string conversions (aka folder_name/file_path.rb → FolderName::RubyFilePath) using dry-inflector (docs).\n\nWe’ve added support for new Serbea 2.0 template features, including the pipe helper which can be used even in ERB templates. And in addition, we now have a pure Ruby template syntax via Streamlined. Documentation is forthcoming, but you can take a peak at what authoring HTML using Streamlined looks like. This is Bridgetown’s official answer to techniques like content_tag in Rails or gems like Phlex, and it takes a great deal of inspiration from JavaScript’s tagged template literals.\n\nWhat’s Changed\n\nBridgetown now defaults to using ERB in new site projects, rather than the previous default of Liquid. You can choose Liquid (or any supported template engine) when you create a new site, but without specifying a particular option Bridgetown will assume ERB.\n\nThe config YAML file is now optional—in fact, in new Bridgetown projects only the config/initializers.rb file is generated as the primary form of configuration. In addition, previous support for .toml files has been removed. We’ll be upgrading our documentation to point out these new defaults.\n\nWe’ve significantly refactored the file-based routes plugin under the hood for more robust behavior and continuing improvements over time to support advanced full-stack applications.\n\nAs promised, an initial batch of first-party framework code (i.e., bridgetown-foundation) to replace Active Support-based dependencies has landed. And we’re making use of the new Inclusive gem to package and import utility code.\n\nOur start command now uses Rackup (and Rack 3) instead of directly interfacing with Puma. This paves the way for future developments such as supporting other Rack-compatible application servers in addition to Puma.\n\nThe way we handle front matter has been extracted into standalone loaders. This will make it easier to maintain our front matter loaders over time as well as offer the possibility of supporting new front matter formats in the future.\n\nWhat’s Been Removed\n\nWe’ve removed legacy code dealing with permalinks, along with a variety of other previously-deprecated code paths. We’ve also removed Cucumber, rewriting our integrated feature tests to consolidate around Minitest exclusively. And our Webpack integration is at last done away with, in lieu of a complete focus on esbuild. End of an era!\n\nWhat’s Next\n\nBridgetown 2.0 is generally “feature complete” at this point, although one or two small enhancements waiting in the wings might sneak in before the final release. In general however, we’re focusing on fixing bugs and tightening up reliability over the next few weeks as we transition from beta status to production.\n\nBeyond that, the team is working on additional ecosystem functionality with gems such as Lifeform (form rendering) and Authtown (accounts and logins) which can be used on Bridgetown projects. Our Sequel gem for database access is already out the door. And for server-side rendering of web components in Ruby with a companion library offering client-side interactivity…well, that too is forthcoming. 😁\n\nAs always, if you run into any issues trying out Bridgetown 2.0 beta, please hop into our community channels and let us know how we can help. And if you’re new to Ruby, we’re happy to recommend other resources and communities which can give you a leg up. We try our best to follow the Ruby language motto: MINASWAN. (Matz Is Nice And So We Are Nice…Matz being Yukihiro Matsumoto, the creator of Ruby.)"
        },
        {
          "id": "future-road-to-bridgetown-2.0-fast-refresh",
          "title": "Road to Bridgetown 2.0, Part 3 (Fast Refresh)",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "future",
          "tags": "",
          "url": "/future/road-to-bridgetown-2.0-fast-refresh/",
          "content": "So before I get right into it, I’m happy to report that Bridgetown 2.0 development progress is proceeding at a rapid pace! Many of the features talked about in the previous rounds (here and here) are well underway, alongside some significant quality of life DX improvements which will make this release really sizzle. Plus I’m looking forward to blogging about some of the underlying particulars soon at the recently rebooted Fullstack Ruby.\n\nRight, now to the topic at hand. I’ll get this out of the way: the Fast Refresh feature—a default setting for the development server coming in v2.0—is not like HMR (Hot Module Replacement), a popular strategy for JavaScript frameworks to make reloading changed code speedy during development. This is in part because—aside from any actual JavsScript files you may have for your frontend—Bridgetown doesn’t use JavaScript.\n\nBridgetown uses Ruby, and to be precise, is based on “old-school” principles of static site generation. (Unless we’re talking about dynamic routes served via Roda…we’ll save that for a future discussion!) The way it works is you have a repo with a wide variety of input files—Markdown, CSV, HTML templates, images, other assets—and a build process transforms all of those input files in a variety of ways and then outputs them in formats suitable for a functioning website. In that sense, to “reload” a site after making a file change means to go through that entire build process again. For a small site, a full rebuild might be relatively quick…or it might be quite slow if you have hundreds or thousands of pages and assets to deal with.\n\nWhere We’ve Been\n\nBridgetown’s progenitor, Jekyll, offers a limited scope of understanding around the types of content &amp; code to rebuild on-demand as it doesn’t come with any frontend pipeline and doesn’t provide any “live reload” functionality at all for the browser—nor will Jekyll reload Ruby extensions in a repo when you change that code. However, what Jekyll does have—as do many SSGs out there—that Bridgetown hasn’t had to date is an optional “incremental” regeneration process—that is, a change to a content file (Markdown, etc.) doesn’t necessarily require rebuilding the entire site from scratch. But even that can come with limitations, and in many cases a change to a file doesn’t trigger the neccessary downstream changes elsewhere—aka you might revise a headline in Markdown file over here, and over there a template which references said headline would still display the old content.\n\nStuff like that really grinds my gears. It’s why Bridgetown hasn’t offered an incremental regeneration feature or fast refresh or whatever you want to call it. Trust is the issue. I want to feel confident that the content I’m viewing in development is as accurate as possible, and to a certain degree, you can’t ever trust that what you’re seeing is actually correct when anything less that a full, from-scratch rebuild has occured.\n\nNevertheless, it’s admittedly a serious UX fail when sites get larger and larger and you suddenly realize that when you fix a typo in a Markdown file you now have to wait 8 seconds before you see that fix appear in the browser. Unacceptable! In an ideal world, you wouldn’t have to wait 8 seconds. Hopefully you wouldn’t even need to wait 800 milliseconds. The refresh would occur as close to “instantaneously” as possible.\n\nThat’s the goal with Fast Refresh in Bridgetown 2.0.\n\nHow did we accomplish this feat? Read on…\n\nSignals (Of Course 😏)\n\nThe concept of Signals has taken the frontend world by storm, and that shift has started to ripple outward into other computing contexts as well. So what are signals? In a nutshell, signals are reactive variables—aka values which, when mutated, cause all subscribers to be notified. If you’re familar with the simpler pattern of observables, you know you have to set up subscriptions by hand—a tedious and sometimes error-prone endeavor. Signals instead are regularly paired with effects—functions which will automatically subscribe to any signals referenced within the function when executed. Later, whenever those signal values change, the effect functions re-execute—like magic! ✨\n\nFor a deep dive into this topic from the Ruby perspective, check out Episode 9 of Fullstack Ruby. TL;DR: thanks to the Signalize gem which I wrote as a direct port of Preact Signals, we can use signals in Ruby. And the reason this is such a game-changer for Bridgetown?\n\nBy placing resource data into signals, and transformation steps inside of effects, we can track via effects which resources or generated pages would need to be updated due to signals changing. In other words, during the initial full build, we’re assembling a dependency graph in real-time of which pages should be rebuilt later. That way during a refresh, instead of a simplistic incremental regeneration acting on one piece of source data and leaving that data stale on other parts of the site—or just doing the full rebuild which can take a long time—we can instead only rebuild 5 interdependent pages, or 10 pages, or even 50 pages…but probably not 200! (Plus we also get to skip a lot of other slow code reloading logic and so forth whenever it’s simply not necessary…which is the majority of the time!)\n\nThe Devil’s in the Details\n\nThis process is fairly straightforward if the changed file in question is indeed a resource. We can build up the resource (which could be a page at a URL or it could be a data file) + dependency graph, and simply regenerate those resources. But things get tricky when “generated pages” are involved such as using prototype pages or pagination. For those cases, we need to backtrack to an original resource and re-extract all the necessary data for the generated pages which follow.\n\nAll of the places where reactive data can end up are vital to the integrity of the Fast Refresh process. Think of all the contexts where content cohesion is crucial:\n\n\n  If you change the name of a person in _data/authors.yml, all of their blog posts should update.\n  If you change the title of a document, a sidebar with a list of those documents should update whichever pages include that sidebar.\n  If you update a blog post description, “page 4” in an archive somewhere should update with that new description.\n\n\nDoing all that is pretty challenging if you have to trace all those dependencies by hand (either under the hood with complex automagical logic, or with specific directives users must understand and maintain themselves…eww!).\n\nThankfully…signals to the rescue. And we’re not simply tracking resource&lt;-&gt;resource connections, but connections between templates and rendered components. If I update a single component template, but that template is only referenced by one or a few resources (or layouts used by those resources), why should the entire site get rebuilt? Let’s just rebuild the resources which directly render that component. Even layouts factor into this: if you edit a layout, only the resources which use that layout will be regenerated.\n\nFor the most part, Fast Refresh will require no changes to existing site repos. It’ll “just work”. But we do have a new mechanism in particular for handling site-wide data which can prove quite interesting. Instead of reaching for site.data, reach for site.signals. All of the keys will be shortcuts for setting/getting signal values—aka site.signals.authors is shorthand for site.signals.authors_signal.value and site.signals.authors.value = ... is shortchand for site.signals.authors_signal.value = .... This means you can save and access site data throughout various plugins/templates, and any changes made to data files will propagate accordingly during Fast Refresh.\n\nAll of this serves to ensure that when you update a file, it’s often rebuilt so quickly that by the time you switch from your editor back over to the site in the browser, it’s already been refreshed. (We also have increased speed overall thanks to revisions to our Rack/Roda integration!) I’ve been experiencing this rapid round-tripping a lot over the past few weeks, and it’s pretty freakin’ cool. 😎⚡️\n\nEscape Hatch\n\nThe version of Fast Refresh shipping in Bridgetown 2.0 will be good, but it won’t be perfect. There are times it may get tangled up in the web of its own dependencies, or fail to account for a particular type of change, and you’ll need to reboot the dev server—or in the worst case, temporarily switch off fast refresh in your config.\n\nFast Refresh will get top priority for bug fixes for the forseeable future, which is one of the reasons we’re releasing it switched on by default. We want as many people as possible to test this right out of the gate, so we can fix edge cases as quickly as possible. My own experience has been that even with an occasional hiccup, the quality of life improvement with the increased refresh speed more than makes up for those annoyances. Most of the time, it rocks.\n\nWe’ll also be shipping a bonus feature: a way for you to hook into Bridgetown’s live reload JavaScript process to control what happens for that browser reload. For users of frontend libraries like Swup, htmx, Turbo, etc. which can swap or even morph page DOM as part of navigation, you could use those to pull in the updated HTML for an even slicker experience. 😎\n\nPerformance is a Feature Too\n\nOne of the goals of Bridgetown 2.0 (and 2.1 and beyond) is to reframe how we look at opportunities to increase framework performance. There’s never been a desire among the core team to shave a few ms off of a synthetic benchmark, or to gain paltry bits of performance at the expense of great DX.\n\nBut if we can identify clear wins around simplifying code steps, creating modular configurations, streamlining algorithms, and encouraging certain architectures over others so as to improve the performance of both static generation and dynamic routes meaningfully, we’re ready to dive in. If you would like to contribute a test site we can use to benchmark Bridgetown 1.x vs 2.x as we fine-tune this release, please get in touch! Our hope is to gradually build up our release QA process to include regression testing…aka a full site build with each new Bridgetown release should be the same or faster, definitely not slower.\n\nOK, that does it for Fast Refresh! Stay tuned for the next installment of the “Road to Bridgetown 2.0” series all about where we’re going with our Roda and Sequel integrations. Spoiler alert: Bridgetown 2.0 will completely support Rack-native, fullstack, database-driven application requirements where even your index file can be a dynamic route if you so choose. Have your static website cake and eat your dynamic server too? 🍰 Yep! 😁"
        },
        {
          "id": "news-happy-birthday-bridgetown",
          "title": "Happy 4th Birthday, Bridgetown!",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/happy-birthday-bridgetown/",
          "content": "Four years ago today, I wrote in my Day One journal:\n\n\n  \n  “I forked Jekyll today and turned it into Bridgetown, so I’m now privately the maintainer of a Webpack-aware, Ruby-based static site generator for the modern JAMstack era. How cool (and crazy) is that?!?!”\n\n\nThe first public website launch and release of Bridgetown 0.10 happened a few weeks later, and the rest as they say is history. As always, a hearty thank you to all our sponsors and 70+ contributors who have helped this open source project flourish in ways I never could have imagined.\n\nSome “hindsight is 20/20 thoughts”\n\nI know we all sort of cringe thinking about Webpack today (esbuild forever!), but back then it was still a Big Deal and represented a major frontend shift towards using ESM, pulling in packages from NPM for both JavaScript and CSS libraries/frameworks, and compiling using JavaScript-based tools. Having a pre-configured frontend pipeline that Just Works™ come ready to roll with your site generator was (and is) nothing to sneeze at.\n\nIt’s interesting to look at my original notes on what was most urgent to add to whatever might emerge from this fork: Webpack (as mentioned), Components (not just basic includes/partials), Internationalization (i18n), and easier third-party API integration were top of the list. A promising start! But a lot of what I love today about Bridgetown hadn’t quite been conceived of yet. Much has happened in only four years! (You can find a more in-depth list of post-Jekyll features and changes here if you’re curious.)\n\nOne major direction for Bridgetown I imagined in those earlier days that we ended up totally shifting away from is a tight integration with Rails. Aside from my own perspective on Rails shifting, it turns out a significant level of interest in the potential architecture such a marriage might produce never materialized. I could do a deep dive some day into why that might be, but the good news (and a direction I never would have foreseen in 2020!) is that we pivoted into a tight integration with Roda. That proved to be a huge boon for the framework—with a lot of newer features being heavily inspired by the “Roda way” like the new Initializers system—and I’m ready to push that all to the max this year. I see no reason why, with just a tad more DX polish, a combined Bridgetown + Roda couldn’t be then used to build substantial web applications serving thousands of customers. I look forward to spreading the message that Bridgetown is far more than “just” a static-site generator (as we clearly say right on our homepage!) by promoting solid integrations with Rodauth and Sequel to round out our server-side offerings. (We’re basically just living in the JECU—the Jeremy Evans Cinematic Universe—at this point! 😅)\n\nIn closing…\n\nWe’re currently in the midst of the Bridgetown v2 development cycle, and I’ll be posting Part 3 of our blog series on the topic shortly. In the meantime, follow us on Mastodon to stay on top of the latest news. Thank you once again for all of your support over the past four years. The Bridgetown community is now larger than any one of us, and I can’t wait to see what the next four years have in store for Rubyists everywhere."
        },
        {
          "id": "future-road-to-bridgetown-2.0-new-baselines",
          "title": "Road to Bridgetown 2.0, Part 2 (New Baselines)",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "future",
          "tags": "",
          "url": "/future/road-to-bridgetown-2.0-new-baselines/",
          "content": "“And blood-black nothingness began to spin…a system of cells interlinked within cells interlinked within cells interlinked within one stem…and dreadfully distinct against the dark, a tall white fountain played.”\n\n\nOK OK, not that baseline. 😜\n\nRather, what we’re talking about are technology baselines. Minimum language version requirements. Language syntaxes. Tool dependencies. That sort of thing.\n\nThe Bridgetown 2.0 release cycle lets us reset some of these baselines, moving the framework into a more modern era without sacrificing developer experience or causing major upgrade headaches. For the most part, you should be able to keep your projects running with few (if any) changes. But of course, with a little spit and polish, you’ll be able to take full advantage of improvements lower in the stack.\n\nLet’s step through the various baselines we’ll be providing in Bridgetown 2.0.\n\nRuby 3.1\n\nBridgetown 3.1 will require a minimum Ruby version of 3.1. Our policy is that for each major version number increment (in this case, v2), we will move the minimum Ruby version to two yearly releases prior to the current one. Since today’s Ruby version is 3.3, that allows us to support Ruby 3.1 and 3.2 as well.\n\nBecause we’re moving from the past minimum of Ruby 2.7 to 3.1, this opens up a whole new world of Ruby v3 syntax and functionality being made available to the Bridgetown ecosystem. I wrote about some of my favorite Ruby 3.1 features over two years ago! And even more has happened since with the releases of Ruby 3.2 and 3.3. (YJIT, anyone?)\n\nWe suspect many Bridgetown projects are already on Ruby 3, and for those still using Ruby 2.7, the upgrade process to switch to at least 3.1 should be fairly smooth.\n\nNode 20\n\nUntil recently, for a good window of time it seemed like the particular version of Node you happened to be running wasn’t a huge deal since Bridgetown’s use of Node has been almost exclusively relegated to compiling frontend assets.\n\nWhile that will continue to be the case, we are making some specific, intentional changes to how Bridgetown integrates with Node. In order to streamline these efforts, it makes sense to standardize on newer versions of Node.\n\nAs of the time of this writing Node 21 is the current production release, but as we suspect Node 22 is right around the corner (based on Node’s yearly release schedule), we are jumping the gun just a tad and going with Node 20 as our baseline (since essentially that will be two versions prior to current once Bridgetown 2.0 is released later in Q2).\n\nWhich brings us to:\n\nESM\n\nThe JavaScript NPM ecosystem has been stumbling towards its goal of coalescing around ES Modules (ESM), rather than CommonJS, for quite a long time now. It feels like we’ve arrived at the moment when it’s now or never, so let’s do it now. For those of you only vaguely aware of what the differences are, here it is in a nutshell:\n\nconst path = require(\"path\") // CommonJS\n\nimport path from \"path\" // ESM\n\n\nYes, for those of you asking, ESM has been the only way you write JavaScript for client-side purposes (since ES6 anyway). Yet Node’s historical CommonJS manner of importing and exporting modules and packages has persisted on the server. The end is nigh though, as universal ESM syntax has been embraced more and more throughout the ecosystem.\n\nThere are two ways to instruct Node to use ESM instead of CommonJS. You can name JavaScript file extensions as .mjs. Or you can add \"type\": \"module\" to your package.json file.\n\nFor Bridgetown 2.0, we will be opting for that latter option, and all configuration files for tools like esbuild and PostCSS will be supplied in ESM format. CommonJS will still work in your existing projects, but for all new projects, it will be all ESM, all the time! 👏\n\nFarewell Yarn\n\nWhen the Bridgetown framework first launched in 2020, many people considered Yarn to be a better package manager than Node’s built-in npm solution. Many frameworks had built up around it, and even Ruby on Rails had embraced it.\n\nBut time doesn’t stand still, and neither did npm because at this point, it works just as well as “classic” Yarn and newer versions of Yarn ended up causing headaches due to changing designs, syntax, and feature-set sweet spots.\n\nSo in Bridgetown 2.0, we’re greatly simplifying and migrating to using npm by default. You still have the option of keeping classic Yarn around for use in your existing Bridgetown projects—or you can switch over to npm or even another popular package manager, pnpm. We’ll focus on npm out of the box, but switching to pnpm should you wish it will be straightforward (we’ve actually supported it in the most recent v1.x releases anyway).\n\nFarewell Webpack\n\nBridgetown has defaulted to using esbuild for its frontend bundler for some time now, but with Bridgetown 2.0 we’ll be removing official support for Webpack. This does mean we recommend migrating your projects from Webpack over to esbuild as soon as possible, since we have no guarantee Webpack will continue to work once Bridgetown 2.0 arrives.\n\nThe writing has been on the wall for some time, as the entire web industry generally pivots away from Webpack and towards more modern solutions like esbuild (Vite, Turbopack, and Parcel being some other popular options).\n\nThe good news is that we continue to feel extremely satisfied with our embrace of esbuild. I personally like to say that esbuild is the “last bundler” I’ll ever use. And I really believe that. I see no reason years from now why esbuild won’t still be perfectly adequate to perform the sorts of lightweight frontend bundling and asset pipeline tasks Bridgetown users typically require. It’s nice to have that level of confidence in a framework dependency.\n\nERB by Default\n\nAnd last but certainly not least, we’ll be switching away from Liquid as our default out-of-the-box template engine and over to ERB. Bridgetown is a Ruby framework after all, and ERB is the template language most Rubyists are familiar with. The reason we ever defaulted to Liquid in the first place was an historical one…Jekyll defaulted to Liquid—and in fact only supports Liquid (you have to install a third-party plugin to get some sort of ERB support). But with Bridgetown having supported ERB for years now, it makes sense to go ERB-first.\n\nHowever, we have a few tricks up our sleeve—some extra features in the works to bring Serbea-like pipelines over to ERB, as well as a new DSL based on procs &amp; heredocs called Streamlined you can use instead of ERB in certain parts of your codebase to generate complicated HTML quickly and efficiently. (This is essentially our answer to “tag builders.”)\n\nAnd for that maximum cool factor, we’ll be unveiling an optional, first-party solution to server-rendering web components which can later be hydrated and become interactive on the client-side. We’ve already offered a solution of sorts along these lines with our integration of Lit-based web components. However, this new solution will provide a whole new component format—one which takes further advantage of Ruby as well as the proposed HTML Modules spec. Ooo…exciting!\n\n(Note that we’ll be retiring first-party support for Haml and Slim. I don’t wish to step on anybody’s toes, but template syntaxes based around indentation/whitespace rules just aren’t on the radar of frontend developers by and large. I would argue they feel more like an artifact of Ruby’s past than anything modern developers are looking for. Let’s rally around writing HTML templates in ways which frontend devs can feel comfortable with and get up to speed on quickly.)\n\nWrap Up\n\nSo there you have it: Ruby 3.1. Node 20. npm. esbuild. ERB and a whole lot more. A solid set of defaults we believe will make Bridgetown that much more robust, appealing, and ready to tackle the challenges of tomorrow.\n\nThere are also some efforts underway to streamline parts of Bridgetown’s internals to make it easier for contributors and maintainers. Besides simply removing a handful of small deprecations, we’re in the process of completely moving away from Cucumber and towards standard Minitest for integration tests to bring them in line with the rest of the automated test suite.\n\nAlong with quality-of-life and maintenance improvements, we hope to make progress in increasing the build performance of Bridgetown. Perhaps not so much in production per se (where, to be honest, it’s less critical—the difference in your site rebuilding in CI in 12 seconds vs. 6 actually doesn’t matter much), but most definitely in development. We all know how frustrating it can be to make a small text change and then have to wait seconds—maybe even tens of seconds!—before the page will refresh in your browser. We have a solution in mind called Fast Refresh based on the principles of Signals, using the Signalize gem which is a Ruby port of Preact Signals. It’s not quite an “incremental build” type of solution, but it’ll get us where we need to go. More on all that in the next installment of Road to Bridgetown 2.0.\n\nUntil then, let us know what you’re most excited about regarding these changes in Bridgetown 2.0. As always, your feedback is most welcome!"
        },
        {
          "id": "future-road-to-bridgetown-2.0-escaping-burnout",
          "title": "Road to Bridgetown 2.0, Part 1 (Stuck in Burnout Marsh)",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "future",
          "tags": "",
          "url": "/future/road-to-bridgetown-2.0-escaping-burnout/",
          "content": "TL;DR: Bridgetown 2.0 is now under active development! Stay tuned for additional announcements coming soon of what’s next for the ecosystem! And now, on with the post…\n\n\n\nA little known area of Bridgetown, upriver from where the tourists typically spend their time on vacation, is a treacherous stretch of water known as Burnout Marsh. My boat got mired there during the back half of 2023, and I barely escaped with my life.\n\nAll right, enough of that clumsy metaphor. 😄 I didn’t want to have to write this post, believe me. I’d much rather just get on to all the juicy technical stuff that’s fun to talk about. Blog posts about “maintainer burnout” are as exciting to read as watching paint dry. It’s a bit akin to the celebrity complaining about how they have to hire security because they’re just so gosh darn famous. Like, dude, you’re famous OK? Quit yer whining before you look like an asshole.\n\nBut the fact remains that maintainer burnout is a thing, and it affects a lot of open source software projects. Everyone’s burnout looks a little different, and mine was no different in that way. A little bit of this (weird, weird time in the tech industry), a little bit of that (too many other irons in the fire), but mostly a particular thing (to be revealed momentarily) which affected the worst possible facet of my role as a maintainer of Ruby-based software: my love of Ruby. 😱\n\nTruth be told, I nearly walked away. 😞\n\nAnd the reason is both complex and simple. Here’s the simple version: the social degradation of 37signals in general and David Heinemeier Hansson (DHH) in particular nearly robbed me of all the joy I felt programming in Ruby. 😬\n\nNow nearly as distasteful to me as wallowing in “maintainer burnout” territory is wallowing in “framework authors taking potshots” territory. So you can imagine I’m doubly not feeling great about where this is headed. I’ve written some version of this post over and over again in my head, and more than once in my drafts folder which you’ll never see let me tell you right now. But like ripping off a Band-Aid, some things just have to be done. So here it goes.\n\nThe 37signals Problem\n\nI was aghast when the meltdown at 37signals happened in 2021. It was widely covered at the time, perhaps best by Casey Newton in several pieces such as Behind the controversy at Basecamp and Inside the all-hands meeting that led to a third of Basecamp employees quitting. At the time, I thought surely there would need to be some reckoning with how DHH conducts himself within open source projects as well such as Rails and Hotwire. Perhaps Rails could set up a more transparent governance structure, or at the very least announce DHH stepping down from a position of influence for a while (while making a very public stand around proper Code of Conduct (CoC) etiquette).\n\nNot only did none of that happen 🤨, but DHH made a huge stink about not being properly invited to keynote RailsConf again right away, leading to RailsConf and DHH going their separate ways and the eventual formation of the “Rails Foundation” and Rails World conferences. So…no such reckoning. DHH would maintain an iron grip on Rails indefinitely (this new foundation really just solidifying his personal influence rather than offering any sort of real check on his power or ego), and in fact go forward to “compete” with RubyCentral and RailsConf. 😩\n\nAs if this wasn’t all bad enough, the next shoe to drop dropped…and in a very public way as these things are wont to do. Out of the blue, without warning to any regular contributors or other community members, 37signals (aka DHH) simply decided to remove TypeScript from the Hotwire Turbo codebase. Again, no opportunity for discussion, no time for a heads up or any sort of guidance on how it might affect existing contributions. Just boom, here’s the PR, insta-merge within hours, self-congratulatory DHH post on HEY World, done. 😳\n\nFolks, this is not how you run an open source software project. Somebody’s hobby project on GitHub that’s really just their own little playground, sure, I guess. But not something as consequential as, oh I dunno, the frontend stack of the Ruby on Rails framework and a tool used even outside of Rails by other frameworks! 🤪\n\nNote carefully that none of what I’m saying or about to say has any bearing on the merits of removing TypeScript. We can debate those merits at our leisure, and anyone who knows me knows I’m no big fan of TypeScript. But that’s not what this is about. This is about how people govern open source projects and conduct themselves among peers.\n\nNeedless to say, this move to unexpectedly rip TypeScript out of Turbo generally went down like a lead balloon, and things got heated fast. That’s never a good sign (when long-time regular contributors to a project are themselves feeling pretty grumpy), and it eventually led to this seminal issue: Remove DHH for CoC Violations · Issue #977.\n\nTo be very clear, nobody’s claiming that making the decision to remove TypeScript was a CoC violation, but the manner in which it was done: with zero involvement of the community and no consideration whatsoever (active hostility in fact) of broad feedback about the decision. I want to quote DHH’s posted response to this claim of CoC violation in full, because there simply is no way for me to read this without feeling enraged once again, and I want you to feel enraged too:\n\n\n  “This is elevating a disagreement of a technical decision to a level of nonsense that has no place here. This project has been founded, funded, and maintained predominantly by the resources and investment of 37signals. We will set the direction as we see fit, accepting input and contributions as they’re presented, but with no guarantee of concurrence or merging. All the best finding a project that’s more to your liking in direction or leadership or otherwise somewhere else 👋”\n\n\nNotice all the very specific language DHH employs here:\n\n\n  “nonsense” — regular readers of his blog know this to be code for “woke lefties” who would dare challenge his alt-right “edgelord” positioning on a variety of topics. Every time DHH’s political machinations are being publicly challenged, you’ll see the accusations of “nonsense” trotted out (here’s but one example…stochastic terrorism is “nonsense” in DHH-speak, a product of “woke scolds”).\n  “founded, funded, and maintained by…37signals” — in other words, you, dear contributor to the Turbo repo, don’t actually matter if you aren’t specifically part of the business entity known as 37signals. This is technically open source, sure, but the benefits mostly flow in a single direction. 37signals gets all the benefits of your efforts to improve their codebase, yet meanwhile you get none of the power. Yes, you get to use their code in the end, but that’s it. That’s the only benefit. Whoop-dee-frickin’-doo.\n  “We will set the direction as we see fit” — and by “we” he means himself. DHH. The big kahuna.\n  “All the best finding a project that’s more to your liking” — aka if you don’t like how I run things, fuck you.\n\n\nFolks, there are times when situations are complicated, nuanced, with no real good guys or bad guys, and it’s genuinely hard to parse out what’s really going on and how to process the myriad of factors in order to arrive at a comprehensive decision.\n\nThis is not one of those times. 😅\n\nClearly what we witnessed in this debacle is far from a shining example of how one should govern an “open source” project. Perhaps it would be better described as “source available” — use the code, but don’t count on the stewards of the code to care for the needs of the community. And to get real specific, I am convinced that yes, DHH has indeed been in violation of his own CoC, and the real tragedy is nobody has the power to call him on his own bullshit. DHH is co-owner of 37signals, and 37signals controls all Hotwire intellectual property.\n\nPersonally, I find DHH’s continued stranglehold over the Rails and Hotwire frameworks nauseating and thoroughly unacceptable. But ultimately, that’s not the hardest part of it for me. It’s all the carrying water for him that’s gone on in the broader community. People—and yes, good people all—still keep promoting Rails (and Rails World), keep releasing Ruby code and educational resources that prop up Rails as much if not more so as Ruby, and essentially keep DHH on his pedestal.\n\nIt’s enough to make you just want to up and quit Ruby. Which I very nearly did. 😭\n\nBut, y’know, I gave myself lots of time to think and reflect. I chatted a lot with my close friends and fellow Bridgetown team mates. I mused on ways I might be able to make Bridgetown “fun” again, both in terms of ongoing maintenance as well as future feature development. I waited to see if maybe I could get my boat unstuck and past Burnout Marsh and start heading downriver towards calmer waters again.\n\nAnd now I’ve arrived here. 😌\n\n\n\nWhat’s Next for Bridgetown in 2024\n\nThis post kicks off a short blog series outlining some of the approach we’re taking to construct the next major release of Bridgetown, version 2.0. Let’s wrap up with a brief mention of what we’re announcing today as part of the “Road to Bridgetown 2.0” push. If you read my diatribe above, what I’ll say next probably won’t come as much of a shock.\n\nWe’ve begun the process of de-37signals-ifying Bridgetown. (Now there’s a mouthful! 😆)\n\nHere’s what this means. We have put an immediate stop to integrating any more dependencies run by 37signals in Bridgetown. In practical terms, this means no additional embrace of libraries like Active Support, and no continued investment in bundled configurations such as Turbo or Stimulus (in fact we’ll be removing these for Bridgetown 2.0). And over time (this will be very much an incremental thing), we will either remove our internal dependency on Rails libraries like Active Support or vendor specific code we can’t easily replace.\n\nThis is certainly not ideal. The Bridgetown codebase, and community at large, has benefited from the features provided by Active Support, Turbo, and other 37signals-run projects. But as DHH so emphatically put it, “all the best finding a project that’s more to your liking in direction or leadership or otherwise somewhere else 👋”. So that’s exactly what we are doing. We’ll be looking at other libraries — or in certain cases just building our own solutions — to replace the functionality we had relied on from 37signals.\n\nWe take Bridgetown’s own Code of Conduct seriously, and part of that approach means we need to be careful we don’t pull in third-party dependencies from open source projects which are themselves in violation of their CoCs. We’re not in the business of policing the internet, nor can we ever vouch for all other open source projects we might ever touch in some way. But it was a strategic decision originally to embrace codebases run by 37signals, and it is another strategic decision to let them go. I’ve talked about this at length with the rest of the Bridgetown core team, and we are in agreement that it’s in the best long-term interests of the Bridgetown project to take a public stand on this.\n\nSo that is merely one aspect of the work that’s ongoing as we head towards Bridgetown 2.0. But thankfully, there’s a lot more that will no doubt prove more exciting and hopeful, from a minimum requirements bump to Ruby 3.1 and Node 20 to a huge performance boost in development in the form of Signals-based Fast Refresh. More on that in the next blog post.\n\nThe big takeaway is that I’m feeling more pumped about the future of Bridgetown than I have in many months. Between sorting out feelings of disappointment around past events, and coming up with a list of improvements to the project and the ecosystem I’m very excited to be moving forward on, a new day has dawned. 🌞\n\nBridgetown 2.0 represents a sort of clean break, not just because we can remove deprecated APIs, streamline aspects of the codebase, and improve features in ways that will help make the framework faster and more stable, but because version 0.x represents an experiment, 1.0 represents something stable yet still new, and 2.0 represents longevity. Bridgetown is here to stay. We have a full major version bump looming. And we hope you’ll stick around to see what comes next. 🤓"
        },
        {
          "id": "release-bridgetown-1.3-kelly-butte",
          "title": "Presenting Islands Architecture in Bridgetown 1.3 “Kelly Butte”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/bridgetown-1.3-kelly-butte/",
          "content": "We’re pleased to announce the release of Bridgetown 1.3 “Kelly Butte”. Thanks to the many contributors who have helped make this release possible!\n\nRead the release notes or installation instructions, or to upgrade from a previous version, simply bump up the version numbers in your Gemfile and run:\n\n$ bundle update bridgetown\n\n# or\n\n$ bundle update bridgetown bridgetown-routes\n\n\nIt’s not mandatory that you update your esbuild config, but it’s definitely encouraged (especially if you’d like to take advantage of the latest experimental frontend features). To do so, run bin/bridgetown esbuild to get the latest default configuration and follow the instructions. Some of the changes required a fair bit of tinkering, so if you run into any problems please let us know so we can correct them right away.\n\nHere’s a rundown of some of the fixes and improvements before we get to the “flashy” new frontend-themed features:\n\n\n  We’ve finally upgraded our Faraday dependency to v2. 😅 Puma has well has been upgraded to v6.\n  Also the included connection helpers in the Builder API has been much improved.\n  Error handling and messaging is much nicer now! Before, certain errors would simply crash the Bridgetown dev process, and if you were viewing your website as that happened, you might not even notice there was a problem. Now, we try to surface error messages right on the website, and the dev process is also less likely to crash. Please let us know if you continue to encounter any severe issues!\n  We’ve added support for Nokolexbor as a faster alternative to Nokogiri for processing HTML Inspectors.\n  The l helper is now available alongside the t helper for localizing dates and other such strings.\n  The Lit and Ruby2JS bundled configurations have seen relevant updates.\n\n\nNow on to the good stuff. ;-P\n\nIntroducing Islands Architecture\n\nOld and busted: putting all your JavaScript, web components, and other frontend libraries in a single bundle that applies to every page on your website.\nNew hotness: only bundling the bare minimum (if anything) for your website as a whole, and instead using “islands” on individual pages as needed.\n\nAs it says in the documentation, the term Islands Architecture was coined a few years ago by frontend architect Katie Sylor-Miller and further popularized by Preact creator Jason Miller. It describes a way of architecting website frontends around independent component trees, all rendered server-side initially as HTML but then “hydrated” on the frontend independently of one another.\n\nNow in Bridgetown 1.3, we’re bringing islands architecture to you with a seamless integration between our view components and our esbuild frontend bundling system. And for even more flexibility, you can even orient your Roda routes around “islands” for a truly modular, full-stack approach to web development.\n\nThis is an early step forward for the framework, so your feedback is crucial as we increasingly align our best practices with the latest improvements across the industry.\n\nDeclarative Shadow DOM\n\nAs it so happens, islands architecture plays very nicely with an experimental new frontend feature we’re super excited about: Declarative Shadow DOM. You can use DSD in your layouts, components, and generally anywhere it would be beneficial to increase the separation between presentation logic &amp; content and work with advanced scoped styling APIs. And of course, paired with islands architecture, you can essentially get “hydrated” components that are first server-rendered and then become interactive on the client-side for “free” using native browser APIs—but only when and where needed for optimal performance. Wow!\n\nWe’re considering these features “experimental” for now, but rest assured, we fully expect they will become mainstays of Bridgetown websites and application architectures in the coming months and years. We can’t wait to see what you come up with!\n\nMore Frequent but Smaller Releases\n\nI personally have found it a bit challenging to stay on a steady release schedule this year, and I think it all stems from my reluctance to bundle “mere” fixes and tweaks into a release that doesn’t offer a marquee feature to promote. That’s my bad, and henceforth I plan to correct it. So starting now, expect to see Bridgetown 1.3.1, 1.3.2, etc. in a more rapid succession, and possibly even 1.4 that’s mostly about minor improvements.\n\nWe’ll never match the release pace of some of our (ahem) JavaScript-based compatriots in the framework space, but we can do better than we have. I also intend to focus more effort on providing improved guidance for new contributors and even new core team members to hop aboard the project and make a meaningful impact.\n\nAs always, if you run into any issues trying out Bridgetown please hop into our community channels and let us know how we can help. If you’re new to Ruby, we’re happy to recommend other resources and communities which can give you a leg up. Cheers!"
        },
        {
          "id": "videos-bridgetownconf-2022",
          "title": "BridgetownConf 2022 Talks Now Online",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "videos",
          "tags": "",
          "url": "/videos/bridgetownconf-2022/",
          "content": "BridgetownConf 2022 was a smashing success! It was held online as a Zoom conference. I’d never run any sort of large-scale event like this, so I was pretty nervous! Thankfully, Zoom has really blossomed as a fantastic suite of technologies. Not only was it pretty great to use in the moment on my Mac, but the cloud recording features worked flawlessly. (My upload bandwidth isn’t super great so my own camera stream has the occasional hiccup, but I wouldn’t blame that on Zoom.)\n\nAll that to say, I expect more single-session meetups on Zoom to follow in the coming year as a way to keep the community engaged and share cool tech demos. As for BridgetownConf itself, will we do it again in a year’s time? Will it be held in person as well? I won’t speculate on the latter question, but as for the former, you betcha we’ll be doing this again!\n\nAll right, enough of my rambling. All of the videos are available here on the BridgetownConf website. Eventually we’ll syndicate them over to YouTube, but for now, enjoy! ☺️"
        },
        {
          "id": "release-1.2-bonny-scope-next-level",
          "title": "Bridgetown 1.2 “Bonny Slope” Takes Your Applications to the Next Level",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/1.2-bonny-scope-next-level/",
          "content": "Earlier this year we released Bridgetown 1.1 “Belmont”, which itself was a followup release after 1.0 “Pearl”. It’s been a busy year! 😄 And now we’re pleased to announce the beta release of Bridgetown 1.2 “Bonny Slope”. There are some specific points to make around upgrading to Bridgetown 1.2 for both sites and plugins, so read up on all that in our latest Upgrade Guide.\n\nThere’s some cool stuff in the Bridgetown 1.2 release such as simplified data access in templates, and “slotted” content for both templates and components. But before we get to that, let’s discuss the Big Feature of Bridgetown 1.2 which at first glance seems not very exciting, but is actually really really important and will affect all your sites &amp; applications going forward.\n\nStart Me Up\n\nMost frameworks have some sort of initialization or boot up sequence. They typically give you a way to hook into this initialization sequence in order to import new code or customize what happens when the framework first loads. No doubt you have experienced this in many other frameworks from Rails to NestJS.\n\nBridgetown, curiously, has not offered an initialization sequence that’s “open to the public”. We just did stuff, as if by magic. Some magic in programming is good, but when it comes to how your application starts up and first runs, we’d prefer to err on the side of explicit determinism. If you want A, B, C, D, and E to happen—in that order— and only D should happen in this context, and only E should happen in some other context—then by golly you should be able to specify that in your code. (If you’re curious, there’s a whole podcast about this singular topic on Fullstack Ruby.)\n\nTo date, Bridgetown’s configuration format has been a single YAML file. You can set some options and…that’s it. Fine for simple projects, but after a while this starts to feel rather limiting. You could also add some Ruby code to your plugins/site_builder.rb file to set up a gem and whatnot, but honestly that file was never designed expressly for placing arbitrary “run this once and only once” boot code. In addition, we’ve had a long-running convention that placing certain gems in a certain Gemfile group (bridgetown_plugins) would mean that they are automatically loaded by Bridgetown during initialization. But this convention means you’d never really know what is loading when, and it doesn’t provide any code-level mechanism for attaching configuration options to that plugin. And more recently, our integration with the Roda web toolkit means we have to accommodate Roda plugins as well, which are traditionally loaded and configured in their own unique way.\n\nCustomize All the Things, Now in Ruby 😎\n\nWouldn’t it be great if we could solve all of these problems at once? 🤯😃\n\nYes, it would. And that’s just what we did in Bridgetown 1.2. We’ve introduced a brand new Ruby-based configuration format that lets you require gems, load plugins, set configuration options, and customize your Roda application—all at the same time with everything in one place. Say hello to config/initializers.rb. It looks something like this:\n\nBridgetown.configure do |config|\n  permalink \"simple\"\n  timezone \"America/Los_Angeles\"\n\n  config.autoload_paths &lt;&lt; {\n    path: \"models\",\n    eager: true\n  }\n\n  init :dotenv\n  init :ssr do\n    setup -&gt; site do\n      site.collections.products.read\n    end\n  end\n  init :\"bridgetown-routes\"\n  init :\"bridgetown-feed\"\n  init :\"bridgetown-lit-renderer\"\n  \n  only :server do\n    init :parse_routes\n  \n    roda do |app|\n      app.plugin :sessions, secret: ENV[\"RODA_SECRET_KEY\"]\n    end\n  end\nend\n\n\n(Don’t worry if this all looks like gobble-dee-gook at first…it will be become clearer as you get familiar with the new system!)\n\nBesides the obvious stuff like setting configuration options, you can set up your plugin boot sequence using init statements. And you can nest configuration within only or except blocks for context-specific settings.\n\nBridgetown plugins can provide their own initializers that are built out of real Ruby code and accept options as real Ruby objects. You can also write your own initializers, initializers can depend on other initializers, you can chain multiple initializers together, you can introspect initializers during boot…yeah, it’s a lot. 😄\n\nRead more about this in our new Initializers documentation. If you maintain Bridgetown plugins, please start upgrading your plugins to support initializers in Bridgetown 1.2+. We also welcome your feedback on the new system to make things as easy and expressive as possible.\n\n(What about bridgetown.config.yml? We will continue to support this configuration format for the foreseeable future, and in Bridgetown 1.2 it’s still recommended to have that file in place. But in Bridgetown 2.0+, it’s likely we will make it entirely optional. Ruby-based configuration can just do so much more than any YAML file.)\n\nWhat This Enables in the Ecosystem\n\nThe story doesn’t end there. A whole new ecosystem of plugins is now possible which will unlock powerful new features in your Bridgetown applications.\n\nFor example, you can now add Active Record to your Bridgetown projects. Pull content in directly from a database, or even create a dynamic CRUD-style application without ever leaving Bridgetown. We’ve also released a new Turbo plugin that lets you use Turbo Streams in responses from your Roda routes for some awesome UX around submitting forms. Speaking of submitting forms, there’s a new gem in the works called Lifeform which gives you the means to create declarative form objects which can be easily rendered in your templates (and then you’d process those form submissions via Roda routes). Sweet!\n\nAnd stay tuned for even more goodies: an integration with Sidekiq for running background jobs to do all sorts of things from sending out email digests to updating content on your site without you having to lift a finger. Or how about authentication and authorization, or paywalls, or a cohesive story around using Stripe for accepting payments. Whoa!\n\nBridgetown 1.2 really blows the doors wide open for a new level of sophistication and quality around the kinds of plugins you can use in your applications. 2023 will be The Year of Bridgetown Plugins. And as we’ve been saying for some time now, we’re no longer just a static site generator but a feature-rich Ruby web application framework. Let’s get building.\n\nAbout Those Other Features Though…\n\nThe new configuration/initialization system alone may be a lot to take in, but Bridgetown 1.2 also comes with some helpful improvements to make your life as a developer easier. Namely: simplified front matter/data access, and content slots.\n\nAre you getting sick of typing in resource.data.xyz all the time? Yeah, me too. So now you don’t have to. ☺️ Just type data.xyz. Boom! data.title, data.video_id, whatever, it’s all good. For certain fields of course, you’ll still need to use the resource object directly like with resource.relative_url or resource.summary.\n\nBut that’s not all! You can now merge in site data and access it via the same data object! So for example you could type in data.authors[data.author] to get access to site data about the author of a particular resource. Awesome sauce. Read the docs here.\n\nContent slots provide a new way to manage how your content flows through templates and layouts. In a resource or page template, you could define a block of content to be put into a slot, and then “up higher” in the view hierarchy (a layout or a partial) you could render out the slotted content. This is great for additions to &lt;head&gt; or right above &lt;/body&gt;, or extra content on sidebars…you name it. Slots are also a fantastic addition to Bridgetown’s Ruby components…you can render parts of your component via slots like headers, footers, or other content regions. Read the docs here.\n\n(Note: this feature is only available via Ruby-based templates such as ERB, not Liquid.)\n\nSave the Date! BridgetownConf 2022 is Coming Up Quick!\n\nWith all of the work to get Bridgetown 1.2 out the door, we’re a little behind in getting our official materials published for BridgetownConf, but you can go here to save the date and get notified when it’s ready! It’s happening online, world-wide, for free on Monday, November 7, 2022. Don’t miss it! We’ll dive into some of the new features of Bridgetown 1.2, as well as showcase demos of how folks are building real-world sites and applications using Bridgetown.\n\nThanks for reading this far, and as always if you run into any issues trying out Bridgetown please hop into our community channels and let us know how we can help. And if you’re new to Ruby, we’re also pleased to recommend other resources and communities which can give you a leg up in learning this delightful and productive language."
        },
        {
          "id": "release-1.1-belmont-is-here",
          "title": "Bridgetown 1.1 “Belmont” Has Landed",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/1.1-belmont-is-here/",
          "content": "We’re pleased to announce the release of Bridgetown 1.1 “Belmont”.\n\nRead the release notes or installation instructions, or to upgrade from a previous version of 1.0, simply bump up the version numbers in your Gemfile and run:\n\n$ bundle update bridgetown\n\n# or\n\n$ bundle update bridgetown bridgetown-routes\n\n\nYou’ll also need to run bin/bridgetown esbuild|webpack update to get the latest default configuration of esbuild or Webpack installed.\n\nCheck out our initial beta announcement for a full rundown of what’s new in v1.1. The TL;DR version is:\n\n\n  I18n: you can create multilingual websites featuring multiple locales with URLs to match (aka /en/products/widget, /es/products/widget, etc.).\n  HTML &amp; XML Inspectors: modify your rendered markup after the fact with some Nokogiri magic!\n  Sass support within esbuild/PostCSS bundling: we forget to mention this in the beta announcement! Bootstrap and Bulma users rejoice, you’re stuck with Webpack no longer…\n  Your choice of automated installations of Lit, Shoelace, Ruby2JS, and Open Props. Oh yeah.\n  Phrase highlighting in Markdown (aka highlighed text) via :: or ==, along with a default syntax highlighting stylesheet.\n\n\nWe’re also excited about publishing our first round of documentation on how to migrate from Jekyll to Bridgetown! If there’s anything we can add to improve the migration guide, let us know.\n\nPlease visit our Community page to learn how to submit feedback, request help, and report issues. And a big shout out to our contributors and all who help make Bridgetown a thriving and growing community.\n\nAs Per Usual, More Changes are in Store…\n\nIn our “Cherry Blossoms 2022 Edition” of what’s coming next for Bridgetown, we talked about a few ecosystem additions and improvements coinciding with the release of Bridgetown 1.1 such as an ActiveRecord plugin.\n\nWell…it turns out that will have to wait for Bridgetown 1.2.\n\nHere’s the deal: Bridgetown’s Builder-based plugin system is an evolution from Jekyll’s plugin architecture. And Roda meanwhile has its own robust plugin ecosystem. Both solutions are fine, but as I’ve been going deeper and deeper into building web applications and pulling in various Ruby gems using the Bridgetown + Roda “metaframework”, it has become clear to me that a canonical method of configuring and extending codebases using an understandable and comprehensive initialization process was necessary. The ad hoc method of adding various gems and throwing various “requires” and initialization steps in a Ruby file here or there simply wasn’t cutting the mustard. Things like dotenv, Mail, Stripe, Turbo &amp; CableReady, etc.\n\nSo work is now underway on a new initialization process which is self-aware of which pieces can be shared between static builds and the Roda server, and which pieces are server-only (or static-only, although I suspect that’s much more rare). In addition, the way Bridgetown plugins get built will change a little—the ultimate goal being that the bridgetown_plugins group within Bundler will no longer be necessary as dependencies/initializers will be defined in a concrete file with a straightforward DSL. Yes, just like how Roda-specific plugins work today.\n\nWe also have a few tricks up our sleeves for component rendering, testing, and even preview in tools like Storybook. Ongoing experiments there look quite promising.\n\nWhile I’m disappointed to have to backtrack a little in what I’d promised earlier this year, I anticipate it will all start to come together sooner rather than later. The plan for the Bridgetown 1.2 release (shortly before the start of Q4 2022) is that will be laser-focused on enhancements to Roda, SSR, Components, and Gem integrations, which should prove to be a welcome improvement to Bridgetown as a framework for building fast, stable, maintainable, and understandable web applications.\n\nIn the meantime, enjoy the release of Bridgetown 1.1…\n\n…and to Whet Your Appetite…\n\nI recently migrated my business website from Netlify to Render, and in the process I needed to handle contact form submissions directly since I could no longer use Netlify Forms. Bridgetown Routes (and the upcoming Roda Turbo plugin) to the rescue!\n\nHere’s the src/_routes/contact-form.serb route file in its entirety. In a nutshell, I render the bottom template portion twice: once in text form and one as HTML. I then make sure the “bot check” field was submitted correctly, otherwise I use Turbo to swap the field out for one with an error notice. If all is well, I use the Mail gem to deliver the contact message to myself, and finally update the contact form modal by re-rendering the ContactModal component with Turbo.\n\n~~~{%\nr.post \"reset\" do\n  sleep 1 # intentionally allow modal to close on the frontend first\n  turbo_stream.replace \"contact-modal\", render(ContactModal.new step: 1)\nend\n\nr.is do\n  render_with =&gt;\n    text_message\n  render_with(\n    data: { layout: :emails }\n  ) =&gt;\n    html_message\n  \n  next html_message if r.params[:preview]\n\n  if r.params[:bot_check] != \"11\" &amp;&amp; r.params[:bot_check] != \"eleven\"\n    next turbo_stream.replace_all(\n      \"#contact-modal sl-input[name=bot_check]\",\n      ContactModal.new(step: 1).bot_check(error: true)\n    )\n  end\n\n  mail = Mail.deliver do\n    to      \"Jared White &lt;jared@whitefusion.studio&gt;\"\n    from    \"Jared White &lt;jared@whitefusion.studio&gt;\"\n    subject \"Whitefusion Contact Form\"\n\n    text_part do\n      body text_message\n    end\n\n    html_part do\n      content_type \"text/html; charset=UTF-8\"\n      body html_message\n    end\n  end\n\n  turbo_stream.update \"contact-modal\", render(ContactModal.new step: 2)\nend\n%}~~~\n\n# Whitefusion Contact Form\n\n## Email Address\n\n{{ r.params[:email] }}\n\n## Message\n\n{{ r.params[:message] }}\n\n\nAnd because the website code is public on GitHub, you can check it out here.\n\nThis may be a fairly simple example in the end, but getting the ecosystem moving in concert up to this point has been quite the journey. I can’t wait share more with you all. ☺️"
        },
        {
          "id": "release-welcome-to-belmont-1.1-beta",
          "title": "Welcome to Belmont and the Beta Release of Bridgetown 1.1",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/welcome-to-belmont-1.1-beta/",
          "content": "Hot off the heels of our Bridgetown 1.0 release, we return to announce the beta of v1.1 “Belmont” and an array of fun new features. The new edge documentation is available here.\n\nTo upgrade and try out the beta of 1.1, edit your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 1.1.0.beta2\"\n\n\nand then run:\n\nbundle update bridgetown # or just bundle update\n\n\nYou’ll also want to run bin/bridgetown esbuild update  or bin/bridgetown webpack update to get the latest default  frontend configuration installed.\n\nSo what’s new in 1.1? Let’s find out!\n\nInternationalization (I18n)\n\nYou can now configure multiple locales for your website and set which particular locale should be considered “the default”. The Ruby I18n gem aids in storing and accessing translations, the same library used by Ruby on Rails. Thus many of the same conventions will apply if you’re already familiar with i18n in Rails. There are also several mechanisms for defining translated variants of your Markdown &amp; HTML content and for switching freely between the locale variants.\n\nCheck out the docs on i18n to see how to add new locales to your website.\n\nHTML &amp; XML Inspectors\n\nThe Inspectors API provides a useful way to review or manipulate the output of your HTML or XML resources. The API utilizes Nokogiri, a Ruby gem which lets you work with a DOM-like API directly on the nodes of a document tree.\n\nHere’s an example of an Inspector which automatically adds target=\"_blank\" attributes on all outgoing links:\n\nclass Builders::Inspectors &lt; SiteBuilder\n  def build\n    inspect_html do |document|\n      document.query_selector_all(\"a\").each do |anchor|\n        next if anchor[:target]\n\n        next unless anchor[:href]&amp;.starts_with?(\"http\") &amp;&amp; !anchor[:href]&amp;.include?(site.config.url)\n\n        anchor[:target] = \"_blank\"\n      end\n    end\n  end\nend\n\n\nCheck out the docs on Inspectors to see how you can manipulate the output of both HTML &amp; XML content on your website. (Hat tip to Cory LaViska who provided the inspiration for this feature!)\n\nAutomated Installations of Lit, Shoelace, Ruby2JS, and Open Props\n\nBridgetown has a feature called Bundled Configurations which lets you install popular and useful tools or integrations in an automated fashion. In Bridgetown 1.1, we’ve added new configurations for:\n\n\n  Lit: For advanced frontend interactivity. Every Lit component is a native web component, with the superpower of interoperability. This makes Lit ideal for building shareable components, design systems, or maintainable, future-ready sites and apps.\n  Shoelace: An instant design system and UI component library at your fingertips. Use CSS variables and shadow parts to customize the look and feel of Shoelace components in any way you like.\n  Ruby2JS:  An extensible Ruby to modern JavaScript transpiler you can use in production today. It produces JavaScript that looks hand-crafted, rather than machine generated.\n  Open Props: A collection of “supercharged CSS variables” and optional normalize stylesheet to help you create your own design system.\n\n\nAlong with prior configurations such as Turbo and Render, you can go from brand-new project to deployed production website in less time than ever!\n\nPhrase Highlighting in Markdown and Default Syntax Highlighting\n\nWe now support using == or :: in Markdown files to denote highlighted portions of text using the &lt;mark&gt; HTML tag. So This should be ==highlighted== folks! will get converted to This should be &lt;mark&gt;highlighted&lt;/mark&gt; folks!.\n\nIn addition, we now ship a default stylesheet for syntax highlighting of code in your Markdown files. No more hunting around for a theme right out of the gate!\n\nAnd Many More…\n\nThere are plenty of other small improvements and fixes in Bridgetown 1.1, so be sure to read the release notes. And no doubt a few more are waiting in the wings before the final release of v1.1.\n\nWe greatly value your feedback in order to fix bugs as well as improve documentation. Please visit our Community page to learn how to submit feedback, request help, and report issues. And a big shout out to our contributors and all who help make Bridgetown a thriving and growing community. Keep your spirits high!"
        },
        {
          "id": "future-whats-next-cherry-blossoms-edition",
          "title": "What’s Next for Bridgetown, Cherry Blossoms 2022 Edition",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "future",
          "tags": "",
          "url": "/future/whats-next-cherry-blossoms-edition/",
          "content": "Springtime Blossoms in Cedar Hills Park\n\n\nThe flowers are blooming, the brooks are bubbling, the birds are, um, twittering…tweeting…wait are we still allowed to use those words outside of the social network?!\n\nAt any rate, spring is in full swing here in Portland and so is work on the next version of Bridgetown! Not only that, but progress is being made on several fronts in the overall Bridgetown ecosystem.\n\nLet’s take a sneak peek at what’s coming next:\n\nBridgetown 1.1\n\nNow that we’ve had a bit of time to breathe and get fully settled into v1.0, there is a myriad of enhancements and fixes we’re looking to pull together for the next point release. Probably the “flashiest” feature on this list is i18n, which stands for Internationalization.\n\nBridgetown 1.1 will offer a simple and predictable way to set up multiple “locales” for a single website. Think resources like marketing pages, blog posts, educational content, products, and more all publishing to URLs such as /en-US/products/92781-fancy widget or /es/docs/usage or /zh/articles/next-level without having to manually place any source files in locale-specific folders. The way this will work is two-fold:\n\nFor some resources, you’ll create one for each locale. So you might have next-level.en.md, next-level.es.md, next-level.fr.md all living right next to each other. Bridgetown will provide the necessary helpers so you can cross-reference the “same” resource in each locale.\n\nIn other cases, you might want just one single file to get published out to all the locales. Simply create a “multi-locale” resource and you can then embed both conditional and “global” content within the file. This approach also works in templates/partials/components. By checking the value of site.locale, you’ll know which locale is currently being rendered out.\n\nAlso, through use of the I18n.t helper combined with locale-specific YAML files, you’ll be able to store and reuse translated content strings as well. (If this part sounds very similar to Rails…well, it is! We’re using the exact same i18n Ruby gem.)\n\nAll of these i18n features will also be integrated into pagination and archives (via prototype pages), so you’ll be listing only the available content in the currently selected locale/language.\n\nWe’ll have more examples and documentation ready to roll when this gets released. We hope it will provide a solid foundation upon which to build global content reaching a wide variety of audiences!\n\nOther features slated for v1.1 include more bundled configurations for popular frontend packages such as Lit, Shoelace, Ruby2JS, and Open Props; additional tightening up of our Roda integration for dynamic routes/SSR; and seamless Sass support for esbuild (it’s currently only supported still with Webpack).\n\nActiveRecord Plugin\n\nConcordantly (ergo, vis-à-vis) with the release of Bridgetown 1.1, we will be releasing an official plugin to provide ActiveRecord support within Bridgetown.\n\nWhy, you may ask, would you need to access a database from a Bridgetown site?\n\nIn fact, why not?\n\nJust because you’re using a static site generator doesn’t mean you should be banned from accessing a database. If you already have a Rails app or other means of creating and managing data, being able to access that same database directly from Bridgetown during the build process means you don’t need to go to the extra trouble of building a REST API or something to that effect for data transfer within Bridgetown. Rather you could stick some ActiveRecord models right into your repo, pull stuff right out of the database, and you’re golden!\n\nNot to mention since Bridgetown also has full dynamic routing/SSR abilities thanks to its Roda integration, once you add ActiveRecord you can do all sorts of fun things like build user signups and handle online payments and process form submissions…the list goes on and on!\n\nOur ActiveRecord plugin will provide full support for generating database schemas and running migrations courtesy of the standalone_migrations gem, and it will also save up-to-date schema information in comment blocks at the top of models courtesy of the annotate_models gem. Plus hot reloading via Zeitwerk (including Concerns), along with the expected YAML DB config which seamlessly maps to Bridgetown development, test, and production environments…well, it’s exactly the streamlined DX you’d expect.\n\nOver time we anticipate this becoming the most productive ActiveRecord integration outside of Rails itself, and it’ll likely give many integrated database solutions in other non-Ruby SSGs and web frameworks a run for their money.\n\nAuth + Auth\n\nBuilding on top of ActiveRecord support, we’re working on a simple auth + auth solution—aka authentication paired with authorization. Authentication is verifying a known user can log in and stay logged in, and authorization is making sure the user only has access to what you allow (aka only admins should be able to access admin areas, etc.)\n\nFor authentication, we’re making use of Rails’ has_secure_password feature combined with Roda’s secure encrypted cookie-based session plugin. It will intentionally be barebones: email/password and that’s it. We will likely refrain from tackling OAuth, SSO, or any other advanced auth scheme any time in the near future. This is for people who literally just need a simple email/password signup feature. Which in this humble author’s opinion is perfectly fine for most general public websites.\n\nFor authorization, we’re making use of Pundit. Pundit provides a straightforward, object-oriented policy subsystem which is easy to use anywhere in your application. Simply create policies to match with the ActiveRecord models and use those to determine things like “can X type of user view or edit Y type of content?”\n\nAuth can become very complicated, and security pitfalls abound, so our hope is provide this totally-optional plugin which offers an “MVP” level of auth for those with simple needs. It will be marked as beta initially in order to become confident over time the solution is robust and (reasonably) secure.\n\nPaywalls\n\nNaturally, once you have database support and auth, the next obvious problem domain to tackle is the paywall. Picture this: you have content—some you want free, and some you want to be presented only to users who are signed in and paid up. Our paywall plugin will provide a clear mechanism and example code for placing a wall around paid-only content.\n\nOur recommended approach will be to keep all resources (and URLs) public, but then place a portion of a resource behind the paywall. This is how most publications do it. You display the first paragraph, or a quick sample video, or something to that effect if the user isn’t signed in. Then once they sign in, everything shows up automatically.\n\nNote that we won’t be implementing the payment side of things for the time being. Thankfully there’s a well-documented Stripe Ruby API for that! Once you’ve confirmed a new subscription, just update the paid status of the user account and watch that paywall come down.\n\nWe’re enthusiastic supporters of content subscriptions and utilize them ourselves. We can’t wait to see how people use Bridgetown to publish their own subscription-based content.\n\nBridgetown Bash During RailsConf\n\nQuick aside: we’ll be hosting a little evening get-together for friends of Bridgetown during RailsConf week in Portland, Oregon. Space will be limited so be sure to follow @bridgetownrb on Twitter and keep a sharp lookout for an RSVP link!\n\nThe Road Ahead\n\nAs you can see, we have quite a full plate of features to look forward to as the year progresses…and we haven’t even mentioned other potential items on the roadmap from form handling to image asset processing. Bottom line: development on Bridgetown continues apace, and we’re excited to get these features out to the community. If you’d like to aid in that effort through contributing code, documentation, tutorials, and the like, please join our community and help push the ecosystem of Ruby web development forward."
        },
        {
          "id": "release-reaching-1.0-next-generation-progressive-site-generator",
          "title": "Reaching 1.0: Just What is a Next-Generation Progressive Site Generator?",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/reaching-1.0-next-generation-progressive-site-generator/",
          "content": "I’ve been giving a great deal of thought to trying to find the right “hook” for how to frame the news that Bridgetown has (finally!) reached 1.0 status.\n\n\n  \n  Before I go on: a huge thank you to ayushn21, subsevenx, fpsvogel, andrewmcodes, adrianvalenz, Eric-Guo, jacobherrington, deivid-rodriguez, JuanVqz, and nachoal for contributing code &amp; documentation to the 1.0 release, to everyone who submitted bug reports and feedback, and to the many people to donated to our fundraising campaign which brought in $4,263 specifically to aid in this launch effort.\n\n\nThere’s a lot we could say here, from the modern plugin &amp; component system to the wide variety of template engines available, the advanced content modeling, dynamic routes, esbuild integration, bundled configurations &amp; automations—but that’s all conveyed on the homepage and documentation elsewhere.\n\nSo instead, I would like to focus on our new tagline itself.\n\nBridgetown is a next-generation, progressive site generator &amp; fullstack framework, powered by Ruby.\n\nWhat does that string of buzzwords mean exactly? In this article I’d like to dive into the initial portion of that statement: next-generation, progressive site generator. Stay tuned for a future analysis of all the rest!\n\nNext-Generation\n\nBridgetown represents an evolution of thinking from previous generations of tools in this space, especially within the Ruby ecosystem. At heart, Bridgetown is essentially a modern rewrite of Jekyll—it started out as a fork in April 2020 but has since been thoroughly reworked and reimagined while adding a ton of new and unique features.\n\nBut besides just making building ambitious web projects far easier than with Jekyll while simultaneously allowing Ruby developers to leverage far more of their skillset, Bridgetown sets its sights farther afield. Ultimately we’re not here to compete with Jekyll, or Middleman, or Nanoc. We’re here to compete with Gatsby.\n\nLest you think that’s a wild piece of hyperbole, we’ve already received reports of people who have left the bewildering complexity and sluggishness of Gatsby behind (and similarly-over-engineered tools in other ecosystems) to focus on building Bridgetown sites instead. Right now it’s a trickle, not a flood. But we’ve only just arrived at 1.0 and still revving up the engines.\n\nWe’re here to extend a welcoming hand to struggling JavaScript &amp; web developers of all stripes. Come on in, the water’s fine! Ruby is a phenomenal language for web development tools &amp; frameworks, it’s very much alive and not going anywhere, and every day we’re dedicated to making this ecosystem even better.\n\nThe pace of progress on the web never stands still, and neither will Bridgetown. We have big plans for 1.1, 2.0, and beyond. The time to hop aboard this train is now.\n\nProgressive Site Generator\n\nFor quite some time, we’ve been familiar as web developers with the concept known as “progressive enhancement”. The idea is that as much as you can start with simple/standard HTML, and slowly build up your CSS and particularly JavaScript without completely breaking the experience for older browsers, slower networks, JavaScript disabled, etc., the better. In other words, you start with a principal baseline, and then “progressively enhance” the site content and functionality via whichever latest hotness in browser tech you wish.\n\nI would like to introduce a new term into our developer lexicon, inspired very much by progressive enhancement but focused instead on the delivery mechanism of that HTML. I call it “Progressive Generation”.\n\nIn a Progressive Generation workflow, you aim to assemble the most basic form of HTML at the web server level: static files. Indeed, this very blog post you’re reading on the Bridgetown blog is just that: a static HTML file living on a web server (a CDN more specifically). This is exactly the type of output you get with a static site generator (SSG) which is the default mode of Bridgetown (and the only mode of Jekyll and other first-generation SSGs).\n\nNext, you get to selectively decide how to progress from there when you need to add more dynamic features such as form submissions, user logins and persistence, e-commerce, paywalls, database access, etc. There are several routes available to you:\n\n\n  SSR: known as Server-Side Rendering, this is where your HTML is not pre-built as static files, but where you render HTML in real-time before delivering it to the browser.\n  CSR: known as Client-Side Rendering, this is where JavaScript libraries manipulate the DOM of the webpage based on user interactivity and API calls to backend or third-party services.\n  Partial Hydration: this is essentially a hybrid of the two approaches. You start first with some combination of SSG &amp; SSR, then “hydrate” certain components on the frontend to handle interactivity only if and when it’s needed.\n\n\nIn an ideal world, the tools &amp; templates you use to construct the baseplate of your website through SSG and the ones you use for additional SSR are one and the same. However, until fairly recently, this was rare and non-obvious across our industry. While it’s true that JavaScript-based frameworks have been leading the way here, along that way they’ve forced us to adopt highly-opinionated, controversial, frontend-focused tools (React for example) as a necessity for such an approach…with much additional build complexity in tow. In addition, an overly-wrought focus on CSR rather than SSR via major frontend frameworks (resulting in SPA—Single-Page Application—architecture) has introduced numerous concerns in both UX &amp; DX.\n\nThis fracturing of “best practices” in web development due to the rush to adopt (and over/mis-use) these types of frameworks has led to seminal articles on the topic such as The Great Divide and Second-guessing the modern web.\n\nEmbracing the Future Web via Progressive Generation\n\nI think the ultimate answer to these conundrums lies somewhere between the two possible extremes, and in particular I posit two major points of consideration:\n\n\n  Progressive Generation advocates that HTML should be generated in multiple ways at various times depending on the use case. In the simplest case, it’s literally a “Zero Javascript” configuration. We must prioritize tools which don’t force you to ship any JavaScript to the client unless absolutely necessary.\n  Progressive Generation advocates that the bulk of your website’s HTML can be generated by any type of tool, any type of framework, any type of programming language, and any type of hosting setup. In other words, requiring “everyone” to adopt a particular flavor of JavaScript/TypeScript running on a particular hosting provider offering a particular service flavor (serverless functions for example) goes against the grain of progressive generation.\n\n\nIf it sounds like I’m advocating for a sort of “lowest-common denominator” of where HTML originates for a website, why yes that’s exactly what I’m advocating for. This is in keeping with the spirit of the origins of the web. HTML &amp; HTTP were expressly designed to allow any operating system, any computer system, any point on a global network to be able to participate in the HyperText revolution—regardless of the tools and processes used in the generation and serving up of that HyperText.\n\nWe MUST evolve dynamic functionality, interactivity, and DX on the web starting from that initial mindset, and Progressive Generation embodies that ethos. Progressive Generation + Progressive Enhancement for a Progressive Web which honors the past while embracing the future.\n\nConclusion\n\nIt’s quite possible that Bridgetown is currently one of very few tools available today which fully embraces Progressive Generation. I certainly expect to see more on the horizon as time passes, but I feel very confident Bridgetown has an opportunity here to make some waves. Already we’re among the first to support SSR &amp; partial hydration of Lit-based web components. And we’ve been hard at work experimenting with pushing the limits of both Hotwire Turbo and CableCar for wiring SSG, SSR, and CSR together. Expect to see much more in this space in the coming months.\n\nI’ll go into further details regarding the “fullstack framework” embedded within Bridgetown in a future article. You don’t need to use it if all you need is an SSG. We’ll always prioritize ensuring Bridgetown is super-simple to get started with for a straightforward blog, marketing page, community resource, portfolio, etc. But when you need more, it’s there for you.\n\nIn closing: Bridgetown 1.0 is here! The DREAMstack has arrived. Now what are you gonna build with it? 😎\n\n\n  \n  Need help? Want to learn more? Please visit our community page for ways you can get started building with Bridgetown, submitting ideas and feedback, and gleaning inspiration from other developers &amp; creators in our growing community."
        },
        {
          "id": "news-last-call-for-fundraising",
          "title": "Last Call to Join the 1.0 Fundraiser",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/last-call-for-fundraising/",
          "content": "A couple of brief announcements:\n\n\n  The official, production release of Bridgetown 1.0 will occur some time next week (i.e., before March). So if there are any last minute bugs or major DX enhancements you’d like to see slip in before then, please hop on over to our GitHub repo and let us know!\n  We’ll be officially ending our 1.0 fundraising effort at the end of this month. As of the time of this writing, we have reached $3,763, or nearly 80% of our goal of raising $5,000. Will we at least make it to $4,000 before the final tally? I sure hope so! 😄\n\n\nI soon plan to write up a “debrief” of my thoughts on the fundraiser (which overall I’m very pleased with) and what we might do differently in the future. In the meantime, please consider contributing to help us shore up our documentation, fix last-minute bugs, and get a polished 1.0 release out the door.\n\nI continue to be gratified hearing all the positive feedback from Rubyists young and ol…erm, vintage 😏 as they try out Bridgetown for the first time. Tweets like these are music to my ears. We’ll continue to strive earnestly to make Bridgetown the best website construction kit available! ✌️"
        },
        {
          "id": "release-beta-1-is-feature-complete",
          "title": "What’s New in “Feature Complete” Bridgetown 1.0 Beta 1",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/beta-1-is-feature-complete/",
          "content": "Big ch-ch-ch-ch-changes around here in 2022! For starters, we have a brand new website! Huge shoutout to Adrian Valenzuela for his work on the refreshed Bridgetown logo and branding guide, and Whitefusion (which, full disclosure, is my little Portland-based web studio) for the overall design &amp; development of the website. We’ll have more to share about the process of creating the new look and a peek under the hood in the days ahead.\n\nBut on to the main event: Bridgetown 1.0 “Pearl” Beta 1 has arrived. Fresh installation instructions are here. To upgrade from a previous version of 1.0, simply:\n\nbundle update bridgetown\n\n\nYou’ll also need to run bin/bridgetown webpack update to get the latest default Webpack configuration installed. Or…if you’re feeling ambitious, you might want to migrate off Webpack and start using esbuild instead! (More on that in a moment…)\n\nTo upgrade from Bridgetown v0.2x, we now have an offical upgrade guide which we’ll be amending as feedback comes in.\n\nA quick word on the state of things: we now consider v1.0 to be “feature complete”. In other words, the beta cycle we’re in now is solely to fix showstopper bugs or add very minor enhancements which directly improve daily quality of life when using Bridgetown. We’re on a fast track to a final 1.0 production release, though thanks to a lengthy alpha release cycle we feel pretty confident Beta 1 is already production quality for the most part. YMMV.\n\nThe reason reaching 1.0 is such a Big Deal is because we’re taking SemVer seriously. This means we’re done tinkering with significant aspects of the underlying codebase, Ruby API, and user-facing feature set. With the final release of 1.0, we want the community to feel confident it’s time to get cracking building plugins, writing tutorials, recording screencasts, designing themes, and generally contributing to the larger body of resources which help Bridgetowners build websites quickly and painlessly.\n\nWith that out of the way, on to what’s new in Beta 1. (For more on what’s new in v1.0 to date, check out this blog post.)\n\nERB from Day 1 (and a new blank site template!)\n\nBeta 1 adds the ability to pick a template engine other than Liquid right when you start a new Bridgetown site. Simply use the -t/--templates option and pick erb or serbea when running bridgetown new. For example:\n\nbridgetown new site_using_erb -t erb\n\n\nRegardless of which template engine you choose, we also have a nice new “blank site” template which looks good enough that you could actually just slap some Markdown content on there and call it a day for a super-dee-dooper simple website. Obviously it’s still easy enough to blow everything away and start fresh with your own design.\n\nMore documentation on template engines here.\n\nFrontend Bundling via esbuild by Default\n\nFor many people the marquee feature in Beta 1 is the arrival of our official esbuild integration. Now truth be told I personally haven’t had much of an adversarial relationship with Webpack. It’s gotten the job done (and reasonably quickly) in the applications I’ve worked with.\n\nBut not everyone’s experience with Webpack has been as rosy as mine, plus it’s undeniable esbuild has gained huge momentum lately as a super-speedy and pliable tool for bundling frontends (aka your CSS/JS/icon/font/etc. assets). So it is with great pleasure that I announce that not only did we build an esbuild integration every bit as solid as our Webpack integration, but we decided to switch defaults: from now on, all new Bridgetown sites will come with esbuild out-of-the-box. If you still want/need to choose Webpack, just pass the -e webpack flag to bridgetown new.\n\nIt wasn’t an easy decision to add esbuild support. You see, we don’t just plop any ol’ frontend bundler in your project and let you deal with it. We hand-craft bespoke default frontend configurations which are intended to support a wide array of Bridgetown frontend needs with virtually no tweaking required by individual developers, and we provide an infrastructure to upgrade that default config over time as we make improvements. So by adding esbuild support without dropping Webpack, we’ve taken on the responsibility of maintaining not one but two significant frontend configurations. We’ve done this because we believe strongly in the backpack analogy. To quote: “if you have to go fishing for a bunch of extra plugins and add a slew of extra libraries and reconfigure settings just to complete basic setup tasks, we’re doing it wrong.”\n\nFor a little more background on why esbuild and why not other concepts like supporting import maps, read this blog post.\n\nFYI: if you’re already using PostCSS with Webpack, there’s an experimental migration command to let you switch from Webpack to esbuild right away! Just run:\n\nbin/bridgetown esbuild migrate-from-webpack\n\n\nFor Sass users, we don’t yet officially support it at all with esbuild. We’re optimistic support will land before the final release of 1.0, but we’re not promising anything at this time.\n\nMore documentation on frontend bundling here.\n\nImproved DX When Errors Arise\n\nWhile not as flashy as the other two features, I greatly appreciate this improvement.\n\nLet’s face it: error messages suck. The only question is how much they suck. The most we can hope for when attempting to improve in this area is getting them to suck less. In Beta 1, we worked on a couple of aspects to move the needle in the right direction:\n\n(a) Eliminate unnecessary backtraces. Most of the time when an exception is raised, the offending line is only a level or two deep in your own code stack. The rest of the backtrace information is a waste of your time and screen real estate (aka you really don’t need to know about which part of Rake, or Thor, or the Bridgetown build command, etc. happened to lead to your error). So we now only include the first 5 lines or so, formatted a little better to boot. And if you really need all that extra trace info for whatever reason, just use the -t flag.\n\n(b) Fix problems with getting a clean trace. We noticed several cases where the error being reported wasn’t really the right error (aka an exception was getting swallowed in once place but then triggering a problem in another place), or the message itself was obtuse (aka missing relevant class names or other helpful context). This was most notably true with errors in Ruby components or in view templates. We’ve made some big improvements in those areas in Beta 1 and will prioritize further fixes in this area.\n\nIf you come across any super-confusing or misleading error messages while using Beta 1, please let us know!\n\nWrapping Up the Beta Cycle\n\nOur goal is to move swiftly through the beta release cycle and get to a final, production-ready 1.0 release within the next month or so. This means your feedback is critical during this time to find and fix showstopper bugs as well as improve documentation (and believe me, there’s still room for improvement!). Please visit our Community page to find out how to submit feedback, request help, and report issues.\n\nThanks to all who have contributed, whether financially, or with ideas, or with code, to Bridgetown in the lead-up to v1.0. We appreicate all that you’ve done thus far and can’t wait to see what you build next!"
        },
        {
          "id": "feature-progress-report-esbuild-aware",
          "title": "Progress Report: Bridgetown is Now esbuild-Aware",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "feature",
          "tags": "",
          "url": "/feature/progress-report-esbuild-aware/",
          "content": "As the winter holiday season kicks into high gear (for us Northern Hemisphere folk), it’s time for a progress report on Bridgetown’s path to 1.0 vis-à-vis the fundraising effort.\n\nAs of the writing of this post, we’ve raised $2,843 out of a $5,000 goal. That means we’re still hoping to raise at least $2,157. We don’t have an “end date” set yet, but ideally we’re getting real close by the end of January.\n\nNow let’s look at developer time spend so far:\n\n\n  Work on improvements across the Bridgetown ecosystem and release alpha6: 1.75\n  Zeitwerk autoload paths: 3.0\n  Continue work on Zeitwerk PR: 2.0\n  Finish up Zeitwerk PR: 1.5\n  Address feedback on Zeitwerk configuration: 0.75\n  Fix weird Ruby 2.7/3.0 compatibility issues with alpha7 and release alpha8: 4.25\n  Serbea documentation: 0.75\n  Improving documentation and preparing for alpha9 release: 2.0\n  Fix descendants and Zeitwerk reload bug, release alpha10: 1.5\n  Add roundtrip saving of repo models: 1.5\n  Refactor hooks (particularly for SSR) and the watcher, update documentation: 2.5\n  Fix production ENV issue with Roda file-based routes: 0.5\n  Deep work for esbuild integration: 5.0 👀\n  Begin official esbuild branch for setup/config: 2.25\n  Get alpha11 release ready: 2.0\n  Clean up listener/watcher: 0.5\n  TOTAL: 31.75\n\n\nDeveloper time has slightly outpaced fundraising at this point, but that’s not unexpected. We also elected to pass $500 on to a very talented designer who is working on a new offical logo and style guide for Bridgetown. More to be announced soon!\n\nThere’s also been a flurry of effort not included in this direct report to further ecosystem enhancements (plugins, methodologies, etc.) which tie into the overall 1.0 launch, such as our Prismic CMS plugin, CableCar/mrujs integration, and seamless support for ActiveRecord (yes, that ActiveRecord!) &amp; databases.\n\nSo, about that “esbuild” line item…\n\nFirst, a little history lesson…\n\nWe put a stake in the ground at the very beginning of the Bridgetown project that we’d embrace frontend developers via first-party Webpack integration. This move wasn’t without controversy at the time, and still garners strong feelings.\n\nSome people just want their static site generators to only deal with HTML output and various related files. Whatever frontend-specific needs people have should be a separate, non-core concern.\n\nI completely reject that philosophy. Virtually nobody is designing a professional website in the year 2021 (2022!) which doesn’t have sophisticated CSS needs, and often JavaScript as well. Proper support for the NPM ecosystem isn’t a “nice-to-have”. It’s table stakes.\n\nOther people think the right solution is to offer various “starter kits” so you can pick your frontend “flavor” of choice. So the core framework just handles A-&gt;B static transformations, and frontend bundling is simply a recommended (and hopefully supported) add-on.\n\nI also reject this philosophy. Bridgetown has always been intended to be an opinionated, soup-to-nuts framework. Just like Rails. Just like Next.js. I’m on record as not liking starter kits (aka download this random repo off of somebody’s GitHub and just use that…). I think frameworks should be able to support whatever obvious project configurations you need out-of-the-box whenever you run the new command. Automations and plugins should enhance your projects in repeatable, testable ways. You will never see me author, nor recommend starter kits. Bridgetown will always encourage you to run new, apply or bundle add. That’s it.\n\nGiven all that, we designed our Webpack integration to be set-and-forget. Our out-of-the-box webpack.config.js file has virtually nothing in it. It simply inherits a strong set of defaults—defaults which are maintained and sometimes upgraded by the Bridgetown core team but are checked into your repo for close examination should you wish to.\n\nIt’s a pretty sweet setup. And, personally, I rather like Webpack. It hasn’t been a horror show for me by any means.\n\nBut not everyone feels this way. Webpack has caused much consternation in various fullstack communities over the years. It can get slow and fiddly. It brings with it a large dependency graph. Heavy-duty transpilers like Babel are needed less and less as everyone adopts modern evergreen browsers. Rails and Phoenix are shifting greenfield thinking away from it. Even some big-name frontend frameworks are regrouping around other tooling like Vite or Snowpack. In all cases, a particular lower-level bundler keeps inserting itself into the conversation, and that’s esbuild.\n\nesbuild is an extremely fast JavaScript bundler, written in Go but with support for JavaScript build plugins. Because esbuild sits at a relatively low level in any frontend toolchain, it has become the basis for higher-level abstractions. esbuild doesn’t itself claim to solve all your frontend bundling needs, but instead provides a performant, tightly-focused baseplate upon which to build your tooling.\n\nHerein lies a particular dilema. In order for Bridgetown to adopt esbuild for its ultimate speed, minimal footprint, and flexibility—all while keeping the DX (Developer Experience) on par with our Webpack integration—we couldn’t just add a basic command to kick off esbuild and call it a day. We needed an opinionated set of defaults…a true out-of-the-box configuration…so you could still do bridgetown new and get a solid experience right away, but also be afforded the option to jump headfirst into customizations in an officially supported way.\n\nSo that’s exactly what we did.\n\nIn this WIP PR, esbuild is now as tightly integrated into Bridgetown as Webpack. This includes a sidecar watch process whenever you run start, deployment scripts (to minify output), an upgradable default configuration, support for common use cases like PostCSS, Turbo, Lit, Shoelace, CableReady, soon Ruby2JS, etc. The works.\n\nIn fact, we’re so confident in our ability to iterate rapidly on this integration and make it sizzle, we’ve decided to make esbuild the default frontend bundler.\n\nWebpack will continue to be available and supported. But with the release of Bridgetown 1.0, we’re all in on esbuild—and PostCSS as well. We believe this is the right path forward for frontend bundling—not just for Bridgetown but for web frameworks across our industry.\n\nFAQ: Vite? Import maps? Migration strategy?\n\nThree answers to likely questions before I close:\n\nQ: Why not just adopt Vite or another high-level frontend toolchain? A: We care deeply about two things on the Bridgetown project: taking on only as much frontend complexity as you truly need, and controlling the core stack and DX as much as possible. As mentioned above, frontend bundling is core to to the Bridgetown story. We want to move further into owning that part of the stack, not hand off more to non-Ruby-oriented, third-party tooling. Vite, etc. are great projects. But we’re building our own. And esbuild is the way to get there.\n\nQ: Why not just use import maps? Rails 7 is doing it! A: Pre-dating Webpacker, Rails has featured a built-in Ruby-based frontend bundler called Sprockets. It never left! All import maps do is provide a way to import third-party JavaScript libraries via CDN URLs, and integrate that into a Sprockets-based pipeline. If that floats your boat, fine by me, but I envision a world of hurt coming along with that approach as soon as someone deviates even slightly from the Rails/DHH happy path. Otherwise, Rails 7’s jsbundling-rails gem pushes things along a bit with actual support for esbuild (as well as a simple interface to Webpack or Rollup if you prefer). However, three problems (!): it’s still intended to be Step 1 before you hand everything over to Sprockets. And the esbuild integration is…virtually nothing. It’s nothing more than command line invocation. Need to customize esbuild in any way? Add plugins? You’re on your own, and apparently that’s not a bug, it’s a feature. esbuild also lives in a different world than PostCSS, which can be used courtesy of a separate gem (cssbundling-rails). Not a fan of that approach to be quite honest, and it’s not in keeping with the Rails Doctrine of Convention Over Configuration. Bridgetown will support PostCSS and esbuild working in harmony out-of-the-box…a surprising amount of effort to achieve!\n\nQ: Well this all sounds cool. How do I upgrade my existing site? A: Well, glad you asked! We’ll be providing a migrate-from-webpack command to help transition your site from Webpack to esbuild, assuming it was created with a recent version of Bridgetown, and it’s using PostCSS. We haven’t added a native Sass option to esbuild yet, and that’s unlikely to change prior to our first release. Our overall philosophy around Sass is that it’s a legacy technology. PostCSS represents the future of stylesheet authoring and that’s where our default config and primary focus will lie going forward.\n\nStay tuned for examples, documentation, and all that good stuff as we get closer to the PR merge and Bridgetown 1.0 Beta 1. In the meantime, if you haven’t tried out anything in the latest alpha, we encourage you to do so! Feedback and bug reports are most welcome in this crucial time.\n\nAlso, if you haven’t already, please contribute to our fundraising efforts! Your direct support is what enables this technology to flourish and further the goals of Ruby web developers everywhere. Cheers!"
        },
        {
          "id": "news-twitter-spaces-ama-recording-and-transcript",
          "title": "Recording and Transcript of November 2021 AMA on Twitter Spaces",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/twitter-spaces-ama-recording-and-transcript/",
          "content": "I recently hosted a live news update and AMA on Twitter Spaces, and it was a great success! A bunch of people joined in and I got to answer a few questions.\n\nTopics included a progress report on the Bridgetown fundraising campaign, the upcoming Prismic CMS plugin, the state of web frameworks and how Bridgetown relates to developments in the Jamstack as well as interop with Rails, and the importance of a “polyglot web.”\n\nEnjoy! (And stay tuned for another Twitter Spaces discussion in December…)\n\n\n  \n\n\n\nTranscript\n\nAutomatic transcription by Happy Scribe, with light edits\n\nSo I just want to do a little bit of an overview of how things are going with the Bridgetown fundraising efforts and kind of talk about what’s going on with the development towards Bridgetown 1.0, and just sort of more of a general kind of overview of what my thoughts are on the state of web frameworks today and the sort of necessary efforts we need to make to champion a polyglot web, as opposed to sort of a monoculture around JavaScript or TypeScript, which is a danger that I see looming.\n\nSo I wanted to talk a little bit about that. But first Bridgetown. So in case you’re not even aware of what Bridgetown is, Bridgetown is a static site generator growing into more of a full stack web framework as well if you need that. It’s based on Ruby, but you don’t have to be an expert Ruby programmer to use it by any means.\n\nBut if you do want to start customizing your website or adding some plugins and things, you will need to know a bit about Ruby. But it’s really an effort to try to bring a lot of modern thinking around how to construct and deploy a website to the Ruby community. Kind of coming at things from a very different direction. Starting out than you might find with Rails, Rails has been phenomenal as a web framework for Rubyists.\n\nObviously it’s played a major role in propelling Ruby into the mainstream, but there’s lots of things you can’t really do easily with Rails or Rails isn’t necessarily the best suited for to start off a project. I’ll get a little bit more into that later. So Bridgetown is sort of an effort to bring some of the ideas from static site generation from the Jamstack and let that flourish in an environment where Ruby programmers can feel right at home and hopefully eventually even grow the community maybe attract people who don’t really know anything about Ruby to learning more about Ruby as they’re building their Bridgetown sites.\n\nAll right. So with that out of the way, progress on fundraising.\n\nSo just a few weeks ago, two and a half, getting close to three weeks ago, we launched a fundraising effort to try to help get Bridgetown 1.0 polished up and out the door. The goal is to reach $5000. We’re just about to $2000 (actually now it’s around $2500 –Ed.), and I feel like that’s a pretty respectable place to be. After only two and a half weeks or so, there’s some good progress there. We’re definitely past the point of being embarrassed.\n\nThat’s always the greatest fear of these things is you launch something you say, here’s our goal, and then weeks later, it’s like a tiny bit of the way along. You’re like, oh, man. Well, that’s embarrassing, but we’re definitely past that point, which is awesome. That being said, the momentum is slowing down, start out more like a flood and then is now a trickle. So I’m asking everyone for your help, to spread the word, to tell all your friends and fellow Web developers and Rubyists that Bridgetown is a project worthy of their attention and perhaps support.\n\nAgain, I’ll get into this a little bit more later. But when you look at the state of Web frameworks, a lot of frameworks have large corporate backing or are literally being funded by venture capital. And there are these very official efforts.\n\nBridgetown is completely a community open source effort. The work I do by and large is just work I do because I want to do it and I enjoy it. And I want to give back to the Ruby community and the web developer community. And so any help through fundraising or through GitHub sponsors just essentially goes to offset the time I’m putting in relative to client work that I do as a freelancer. I work for clients and they pay me. So if I’m not working for clients, I don’t get paid.\n\nIt’s as simple as that. So if I’m doing a ton of work on open source software and I’m not working for clients, I’m not getting paid. So those are just the economic realities of how open source works. So I just want to say thank you to everyone who has contributed to fundraising so far. For more information on all that, you can go to our website, which is bridgetownrb.com.\n\nAll right, with that pledge drive out of the way, I’ll move on to a couple of the other topics we’ll go over today and then I’ll answer a few questions.\n\nSo the next topic is there is a Prismic plugin on the way. Prismic is a headless CMS. What that means is you can go to Prismic.\n\nYou can set up content types. You can specify all the fields you want for the content types, add images, add text, add rich text, link to documents, and MP3s, whatever you want to do. You can set up your entire content structure within Prismic, and then they provide an API that you can use to get that content out of the system and then do whatever you want with it. So typically, people use Prismic along with some kind of static site generator or web framework. So with the Prismic plugin, we’ll be able to enable you to create a new Bridgetown site project, connect it to your Prismic repo, and then create all your content in Prismic, and it’ll all get sucked down into your Bridgetown site, and with just a little bit of configuration, you’ll have all that content available within Bridgetown.\n\nThis answers a big question that I’ve been asked many times, which is Bridgetown looks nice and all… But what do I use for a CMS? Because out of the box, Bridgetown is just using files on your hard drive or on a server somewhere in your GitHub repository. It’s all file based. You’re creating markdown files, you’re creating HTML files, you’re creating templates.\n\nAnd that’s great from a developer standpoint. That’s awesome. That’s what’s so cool about all these new static site generators we have today is we have all these nice file based ways of setting up content and templates, but that’s not great for content editors and anyone nontechnical, anyone on the business side of an organization where they’re just like, I just want to log into a thing, put in some text, click, publish, and I’m done. I don’t want to know anything about what is Markown, what are these files, what are GitHub repos? Don’t confuse me with all that crazy stuff.\n\nSo this is going to be a cool solution to be able to allow Prismic CMS content authors to work on the content. And then your Bridgetown site can just pull that all in and deploy it whenever you need to publish new content. So that should be out in the next couple of weeks.\n\nAnd then, of course, Bridgetown 1.0, the big release. That work continues to be ongoing. The fundraising effort is definitely helping to accelerate that. And I’m hoping to get an official 1.0 release out the door by the end of the year, perhaps right around Christmas, to coincide with a new release of Ruby. That would be really fun.\n\nSo exciting times for Bridgetown. All right. And then finally, kind of the last topic I want to cover before questions is my thoughts on the state of frameworks today: web frameworks, static site generators. What’s going on out there? Because the landscape has just gotten nuts.\n\nJust in the last day or two, we found out that Vercel has hired Rich Harris, who is the creator of Svelte and SvelteKit. That’s a popular frontend framework for developing rich, reactive components and interfaces. So that is on the frontend (you can’t see me but I have air quotes going on here)—that’s on the frontend. But increasingly, what we’re seeing is that what happens on the frontend is what drives what people are building websites out of. A lot of things are starting essentially frontend first, and then at some point down the road, you start to bolt on some kind of backend solution. And increasingly, those solutions are serverless functions or some kind of Backend-as-a-Service or GraphQL API. There’s so much stuff people are looking at now for what we think of as traditional backend development, but it doesn’t really look like traditional backend development.\n\nSo this is why I say, I think some people who are Rails enthusiasts don’t quite understand what the competition to Rails is anymore. In the past, if you ask somebody, hey, what do you think are the top competitors to Rails? Somebody might say, oh, probably Django for the Python folks, or Laravel for the PHP folks, or maybe NodeJS Express or, I don’t know, NestJS or one of the other backend frameworks for Node.\n\nAnd increasingly, I would argue that Rails top competitors are Next.js, Gatsby, Nuxt SvelteKit—these other sort of frontend-first frameworks that you basically start out doing a lot of the UI and interface work up front, and then you start to trickle in backend-looking stuff later. You don’t just start out with a traditional fullstack framework. So that’s why I’m super enthusiastic about the work going on right now for Bridgetown, because Bridgetown is kind of an attempt to create what looks more like a frontend-focused framework—but in the context of Ruby, in the context of creating static sites that you can easily deploy. And then once you have the beginnings of a site—maybe you have a public site with a portfolio or a blog or a list of products or something like that, and now you want to start adding some backend-looking things. You know, the direction we’re going is that Bridgetown out of the box will say, like, oh, here’s where you can plug in some backend code and you can deploy that sort of at the same time you’re deploying a static site, and it’s all just wired up and working together. Now you might ask, well, what is that backend technology that’s in Bridgetown? So that is Roda. Roda is a very lightweight, Ruby-based backend API framework that you can use.\n\nYou can certainly go use Roda standalone by itself, it’s very simple to get started with, but what we’re doing in Bridgetown is marrying Bridgetown’s static site features with Roda’s dynamic API features and kind of bringing it all together. So Bridgetown essentially ends up as the view layer for Roda. It’s very cool. And we’ll be posting a lot more examples and tutorials and things like this as we near the release of 1.0 and then the final piece of this as well.\n\nWhat if you need something even more advanced than that? What if you need to really build a big, large, sophisticated application that powers your public static site with user logins and dashboards and reporting, and all these different things that might need to be attached to your site. And the answer to that is, well, you can actually build that in Rails too. You can build a Rails side of your project and have Bridgetown and Rails work together. And we’ll be posting a lot more about that in the coming months as well.\n\nSo getting back to the state of web frameworks, I’m increasingly feeling like there’s a significant class of websites where reaching just for Ruby on Rails doesn’t really make sense. And for a lot of people, they’re just heading straight into the territory of Next.js or Gatsby or Nuxt or SvelteKit or one of these kind of JavaScript-based tools. And that makes me sad, because I don’t want to see Ruby-based solutions fall by the wayside and not be relevant to a whole class of developer. So that’s what motivates me a lot right now as I work on Bridgetown.\n\nAnd then finally, just in general, I think it’s important for us to shout from the rooftops, as it were, the importance of a polyglot web.\n\nAnd what I mean by that is I don’t want to see any one language or any one ecosystem win. I don’t see the web as a zero-sum game by any means. I don’t want to see JavaScript or TypeScript win. I certainly wouldn’t say Ruby should win or Rust should win or Go should win or any other language. Right? The web started out essentially as a platform that could be used on any platform, and you could use any language to generate HTML and serve up HTML.\n\nAnd I think we really need to help people realize how important that is and not just kind of let JavaScript as the de-facto language win because that’s expedient or easy to do. That being said, I’m certainly not anti-JavaScript by any means. There’s actually a lot of cool stuff happening right now on the frontend with web components, with tooling like Lit which I’m super excited about, a lot of the neat stuff that’s in Hotwire technology is, of course, frontend-related like Stimulus &amp; Turbo.\n\nThe conversation isn’t: do you love JavaScript or do you hate JavaScript? It’s what kind of web do you want to build? Do you want to build a polyglot web where there are lots of languages you can use to generate your HTML? And then maybe you write some JavaScript for frontend components, or maybe you use a language that can transpile to JavaScript so you don’t even write JavaScript per se on the frontend either. I just think all the options should be on the table and we should encourage that.\n\nAll right. I think that is it for my spiel. Now comes the time when I figure out how to take your questions.\n\n[Question from Andrew]\n\nYeah, that’s a great question.\n\nI just answered a similar question the other day, and what I said was we really need more people testing. Testing. Testing is super helpful to me. So, yeah, I encourage anyone to install the latest Bridgetown 1.0 alpha, try to create a new site, play around with things a little bit, and if you run into any problems, if you have any sort of questions or something in the docs, we’re working on better documentation for 1.0. Essentially any feedback is super helpful right now.\n\nThere’s nothing like hammering away on the actual code and trying to use it to reveal any bugs or problems that we need to fix. So that’s probably the biggest thing.\n\nAll right. Jacob is up next.\n\n[Question from Jacob]\n\nThanks, Jacob, for that question. So, Bridgetown, and how it compares to Jekyll, this is one of those areas where I’ve had to tread carefully and haven’t always got it right, because on the one hand, I certainly want to discuss in length all the things I think are exciting about Bridgetown and how it improves upon Jekyll, I would say, but I know there are still a lot of Jekyll fans out there, and I don’t want to ruffle people’s feathers if they’re happy with something they’ve built using Jekyll.\n\nSo what I would say is Bridgetown starts out with what I think is Jekyll’s crowning achievement, which is this idea that you can just create a Markdown file, put some front matter up at the top with your metadata, type a command, and you have a website built. Jekyll really was a pioneer in that space before. There weren’t really many other options like that.\n\nSo hats off to that really nice way to onboard getting a website project started. Where I think Jekyll has languished is just not really providing very good APIs for Rubyists. You can create your own Jekyll plugin, but there’s not a lot of documentation. It’s kind of hard to figure out. The API is really sparse, and the platform that Jekyll has been married to for quite a while, which is GitHub Pages doesn’t support any custom plugins, so that’s kind of been its Achilles’ heal for a while. So one of the things we’re trying to do in Bridgetown is create lots of really nice APIs that are maybe a little bit more influenced by Rails to make it easy to write plugins, to make it easy to use Ruby-based template languages such as ERB.\n\nWe have recently introduced the idea of Ruby-based components so you can write your own components. And that’s really like a Ruby file and a template that goes along with that Ruby file, and you can construct all of the HTML on your site out of sort of a graph of components. And if you have any familiarity with ViewComponent by GitHub for Rails, it’s very much like that. And in fact, we actually have a compatibility layer so you can use ViewComponent in a Bridgetown site, which is kind of bananas, but also kind of awesome.\n\nThat’s possible.\n\nNow I would just say Jekyll versus Bridgetown, you’re going to find with Bridgetown that it leverages any Ruby knowledge. You may have a whole lot more, but you also can do things that are very simple. Just create a Markdown file and stick a couple of Liquid tags in there and you’re done. It definitely supports that as well. So the goal is to start out as simple and as easy as Jekyll, but to kind of scale up with your ambitions a lot farther.\n\n[Question from ?]\n\nInitially Bridgetown was simply a labor of love. I wasn’t even intentionally trying to raise funds to work on it, but then I was encouraged by a few folks, including Andrew Mason, who is here. Hi, Andrew! I was encouraged to start a GitHub sponsors program, and I was almost reluctant at first because I didn’t want this to feel like a commercial enterprise. I really wanted to be in the spirit of open source, but I decided to try it out and was very pleasantly surprised by how well that started to go, and I started to honestly rely on that little bit of side income to help me carve out more time in my schedule to work on Bridgetown.\n\nWhen plans really started to solidify for getting Bridgetown 1.0 out the door, I was increasingly realizing that I’m going to have to spend really a lot of time and a lot of concentrated time to really get this release solidified and polished up and out the door. There are other contributors to Bridgetown. I’m not the only person on our core team, but I’m the primary contributor, so it’s up to me to just put in the time to sit down and literally write code and test ideas. So again, someone else, not myself, encouraged me to think about starting a fundraising campaign, and that seemed like a legit idea because it’s not an open ended thing forever per se.\n\nIt’s just for this effort right now, as the site says, the goal is to raise enough funds to essentially provide me with a block of hours.\n\nAnd in those hours, I can work on getting 1.0 done and out the door. Yeah. And I’ll post something a little bit down the road, too, about just the technical aspect of setting up the site itself, because I’m eating my own dog food here. The fundraising site is a Bridgetown site, and it supports three different kinds of payments. You can actually click over to GitHub sponsors and pay through that, which is just a simple link.\n\nBut then there’s PayPal, which requires a bit of an integration, but only on the frontend. And then there’s Stripe, which is both a frontend and backend integration. So I’m actually using Bridgetown’s Roda integration to handle the private Stripe key kind of stuff you have to do on the backend. So it was kind of interesting just putting that project together technically, and I’ll share all that soon in a blog post.\n\nAll right. Looks like one more question here from Ivy. So take it away.\n\n[Question from Ivy]\n\nYeah, that’s a good question. “Non-goals.” I would say that I’m very much influenced by the Rails philosophy and what I mean by that is convention over configuration. Really trying to focus on a happy path for a developer…don’t necessarily give them too many options at first. It’s always a tricky balance. Right?\n\nLike, on the one hand, you want a system that’s infinitely extensible, and it’ll work on every platform, and it’ll work in any hosting environment—just an endless list of options and possibilities. But there are real downsides to that. And so for me, working on Bridgetown, I’m constantly trying to imagine a singular experience that’s really ideal for a developer. So kind of focusing on using a particular version of Ruby and using a particular OS and deploying to a particular web host. And it’s not that we’re only going to limit ourselves to supporting those, but to really kind of focus on what’s the primary recommended way to build a Bridgetown site and deploy a Bridgetown site and really focus on that.\n\nSo from a standpoint of tutorials and examples and things like that, and if someone really knows what they’re doing and they’re saying, well, I don’t want to deploy my site using Render. I want to build it over here in this Docker container, and then I want to upload that to Amazon S3, and then I want to set up Cloudfront to be a CDN…. If you want to do all that, that’s awesome. But that’s not what we’re going to recommend. That’s not what we’re going to write a tutorial about. Right?\n\nSo a non-goal is kind of like, well, here’s a tool, figure out how to use it, figure out how to deploy your site. Here are a million options for you. I would say no to that. But at the same time, we don’t want to create something that’s too confining. So there’s definitely a balance there.\n\n[Question 2 from Ivy]\n\nYeah. Another great question there. It sounds like what you’re looking for maybe is what are some good use cases? What are some good industries or approaches to building websites that are an inspiration for Bridgetown that you can use Bridgetown for? And I would say over the years, certainly quite a few years ago, for me, it wasn’t just developing Web apps per se. I was developing websites, and they are primarily informative, educational and so forth.\n\nAnd at the time, for a little while there, I was just using WordPress, and obviously WordPress is its own behemoth in our industry. In some respects, any web framework or any technology used to build websites, its primary competitor is probably WordPress. I hate to say it, but it’s true.\n\nSo in that respect, I have some background there thinking through what message are you trying to convey to an audience? How are you going to motivate them? How are you going to get them excited about what you’re selling, what you’re building, what you’re writing, whatever it is you’re trying to promote. And so sites like that maybe look like blogs, maybe look like portfolios, certainly ecommerce, landing pages for new services, publications where there’s maybe a podcast and a newsletter and some different things you’re trying to promote, in addition to just a post on something.\n\nAnd so those are all the kind of sites I’ve built in the past, and it was always a question of what tool do you use for that? Do you use WordPress? Do you use some kind of hosted solution like Squarespace? Do you use a static site generator like Jekyll or 11ty or one of these new fancy ones?\n\nEndless possibilities right? So looking at Bridgetown again, the goal is to provide something where you can start out one of these kind of website projects with just an idea, like I have a product to sell. I have a message to get out. I want to promote a podcast. I want to promote a newsletter.\n\nI don’t know. I want to show up a cool graph of a bunch of data I’ve collected about some interesting topic and let you start out with that idea and build something quickly. But do that using the Ruby language as sort of a base of that because I love Ruby, and I think Ruby is awesome in many ways, specifically for web developers. So I want to see that kind of tool out there on the market. And so that’s what we’re working on.\n\n[Question about Remote Ruby]\n\nWell, I’m part of the Andrew Mason fan club, as I know many of you are out there. So what can I say?\n\nActually, I have to say Bridgetown had barely started as a thing when I managed to get in touch with Chris Oliver, and right away he was just like, hey, come aboard Remote Ruby and tell us what you’re working on. And I was kind of floored by that. So shout out to Remote Ruby for just being so approachable and welcoming to anyone in the community. And it’s really a great resource. Cool.\n\nAll right. Well, if there are no other questions, I think we can wrap it up for today. I’m actually once again floored by the fact that the Spaces feature here actually worked and a bunch of people showed up. So this is awesome. I’ll definitely try to do this again, maybe once a month, at least.\n\nSo people have an opportunity to share ideas or ask questions. And also, of course, we do have a Discord chat. If you go to bridgetownrb.com and scroll down to the footer there or click over to our community page, you can get a link to our Discord chat, and that’s a great way to just ask questions, get help.\n\nSometimes it’s not even about Bridgetown. People have come on asking questions about CSS or how to use some kind of JavaScript library or just any number of things. And usually somebody there is trying to help out and provide some answers and kick ideas around. So that’s been really cool as well. Alright. Without further ado, thank you all for tuning in and I will end the space.\n\nNow have an awesome day, evening, wherever you are. And I’ll see you next time!"
        },
        {
          "id": "release-era-of-bridgetown-v1",
          "title": "The Era of Bridgetown v1 Has Begun. Welcome to the “Pearl”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/era-of-bridgetown-v1/",
          "content": "Named after the famed Pearl District on Portland’s west side, I’m pleased to announce the first public alpha release of Bridgetown 1.0 (technically 1.0.0.alpha5). v1 is chock full of major advancements for the platform. Read the “edge” documentation now available here—or skip down below for upgrade notes from v0.x.\n\nBut before we get to all that, a second announcement!\n\nAs you may know, Bridgetown is entirely funded by “viewers like you,” and we’ve taken on (and in some respects already executed!) an ambitious roadmap heading into 2022. In other words, we need your help to get v1 finalized, polished, and ready for production—as well as further all the work which goes into writing documentation, creating tutorials, attending to design &amp; branding, and generally shepherding the ecosystem.\n\nTherefore, we’ve launched a dedicated fundraising site for Bridgetown. We hope you join the campaign to push v1 over the finish line, and please help us spread the word so other interested Rubyists and web developers may contribute as well.\n\nNow without further ado, onto the latest version of Bridgetown codenamed “Pearl”. I’ve talked about some of the changes therein previously, but let’s recap what’s new in this release!\n\nRuby-Centric Tooling (Rake, Rack, Puma, &amp; Roda)\n\nIn prior versions of Bridgetown, we had leaned on some Node-based tooling to provide features such as CLI scripts (via Yarn/package.json), executing multiple processes simultaneously (Bridgetown’s dev server + Webpack), and live-reloading.\n\nIn 1.0, we’ve replaced nearly all of those Node-based tools. Yarn is still utilized to kick off frontend bundling, but everything else has been brought in-house. By simply running bin/bridgtown start (bin/bridgetown s for short), you get a dev server, build watcher, and frontend bundler (Webpack for now, but alternatively esbuild in the near future) all booted at once along with live-reload functionality. If you’ve ever used Foreman or Overmind before to run multiple processes in parallel, Bridgetown does that now without any additional dependencies.\n\nBridgetown’s new dev server is based on the battle-hardened one-two punch of Rack + Puma (our previous WEBrick-based server is now deprecated). This new server is fully capable of serving (heh, heh) as a production server as well, should the need arise. On top of Puma we layered on Roda, a refreshingly fast &amp; lightweight web framework created by Jeremy Evans. On a basic level, it handles serving of all statically-built site files. But that’s just the beginning as you’ll soon discover.\n\nBy moving to a Rack-based stack, this means every Bridgetown site is potentially a Rails app, or Sinatra, or Roda itself, or…. Because Rack can mount multiple “apps” within a single server process, you can run Bridgetown alongside your favorite backend/fullstack Ruby app framework. Yet with Roda on the scene, we just couldn’t help ourselves…\n\nBridgetown SSR, Roda API, and File-based Dynamic Routes\n\nServer-Side Rendering, known as SSR, has made its peace with SSG (Static Site Generation), and we are increasingly seeing an SSG/SSR “hybrid” architecture emerge in tooling throughout the web dev industry.\n\nBridgetown 1.0 takes advantage of this evolving paradigm by providing a streamlined path for booting a site up in-memory. This means you can write a server-side API to render content whenever it is requested. While this API could be implemented with any Ruby framework, you might very well want to take advantage of our native Roda integration because it’s just that easy to add a dynamic API route.\n\nFor example, I’ve been building a new site which downloads content from the Prismic headless CMS. I need a dynamic route which can render a preview of any content as requested by a content editor working within Prismic. Here’s an example (slightly simplified) of what that looks like:\n\n# ./server/routes/preview.rb\n\nclass Routes::Preview &lt; Bridgetown::Rack::Routes\n  route do |r|\n    r.on \"preview\" do\n      # Our special rendering pathway to preview a page\n      # route: /preview/:custom_type/:id\n      r.is String, String do |custom_type, id|\n        bridgetown_site.config.prismic_preview_token = r.cookies[Prismic::PREVIEW_COOKIE]\n\n        Bridgetown::Model::Base\n          .find(\"prismic://#{custom_type}/#{id}\")\n          .render_as_resource\n          .output\n      end\n    end\n  end\nend\n\n\nThis route handles any /preview/:custom_type/:id URLs which are accessed via Prismic. It pulls in a preview token cookie (previously established by a different route), finds a content item via a Prismic Origin ID (an aspect of the Prismic Bridgetown plugin I’m developing), and renders that item’s resource which is then output as HTML. Needless to say, this was simply an impossible task prior to Bridgetown 1.0.\n\nSSR is great for generating preview content on-the-fly, but you can use it for any number of instances where it’s not feasible to pre-build your content. In addition, you can use SSR to “refresh” stale content…for example, you could pre-build all your product pages statically, but then request a newer version of the page (or better yet, just a component of it) whenever the static page is viewed which would then contain the up-to-date pricing (perhaps coming from a PostgreSQL database or some other external data source). And if you cache that data using Redis in, say, 10-minute increments, you’ve just built yourself an extremely performant e-commerce solution. This is only a single example!\n\nBut wait, there’s more! We now ship a new gem you can opt-into (as part of the Bridgetown monorepo) called bridgetown-routes. Within minutes of installing it, you gain the ability to write file-based dynamic routes with view templates right inside your source folder!\n\nHere’s a route I’ve added, available at /items, which shows a list of item links:\n\n---&lt;%\n# ./src/_routes/items/index.erb\n\n# route: /items\nr.get do\n  render_with data: {\n    layout: :page,\n    title: \"Dynamic Items\",\n    items: [\n      { number: 1, slug: \"123-abc\" },\n      { number: 2, slug: \"456-def\" },\n      { number: 3, slug: \"789-xyz\" },\n    ]\n  }\nend\n%&gt;---\n\n&lt;ul&gt;\n  &lt;% resource.data.items.each do |item| %&gt;\n    &lt;li&gt;&lt;a href=\"/items/&lt;%= item[:slug] %&gt;\"&gt;Item #&lt;%= item[:number] %&gt;&lt;/a&gt;&lt;/li&gt;\n  &lt;% end %&gt;\n&lt;/ul&gt;\n\n\nWait a minute, I hear you saying. I thought Bridgetown was just a static site generator. Are you really telling me you can now build server-side apps with it?\n\nYup.\n\nSince all the data in the above example is created and rendered by the server in real-time, there’s no way to know ahead of time which routes should be accessible via /items/:slug. That’s why bridgetown-routes supports routing placeholders at the filesystem level! Let’s go ahead and define our item-specific route:\n\n---&lt;%\n# ./src/_routes/items/[slug].erb\n\n# route: /items/:slug\nr.get do\n  item_id, *item_sku = r.params[:slug].split(\"-\")\n  item_sku = item_sku.join(\"-\")\n\n  render_with data: {\n    layout: :page,\n    title: \"Item Page\",\n    item_id: item_id,\n    item_sku: item_sku\n  }\nend\n%&gt;---\n\n&lt;p&gt;&lt;strong&gt;Item ID:&lt;/strong&gt; &lt;%= resource.data.item_id %&gt;&lt;/p&gt;\n\n&lt;p&gt;&lt;strong&gt;Item SKU:&lt;/strong&gt; &lt;%= resource.data.item_sku %&gt;&lt;/p&gt;\n\n\n\nThis is a contrived example of course, but you can easily imagine loading a specific item from a data source based on the incoming parameter(s) and providing that item data to the view, all within a single file.\n\nYou can even use placeholders in folder names! A route saved to src/_routes/books/[id]/chapter/[chapter_id].erb would match to something like /books/234259/chapter/5 and let you access r.params[:id] and r.params[:chapter_id]. Pretty nifty.\n\nTesting is straightforward as well. Simply place .test.rb files alongside your routes, and you’ll be able to use Capybara to write fast integration tests including interactions requiring Javascript (assuming Cuprite is also installed).\n\nRest assured, a full setup guide &amp; tutorial for all this stuff is on its way. (Want to get it sooner? 😉)\n\nThe DREAMstack Has Arrived\n\nThe bottom line it this: Bridgetown has evolved into more than “just” a static site generator and can now be considered a fullstack web framework, thanks to the incredible building blocks of Rack, Puma, Roda, and what’s come before in Bridgetown. You’re certainly under no obligation to use any of this new dynamic routing functionality, but it’s there if and when you need it. This is the final realization of what I have lovingly referred to as the “DREAMstatck” — Delightful Ruby Expressing APIs And Markup.\n\nIn terms of a deployment strategy, we highly recommend Render. With a simple render.yaml file, you can deploy both a static site and a dynamic server from a single codebase (monorepo) which are then both refreshed in tandem any time you commit and push to GitHub. We’ll post an entire article all about this deployment strategy in the coming days. And if you don’t expect a whole lot of traffic, you can even jettison the static site entirely and only deploy the server, letting Puma handle all incoming traffic. It’s not as crazy as you might think because any static-only content is always pre-built and served as static assets (even if Bridgetown SSR is enabled) whenever you run bin/bridgetown start. The only downside would be it’s not cached on a global CDN as a fully-fledged static site on Render would be.\n\nThe Migration to Resources is Now Complete\n\nThe other major shift in Bridgetown 1.0 is the removal of the “legacy” content engine in favor of the new “resource” content engine. By consolidating all disparate types of content—datafiles, posts, pages, and custom collection entries—down to one singular and powerful concept (the resource), a vast number of limitations, confusing discrepancies, and outright bugs have been eliminated, and we’re well on our way to providing next-gen content modeling and authoring capabilities rivaling the world’s finest SSGs.\n\nI’ve been using resources exclusively on all Bridgetown site projects for a while now, and it’s been a blast. I simply can’t wait to see what Ruby web developers near and far create using this new technology. Resource documentation is now available on our edge site. (If you aren’t in a position to migrate your codebase to use resources just yet, no worries. We’ll continue to update the 0.2x version of Bridgetown with major bugfixes/security patches until well after 1.0’s official release.)\n\nMore Awesomeness to Come (I18n, esbuild, ERB/Serbea starter kits, SSR enhancements…)\n\nBridgetown 1.0 “Pearl” is already the most action-packed release since the initial launch of the framework, but in many ways we’re just getting started. From full internationalization (I18n) features to blazing-fast frontend bundling via esbuild, from new supercharged starter kits to numerous SSR and Roda API improvements, we expect subsequent releases past 1.0 to continue the trend of making rapid progress for maximum DX (Developer Experience).\n\nWe encourage you to try Bridgetown 1.0 alpha today, and then jump into our Discord chat to let us know what you think. Your feedback and ideas are invaluable in shaping the future of Bridgetown and helping make it the best site generator for Rubyists everywhere.\n\nUpgrading from v0.2x\n\n\n  \n  There&#8217;s now an official up-to-date upgrade guide available here.\n\n\nFirst, you’ll need to specify the new version in your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 1.0.0.alpha11\"\n\n\nYou’ll also need to add Puma to your Gemfile:\n\ngem \"puma\", \"~&gt; 5.2\"\n\n\nThen run bundle install. (You’ll also want to upgrade to the latest version of any extra plugins you may have added, such as the feed and seo plugins.)\n\nNext we suggest you run bundle binstubs bridgetown-core so you have access to bin/bridgetown, as this is now the canonical way of accessing the Bridgetown CLI within your project.\n\nYou will need to add a few additional files to your project, so we suggest using bridgetown new to create a separate project, then copy these files over:\n\n\n  config.ru\n  Rakefile\n  config/puma.rb\n  server/*\n\n\nFinally, you can remove start.js and sync.js and well as any scripts in package.json besides webpack-build and webpack-dev (and you can also remove the browser-sync and concurrently dev dependencies in package.json).\n\nGoing forward, if you need to customize any aspect of Bridgetown’s build scripts or add your own, you can alter your Rakefile and utilize Bridgetown’s automatic Rake task support.\n\nNote: starting with alpha8, your plugins folder will be loaded via Zeitwerk by default. This means you’ll need to namespace your Ruby files using certain conventions or reconfigure the loader settings. Read the documentation here.\n\nThe other major change you’ll need to work on in your project is switching your plugins/templates to use resources. There’s a fair degree of documentation on our edge site regarding resources. In addition, if you used the Document Builder API in the past, you’ll need to upgrade to the Resource Builder API.\n\nWe’ve added an “upgrade-help” channel in our Discord chat so if you get totally suck, the community can give you a leg up! (Access to the problematic repo in question is almost always a given in order to troubleshoot, so if your code needs to remain private, please create a failing example we can access on GitHub.)\n\n\n\nYou’ve made it this far? Wow! Thanks so much for your interest in the future of Bridgetown—and if you haven’t already, please join our fundraising campaign so we can keep adding nifty goodies to Bridgetown and help make your websites awesome."
        },
        {
          "id": "feature-lets-talk-bridgetown-terminology",
          "title": "Let’s Talk Bridgetown Terminology",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "feature",
          "tags": "",
          "url": "/feature/lets-talk-bridgetown-terminology/",
          "content": "As you read through our documentation or spend time in our community chat, you’ll discover we tend to throw around a lot of terms, many of which may be unfamiliar at first glance. It’s easy to feel overwhelmed with all the different tools and options available. But never fear, it’s time to break it all down!\n\nThe Stack\n\nBridgetown is sometimes called a “static site generator” or a “Jamstack” web framework. What does any of that mean?\n\nPerhaps it’s simpler to think in terms of progressive generation, or the idea that the moment at which your HTML output + website assets is generated can vary depending on the tooling or the configuration you choose to use.\n\nThe main options available today are:\n\n\n  Static Site Generation (SSG), or build-time generation: you build your website output once and deploy it to a web server or CDN (Content Delivery Network). From then on, everything that website visitors see comes from those pre-built HTML/CSS/JS files. This is extremely performant as well as secure, because your website is nothing more than files in folders. No special per-request computation required. You can even pull data from APIs or headless CMSes at build time, and redeploy each time there’s a major change.\n  Server-Side Rendering (SSR), or dynamic generation: you write backend code which then gets executed for each request/response cycle. This is necessary when you need to interact with a database or offer user-scoped functionality such as authentication and commerce. You can choose to expose certain SSR routes which will enhance the functionality of your primarily SSG’d website.\n  Client-Side Rendering (CSR), or reactive frontend: you write code which gets executed by the browser to provide on-page interactivity and handle UI events (mouse clicks, window resizing, etc.).\n  Partial or full hydration: you use SSG/SSR techniques to render frontend components server-side, and then when the page is loaded by the browser, it will instantiate the frontend components using the SSG/SSR’d data and continue the CSR lifecycle from there.\n\n\nThere was a time when web frameworks which specialized primarily in SSG, SSR, or CSR were worlds apart from one another. But over the past couple of years, we’ve begun to see a convergence where a single “stack” (or at least a single code repository) can reliably address all these various rendering scenarios. Bridgetown is on track to deliver such a unified stack later this year with the release of v1.0, and we’ll also provide examples of using Bridgetown for SSG alongside popular SSR frameworks such as Ruby on Rails.\n\nThe Languages\n\n Bridgetown is mostly written in Ruby, a programming language first invented by Yukihiro Matsumoto, or “Matz” as he is commonly known, back in 1995. Ruby has been called a language which optimizes for “programmer happiness”, and that’s certainly been our experience over the years. Ruby first saw widespread adoption in large part due to the rise of the Ruby on Rails web framework, but Ruby is far more applicable than just being the foundation of the Rails stack. We’re constantly trying to find ways to make Bridgetown better not in spite of Ruby, but because of it…to leverage what’s so amazing about this elegant and delightful language. There’s also a saying in the Ruby community: MINASWAN (Matz Is Nice And So We Are Nice). It’s a community which tries (and hopefully succeeds more often than not) to be welcoming, encouraging, and safe.\n\nBridgetown also features a frontend-specific layer written in JavaScript, the language which powers webpages everywhere. For a long time, JavaScript was known mainly as the language you’d use alongside HTML and CSS for execution within a browser context. But in 2009, along came Node, which popularized the idea of running JavaScript software at a command line and on the server. Most frontend-specific build tools are now JavaScript-based, thus requiring Node to be installed.\n\nThe Package Managers\n\nBoth Ruby and Node come with their own package managers. A package manager lets you specify modular pieces of software your project relies on, along with their versions (and optionally sources, such as GitHub). When you create a Bridgetown site, for example, your site requires the Bridgetown packages to be installed.\n\n Ruby “packages” are called gems, and gems are published on and downloaded from RubyGems. Ruby’s package management system is comprised of two command line tools: gem and bundle (aka Bundler). When you run bundle install after creating or cloning a Ruby app on your computer, that tells Bundler to look at your Gemfile and Gemfile.lock files, determine the correct dependency tree, and download/install the necessary gems from RubyGems. You can also execute local commands within the correct gem dependency environment by using bundle exec (for example, bundle exec bridgetown build).\n\nNode likewise comes with its own package manager, npm, which will download published JavaScript packages from the npm registry. However, for historical and developer experience (DX) reasons, another commonly-used package manager for Node is called yarn. Both Bridgetown and Rails (and a number of other frameworks in various languages) recommend using Yarn as your package manager, and we include instructions for how to install both Node and Yarn in our installation guides.\n\nJust like Bundler uses Gemfile/Gemfile.lock for Ruby dependency management, Node/Yarn uses package.json/yarn.lock for JS dependency management. Typically you never have to interact with the lock files yourself. They’re only there to “lock in” very specific versions of all dependencies. You will only need to modify either Gemfile or package.json.\n\nThe Version Managers\n\nMost people working on multiple software projects at once will quickly run into the massive headache of having to support and utilize multiple versions of language runtimes. For example, you might be using Ruby 2.6 on one project, Ruby 2.7 on another, and Ruby 3.0 on yet another. Or you might be using Node 14 on one project and Node 16 on another project.\n\nThankfully this is a solved problem when you use a version manager. Version managers are responsible for installing (and often compiling ahead of time) the specific languages you need for your various projects. For Ruby, we recommend using rbenv and include those instructions in our installation guides.\n\nBecause Bridgetown’s use of Node is fairly lightweight, we’re not too opinionated about the exact version of Node you use. But if you wish to utilize a version manager for Node as well, we recommend using nvm.\n\nIn both cases, you’ll add “dot files” to your projects specifying the version of the language you need. For example, saving ruby-2.7.2 to .ruby-version will instruct your Ruby version manager you want to use Ruby 2.7.2. Or saving 14 to .nvmrc means you want NVM to use the latest version of the Node 14 releases.\n\nThe Scripts\n\nOftentimes you want to be able to supply custom scripts in a project to kick off build processes or to interact with tooling or testing in various ways. Yarn makes this easy by looking at the scripts section of your package.json file. So for example, when you run yarn webpack-build in a Bridgetown project, what’s really going on is Yarn will execute the webpack-build script, which itself says to run the webpack --mode production command.\n\nMany Ruby projects rely on a tool called Rake which can run entire tasks (essentially “mini” Ruby programs) defined in a Rakefile. In the next release of Bridgetown, we’ll be adding full support for Rake tasks and migrating some of our own scripts out of package.json and into tasks or dedicated commands.\n\nOnce that release is available, you’ll be able to use “binstubs” — essentially Ruby scripts saved to your project’s bin folder which automatically instantiate the Bundler environment for you. So instead of typing bundle exec bridgetown, you’ll be able to type bin/bridgetown, and instead of starting Bridgetown’s local dev server processes via yarn start, you’ll instead run bin/bridgetown start.\n\nThe Ruby Server\n\nIn the currently shipping release of Bridgetown, we use a Ruby-based web server called WEBrick to serve up the static pages and files generated by Bridgetown’s build process. If you directly run bundle exec bridgetown serve, a WEBrick server is spun up and handles all requests. This is largely for historical reasons, and in the next version of Bridgetown, we’re making a substantial change and migrating to Puma.\n\nPuma is currently the server of choice for the Rails framework, and with good reason. It’s extremely fast, well-maintained, and can scale from simple local development all the way to massive production server deployments.\n\nPuma works in concert with Rack, a low-level specification and infrastructure for Ruby-powered web stacks. Virtually every modern web framework from Rails to Roda to Sinatra to Hanami sits atop Rack, and one of the amazing things about Rack is you can use multiple frameworks at once! Mount a Sinatra app alongside Rails, mount a Rails app alongside Roda, etc. Rack also supports a broad assortment of “middleware” — plugins which will add functionality to or otherwise affect the request/response cycle of your website.\n\nSwitching to Puma + Rack will be a huge win for the Bridgetown codebase and open up all sorts of new opportunities for the platform. Stay tuned.\n\nThe Frontend Tooling\n\n For the frontend (CSS, JavaScript, and related assets such as fonts and icons), Bridgetown uses three primary tools:\n\n\n  Webpack: a popular “bundler” and asset pipeline which takes in one or more “entry points” (typically your frontend/javascript/index.js file), analyzes the tree of various JS &amp; CSS-related import statements, and outputs a final, compiled bundle of JS and CSS code which your Bridgetown site will load.\n  esbuild: a fast JavaScript “transpiler” which will ensure your modern JavaScript code is output in a format consumable by virtually all modern browsers. In the past we used Babel for this task, but esbuild is an order of magnitude faster, and many projects from Rails to Vite to Astro are reworking their frontend infrastructure to leverage esbuild. In the future, we ourselves plan to offer a “lite” frontend pipeline which jettisons Webpack in favor of esbuild only.\n  Either PostCSS or Sass: Depending on how you set up your project, you’ll be using either postcss or sass to process your CSS files. Sass is a well-known superset of CSS which enjoyed popular appeal for many years, but in recent times PostCSS has arisen as a slick way to utilize upcoming standards-based proposals for CSS itself (such as nesting, advanced color functions, etc.) but transpile them to CSS which works in browsers today. Unless you know you need to use Sass for a particular reason (perhaps to leverage a framework like Bulma or Bootstrap), we recommend people start new projects using PostCSS.\n\n\nThe Template Languages\n\n Much of the time as you work on your Bridgetown site, you’ll be authoring content in either Markdown or HTML. Markdown is a great way to include formatting in your plain text documents whether you’re writing a blog post or a portfolio or a marketing page.\n\nWhether using Markdown or HTML to lay out pages and components, you’ll often be working within a “template language” — aka a language which lets you augment your markup with statements to insert or process data, loop through data, filter data, render other components, and generally add all the “smarts” you need on any given page.\n\nBridgetown comes with support for two template languages out of the box, with more available as plugins. These two are Liquid and ERB.\n\nLiquid, the default option, is a fairly simple (and “safe”) template language invented and popularized by Shopify, along with Jekyll (the progenitor of Bridgetown). By using Liquid tags and filters, and by moving repeatable or reusable sections of markup into Liquid components, you can build a fairly sophisticated site in short order.\n\nFor more power and a greater “Ruby-like” experience, you can switch to using ERB. Just like in Rails and other Ruby frameworks, ERB lets you write actual Ruby code within your HTML and Markdown files, which provides additional flexibility and access to the entire world of gems for advanced data processing. But with great power comes great responsibility, and it’s easier to make breaking mistakes when writing ERB (Liquid tends to be more forgiving). Which option is best for your project? It depends mostly on personal preference and which specific features you need.\n\nOther template languages available are Serbea (a “superset” of ERB which adds back in some of the elegant filtering features provided by Liquid along with other shorthand expressions), Slim, and Haml. And if you really want to go out on a limb, you can even render Lit components right in your Ruby template files!\n\nAnd the Rest…?\n\nWhew, that was a lot of terminology! You’d be forgiven if you have a tough time remembering it all. That’s why we’re here to help break it all down and provide you with a solid foundation for growth as a web developer. You don’t need to use or know about all the options here at once. But it’s good to know where to look if you get stuck or when you need to level up your Bridgetown project.\n\nStill got questions? Feedback? Please reach out to us on Twitter or Discord and let us know how the Bridgetown community can help."
        },
        {
          "id": "future-rip-jekyll",
          "title": "Jekyll and the Genesis of the Jamstack",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "future",
          "tags": "",
          "url": "/future/rip-jekyll/",
          "content": "September 15 Update: There’s been a fair amount of internet consternation since I published this article. While I do stand by everything in the post factually-speaking, I apologize for the insensitive timing of this article—coming so soon after Frank’s passing. I’m genuinely sorry this came across as a “Jared vs. Frank” debacle. Should I have waited a few more weeks or months? Probably. Perhaps it was originally a mistake for me to refrain from publicly commenting on the statements regarding Jekyll’s “permanent hiatus” back in May. It’s hard to say. At the very least, I hope we can all agree that Jekyll’s legacy as the “first among many” of modern static site generators is meaningful to a lot of people, even if we sometimes disagree on the best way to honor that legacy and push Ruby on the Jamstack forward. If the one thing that comes out of all this is that more people step forward to share their positive experiences with Jekyll, Ruby, and building websites, that’s a good thing.\n\n\n\nOriginal Article as Published:\n\n“I can tell you there’s no secret plan to revive Jekyll from the dead.” (source)\n“Jekyll is in frozen mode and permanent hiatus. RIP Jekyll 2009-2018.” (source)\n“Good luck to Jared White to build a modern SSG for the Ruby community.”\n\n—Frank Taillandier, (late) release maintainer of Jekyll (known as DirtyF on GitHub)\n\nThose comments were posted in May 2021 by Frank Taillandier in The New Dynamic Slack chat. (Please follow the above source links for additional details.) They’re unfortunately no longer in the Slack archive due to history limits, so those screenshots which I took at the time may very well be the only proof of this information. If they sound shocking to you, they should!\n\nBut let me back up a moment. I adored Jekyll. I loved it ever since I first discovered it—and the brave new world of static site generators—in 2016. As a refugee from the wild reaches of PHP &amp; WordPress, I pivoted my own web studio, Whitefusion, to build Jekyll (and Rails) solutions for clients. I watched firsthand the rise of Netlify and the Jamstack. I cheered Jekyll on from the bleachers and wanted nothing but the best of success for the project…until it became clear to me in early 2020 that there were serious concerns to be had about the future viability of Jekyll. Those concerns led me to fork Jekyll and create Bridgetown.\n\nSo everything I’m about to tell you comes from a place of love, not gamesmanship.\n\nBack to the topic at hand…immediately after Frank posted the above comments, I attempted—repeatedly—to reach out to him in order for us personally to issue a joint public statement as to the nature of Jekyll’s frozen status (and the implication that Bridgetown seemingly had become heir apparent to Jekyll), but to no avail. I can only speculate why he would feel free to communicate with me in The New Dynamic’s Slack about Jekyll’s frozen status—and wish me luck in building Bridgetown (!)—yet refrain from making any public statement to that effect.\n\nI considered publishing my own unilateral statement concerning the matter, but in the end decided to respect Frank’s desire to communicate only through the Slack chat and not work with me on a public statement. Since that time however, Frank has sadly and tragically passed away. (Here’s a wonderful obituary by Scott Gallant, CEO of Forestry/TinaCloud.)\n\nNormally the passing of the lead/release maintainer of a major GitHub project with over 43,300 stars (which also happens to be the progenitor of the modern Jamstack movement) would be a topic of conversation. But with no mention of this news and how it affects the Jekyll project on Jekyll’s website, GitHub repo, or community forum as of September 13, 2021 (9/14 UPDATE: a post written by Ashwin Maroli was published on Jekyll’s website)—and with seemingly no involvement from GitHub directly (more on that shortly)—we can only surmise that the flow of public communication for the project truly has ceased.\n\nTherefore, it’s now the time where I feel obligated to make a public appeal. In my honest assessment:\n\nThere is no clear path forward for Jekyll as a viable and reliable open source technology.\n\nIn recent years, the only other active core team member besides Frank—Ashwin Maroli—was inexplicably absent for most of 2021. As you can see in Frank’s comments back in May, he believed Ashwin had simply stepped down for good. Mere weeks ago, however, Ashwin suddenly emerged once again in commits/comments on Jekyll’s repo with no public mention of why, or what his next plans are.\n\nThis doesn’t inspire confidence in the future of Jekyll.\n\nI hope for the sake of everyone who relies on Jekyll for their businesses and organizations that Ashwin has decided to remain and pay attention to Jekyll again, but I can tell you right now this is not how you establish and maintain trust in the open source community. Open source in 2021 looks like:\n\n\n  Engagement on Twitter\n  Official Discord chat room\n  Public roadmap\n  Predictable release cycles\n  Welcoming community involvement in shaping new features and tackling technical debt\n  Cultivating working relationships with wider ecosystems (in this case Ruby, Jamstack, etc.)\n\n\nLack of any one of these points isn’t the end of the world, but at the present moment, Jekyll lacks ALL of them. That’s a real problem.\n\nIs Jekyll truly dead, as Frank surmised in his May 2021 comments? It all depends on how you look at it. But any honest assessment of the situation must acknowledge that Jekyll’s future is in grave peril.\n\nBridgetown’s Road to 1.0 and the Future of Ruby Static Site Generators\n\nLet’s look at some positive developments that will provide a path forward for Jekyll users.\n\nAs lead maintainer for Bridgetown, I recently posted a brand new roadmap for reaching v1.0 and beyond, along with our upcoming fundraising efforts to meet and exceed those goals. What I forgot to reiterate is how important it is to me personally that we provide clear guidelines and documentation for Jekyll users who wish to upgrade to Bridgetown.\n\nWhile Bridgetown has diverged somewhat from Jekyll in terms of architecture and is not source compatible (for example plugins and themes), it nevertheless remains “inspired by” Jekyll and can offer a compelling answer for nearly all of the features and configuration options Jekyll users know and love—all while adding a dizzying array of new features Jekyll has never and will likely never provide. I have yet to hear feedback from a former Jekyll user who didn’t immediately fall in love with Bridgetown.\n\nBut if for some reason Bridgetown simply isn’t to your liking, what’s the alternative? You could try out another Ruby static site generator with a long and impressive pedigree, Middleman. There’s also Nanoc.\n\nBesides those, the most obvious choice (some might say) would be to switch to Eleventy, which is very popular and offers a fairly Jekyll-like experience but for JavaScript users in the NPM ecosystem.\n\nI certainly have nothing against any of those projects and many more in the world of SSGs from Hugo to Pelican, but I’m a Rubyist through and through. I want Jekyll damnit! (Only WAY better. 😅) That’s why I started Bridgetown in the first place and why it’s been making waves over the past 16 months. (See here, and here, and here, and here, and…)\n\nSo let’s stop beating around the bush. Despite the sad news regarding Jekyll, the future for Ruby on the Jamstack remains bright indeed, and we’re here to lead the way. We’ll be sharing the details on our first major fundraising effort along with a line-by-line project plan later this month. In the meantime, please follow us on Twitter and join our Discord so you won’t miss a thing.\n\n\n\n• Some of you may be wondering wait, I thought Jekyll was created by GitHub and powers GitHub Pages? Doesn’t GitHub work on Jekyll?! The answer is…complicated. While Jekyll was indeed initially created by Tom Preston-Werner, the founder of GitHub, it has stood apart as a separately-maintained project for quite some time now. Matt Rogers, listed as one of three core team members, has no active code-level involvement other than an occasional PR review, and previous lead maintainer Parker Moore stepped down in 2018. Most damning however, is that GitHub Pages is still running on the Jekyll 3.x branch and never upgraded to Jekyll 4! Yes, that’s right: any meaningful improvements Jekyll has made in 4.0 and beyond are not available in GitHub Pages—unless you precompile your site using a GitHub Action. But if you do that, you can theoretically use any SSG—including Bridgetown! I suspect therein lies the actual future of the GitHub Pages produce…it’s likely to evolve into a generic hosting tool and Jekyll will merely be one option among many. So no, from everything I can tell, GitHub won’t suddenly be stepping in to “save” Jekyll. That ship, my friends, has sailed. As Rubyists and web developers, we must plan our next moves based on how things truly are, not on how we wish them to be. Once Jekyll sparked joy, now we must thank it, let it go, and press onward to a better future."
        },
        {
          "id": "release-break-from-summer-to-release-bridgetown-0.21.1",
          "title": "A Break from Summer Break to Bring You Bridgetown 0.21.1",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/break-from-summer-to-release-bridgetown-0.21.1/",
          "content": "Things have been a bit quiet here lately due to the hot, lazy days of summer (in the Northern Hemisphere at least). But we’re taking a break from popsicles, river floats, and jazz festivals to bring you our latest release, v0.21.1.\n\nIt may be a small point release, but it nevertheless has some cool enhancements!\n\n\n  Webpack 5 support. Yes indeed! If you’re ready to hop aboard the latest version of Webpack, we’ve got you covered. To upgrade an existing site, just run bundle exec bridgetown webpack update.  This only works if your site is fairly new, or if you’ve run bundle exec bridgetown webpack setup once before (make sure your files are committed before running setup as it will overwrite your Webpack config!). One other note: if you’re still using node-sass in your package.json dependencies, you might want to consider switching to the newer sass (aka Dart Sass) package as well.\n  Babel begone, hello esbuild. In our updated frontend config, we’ve switched from using Babel (a transpilation tool for converting the latest JavaScript features to polyfills for wider browser support) to esbuild, improving performance and simplifying the bundling process. At some point in the future, we might even be able to retire Webpack! But for now, Webpack + esbuild = 😍.\n  Resource Extension API. If you’ve ever wondered what it’d be like to add your own methods to resource objects, wonder no more! This is now possible, either in local plugins you write or via third-party gems. The API also forms the basis of our new extension point for “summarization” services. You can read more about this in the docs here.\n  Heads up! The configuration option baseurl has changed to base_path so it’s less confusing. Bridgetown will continue to support baseurl in your config file for now but it is marked deprecated.\n\n\nThat’s all folks. Feel free to hop in our Discord chat if you have any questions. We’ve got some massive features in development for later this year so stick around! 😎"
        },
        {
          "id": "release-embracing-ruby-in-0.21",
          "title": "Embracing Ruby, the Best Language for Building Websites, in Bridgetown 0.21",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/embracing-ruby-in-0.21/",
          "content": "Bridgetown v0.21 “Broughton Beach” has been released! 🎉 It’s a major leap forward for the project (just look at those release notes!), and we’re proud of the work we’ve accomplished to push Bridgetown much closer to its 1.0 milestone. As always, you can upgrade by simply bumping the version in your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 0.21\"\n\n\nand running bundle update bridgetown. In addition, we now have a way to upgrade your Webpack config! Keep reading for further details.\n\nSo what’s new in Broughton Beach and why do we think Ruby is the best language for building many of today’s demanding websites?\n\nThe Rise of Components\n\nReact (along with other frontend libraries/frameworks) has made component architecture front and center in web development. The result is some welcome advances in how we author, test, and deliver web content and functionality…but with it often comes enormous complexity requiring advanced build tooling which often falls apart when you need it the most.\n\nOne of the most exciting new ways of thinking about components in the context of server-rendered applications (aka SSR) has been GitHub’s ViewComponent library. ViewComponent has been instrumental in modernizing GitHub’s massive public-facing app with a view architecture that is at once familiar and forward-thinking. It’s increasingly at the heart of many of GitHub’s web properties and has revolutionized how many Rails developers approach building UI. Like Rails, it encourages convention-over-configuration and is incredibly easy to get started with.\n\nIn Bridgetown 0.21, we’re bringing that same ethos to the world of static site generation (SSG). Instead of thinking of your website design as a labyrinthine sequence of templates and partials, you can instead think about it as a composition  of components which are encapsulated, repeatable, testable, and distributable. These components are a combination of real Ruby code, with all of the power and flexibility that affords, plus template files written in your choice of language (ERB, Serbea, Haml, Slim, and beyond!). And by combining your SSR/SSG component markup with modern frontend techniques such as web components and design tokens, you can reason about your frontend CSS and JS code at the same component level and it all Just Works™.\n\nHere’s a peak at what this all looks like:\n\n# src/_components/field_component.rb\nclass FieldComponent &lt; Bridgetown::Component\n  def initialize(type: \"text\", name:, label:)\n    @type, @name, @label = type, name, label\n  end\nend\n\n\n&lt;!-- src/_components/field_component.erb --&gt;\n&lt;field-component&gt;\n  &lt;label&gt;&lt;%= @label %&gt;&lt;/label&gt;\n  &lt;input type=\"&lt;%= @type %&gt;\" name=\"&lt;%= @name %&gt;\" /&gt;\n&lt;/field-component&gt;\n\n\n&lt;!-- some page template --&gt;\n&lt;%= render FieldComponent.new(\n      name: \"email_address\", label: \"Email Address\"\n    ) %&gt;\n\n\nThis is enabled by the new Bridgetown::Component infrastructure built-into Broughton Beach, which features an API very similar to ViewComponent. We feel this is a great start for many Bridgetown projects. However, we didn’t stop there. We brought ViewComponent directly to Bridgetown. 🤯\n\nYes, you’re reading that right. You can write new components using  ViewComponent::Base, and in many cases utilize existing components you’ve already written. We made this work by instantiating a tiny shim which “fools” ViewComponent into thinking it’s loaded inside of a standard Rails application. Because ViewComponent almost entirely relies on Rails’ ActionView gem alone, it integrates pretty seamlessly with our own ERB-and-beyond rendering pipeline.\n\nCheck out this example project which showcases GitHub’s Primer design system, and read up on the new component documentation. We think you’re gonna love it.\n\nP. S. Breaking Change: In order to provide better compatibility with ViewComponent and the Rails ecosystem, ERB now uses an output safety buffer to escape HTML in most strings. This means you may need to use raw/safe helpers or html_safe whereas in previous versions you didn’t.\n\nAll-new take on Ruby Front Matter, plus pure-Ruby data files and templates\n\nBridgetown has long offered a mechanism where you can add a block of Ruby code inside of front matter YAML, but it was a bit awkward, plus it still had a hard dependency on YAML as the only front matter format supported.\n\nNo longer! In Broughton Beach, you can write pure Ruby code as front matter for any or all of your content and layouts (requires the new Resource content engine to be enabled). Not only that, but you can write resources as Ruby files and this even works with data files (yep, add .rb files in src/_data FTW!). Why would you want to do this? Because Ruby is amazing at allowing you and others to write sophisticated DSLs (Domain-Specific Languages) which can express high-level concepts around data processing and serialization as well as markup generation. The greatly expands Bridgetown’s purview from just handling Markdown/HTML content to facilitating a wide range of number-crunching, data analysis, and niche publishing use cases.\n\nWhen you contemplate what you’ll be able to easily accomplish now—especially in the realm of teaching/learning and rapid prototyping—the sky’s the limit. Check out my (Jared)’s blog post on the topic for more information. Or read the documentation here.\n\nAdditional progress on the Resource content engine — and this is the LAST release where it’s optional\n\nWe’ve made many notable improvements to the Resource content engine in Bridgetown 0.21, including a whole new way of expressing relations between resources (one-to-many and many-to-many) for advanced content modeling.\n\nAlso be advised this is the last release where the Legacy content engine is the default and the new engine is something you opt into. In 0.22 we’ll be transitioning to the new engine by default—plus completely overhauling our documentation to reflect that—and you’ll need to opt into the legacy engine if necessary. And a release or two later, we’ll officially remove the legacy engine. More and more sites are currently in development or getting pushed to production using the new engine, and we’re confident this is a solid platform to build upon going forward. In case you need a refresher on why the new engine was needed, here’s additional context.\n\nWebpack upgrades are now a piece of cake\n\nThanks to a tremendous effort by core contributor Ayush Newatia, we now provide a canonical Webpack configuration automatically managed by Bridgetown within your repo and provide a clear integration point to add your customizations (should you need to). Check out this blog post by Ayush to learn more about how you can upgrade your existing Bridgetown sites to the latest Webpack.\n\nDown the road, we anticipate a similar approach to new frontend bundling tools like Snowpack or Vite, as well as adding a flag so you can prevent initial installation of any frontend bundler. Stay tuned.\n\nP. S. We’ve improved the automation steps to enable PostCSS or add support for Tailwind, and we’ve also upgraded our Sass support to the latest Dart implementation. Have a favorite frontend CSS or JS library? We’d love to hear about it and perhaps add it to Bridgetown’s default list of configurations.\n\nA note about Jekyll\n\nPeople are increasingly posting online and asking us (and the Ruby community at large) what’s the deal with Bridgetown vs. Jekyll and where’s it all headed? As Bridgetown has been actively developed (rolling out dozens upon dozens of new features and major improvements) since its initial fork from Jekyll over a year ago, we feel it’s time to offer some clarity.\n\nWe’ve tried and have thus far been unsuccessful in encouraging an official public announcement by the Jekyll core team about the future of Jekyll. Which is unfortunate because it perpetuates continued confusion about the status of these two seemingly similar projects. We have enormous respect for the legacy and impact Jekyll has had (it’s literally the progenitor of the modern Jamstack movement!), so it’s a real shame to see it simply wither on the vine.\n\nOur take is this: Bridgetown is the future, Jekyll is the past. The version of Jekyll you can download today is pretty much what Jekyll will be going forward. The exact words from the core team are “maintenance phase”, and regarding a roadmap, “there isn’t one”. And those comments are over a year old—predating the genesis of Bridgetown! The situation has gotten even more dire since then, with the sole remaining Jekyll maintainer who’s active on the project actively promoting competing solutions like Next.js and Hugo. Make of that what you will.\n\nOf course we’re terribly biased, but we recommend against starting any new projects using Jekyll. If for some reason you’re looking for something that’s not Bridgetown, we recommend taking a look at either Middleman (also Ruby) or Eleventy (JavaScript).\n\nIn the coming weeks we’ll be putting together a detailed migration guide to help you move your existing Jekyll sites to Bridgetown. If you’re a Jekyll plugin or theme author, please reach out to us. We’ll help you with porting your solution to Bridgetown as well as showcase it on our upcoming redesigned website when it launches in Q3 2021. And if you’re looking for enterprise-grade Bridgetown consulting services, there’s an increasing number of offerings available.\n\nConclusion\n\nTime and time again when we ask folks (on Twitter and elsewhere) why they choose Ruby over other languages to build projects with, we hear a constant refrain: Ruby offers the most expressiveness, power, and high-level design thinking in the industry today, enabling tiny teams to compete favorably with much larger competitors—all while staying fun and drama-free (aka minimal yak shaving necessary). Also—in case you haven’t noticed—Ruby’s gotten fast. And when we ask why a few folks have switched to other languages instead, it’s almost always an issue of Ruby being hard to install or not well-supported in some very specific way.\n\nWe’re on a mission to solve the latter, while emphasizing the former. Can Ruby prove to be easy to install and well-supported all while kicking butt in the world of building amazingly sophisticated web software and publications? We believe so. That’s why we’re building and promoting Ruby + Bridgetown = ❤️ now and will continue doing so well into the next decade.\n\nJoin our online community, become a contributor, and let’s build the world’s happiest website development platform together!"
        },
        {
          "id": "release-back-to-basics-0.20-healy-heights",
          "title": "Back to Basics: Bridgetown v0.20 “Healy Heights”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/back-to-basics-0.20-healy-heights/",
          "content": "Before I go grab some corned beef and cabbage to celebrate St. Patrick’s Day (here in the U.S.), I want to tell you all about our latest release! Introducing Bridgetown v0.20 “Healy Heights” — for those looking to upgrade, simply edit your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 0.20\"\n\n\nAnd then run bundle update bridgetown. You might want to create a new blank project and investigate what’s new in package.json and webpack.config.js because we’ve made some significant improvements. (And in the future, we’ll have a more automated upgrade tool there you can use!)\n\nSo what’s new in “Healy Heights”?\n\nSmarter Defaults and Integration with Webpack\n\nWhen we added Webpack integration from the very beginnings of Bridgetown, it was always in service to one goal: make utilizing the latest frontend packages and tools a no-brainer for your site in as straightforward a manner as possible. Part of accomplishing such a goal is to have really a solid “default” Webpack config so you mostly don’t need to touch it or worry about it as you work on your project.\n\nSo we kept the config pretty simple. But over time we’ve heard that it was a little too simple. For example, if you wanted more than one entry point (aka bundle with an output JS file you can load), or you wanted to reference bundled images and other assets directly from your HTML or CSS, that was hard to do.\n\nIn Bridgetown 0.20 we’ve beefed up the webpack_path helper/Liquid tag so that you can use it to reference any entry point or bundled asset in your Webpack manifest. We also improved the configuration defaults a bit around the handling and fingerprinting of font files and image files. So take it out for a spin and let us know if we got it right this time!\n\nBetter Console DX (Developer Experience)\n\nSomething that was personally driving me bonkers was whenever you opened up a console (aka REPL, aka IRB) and pressed the up-arrow key, you wouldn’t get any of the commands you’d executed in the console previously. You’d get stuff from IRB run elsewhere (say, a Rails app). What the what?!\n\nThankfully, that’s now fixed in Bridgetown 0.20! Any time you run bridgetown console and access your command history, you’ll get the latest stuff everytime. Now only that, but any time you print out or inspect the site object, you’ll get a concise little description rather than a giant wad of Matrix-style gobbledeegook all over your screen. DX FTW!!\n\nThe Great Content Realignment: Introducing Resources\n\nBut the biggest news by far in “Healy Heights” is the arrival of an experimental, opt-in new content engine. Yes, my friends: the days of pages being different than blog posts being different than custom collection documents being different than files in _data are coming to an end! The days of blog taxonomies (categories and tags) being restricted to posts only are coming to an end! The days of scratching your head trying to figure out why permalink configuration is so confusing and sometimes mislabeled are coming to an end! The days of wondering how you’ll implement robust i18n are coming to an end! The days of not being able to easily implement targeted data fetching and rendering to enable all kinds of cool use cases (like a Rails API surgically serving up layout-less HTML of a specific page on the site in real-time) are coming to an end! The days of…well, I think you get the idea. 😃\n\n\n\nWe have an entirely new system built around a singular concept we’re calling the Resource. As the documentation says, a resource is a 1:1 mapping between a unit of content and a URL (remember the acronym Uniform Resource Locator?). A “unit of content” is typically a Markdown or HTML file along with YAML front matter saved somewhere in the src folder. While certain resources don’t actually get written to URLs such as data files (and other resources and/or collections can be marked to avoid output), the concept is sound. Resources encapsulate the logic for how raw data is transformed into final content within the site rendering pipeline. (And the docs go on…)\n\nWithout getting to far into the weeds, the greatest strength of Bridgetown having been built on top of the proven foundation of Jekyll has also been our greatest weakness. We inherited all the nice parts, but we also inherited all the nasty bits. Bridgetown 0.20 is our first real foray into doing away with most of the low-level nasty bits, ensuring an advanced and forward-looking foundation for the next ten years of Bridgetown.\n\nThe transition from the legacy content engine to the new “resource” content engine will be a bit rocky. That’s why we’re outlining a clear road map for how to get from A to B:\n\n\n  In this release, the resource content engine is opt-in.\n  In a subsequent release (likely either 0.21 or 0.22), we will make the resource engine the default with the legacy engine still available but officially marked deprecated.\n  For the release of Bridgetown 1.0 later this year, the legacy engine will be removed and the resource engine will reign supreme. This is the place of “API stability” we want to arrive at in order to feel secure in labeling the release a “1.0”.\n\n\nMuch of the pain will be related to rewriting vast swaths of documentation and releasing various plugin updates, as well as providing enough assistance for the Bridgetown sites already in the wild to upgrade. We also anticipate feedback and ideas informing further changes and enhancements to the resource content engine before we hit 1.0. The time to “get this right” is now. So let’s get it right.\n\nUltimately, our goal isn’t simply to compete with Jekyll. Bridgetown sits in a competitive landscape among Jamstack giants such as Gatsby, Eleventy, Next.js, Hugo, and many others. If the Ruby ecosystem isn’t able to produce a worthy contender, we might as well pack our bags now. (Thankfully, we’re genuinely excited about what’s coming down the pike.)\n\nSo as we look ahead to celebrating Bridgetown’s one-year anniversary next month, let’s appreciate how far we’ve come, even as we recognize how far we have to go. The journey is worth the effort.\n\nHave any questions? Run into any issues? Got some juicy feedback? Want to tell us about your spiffy new project? Hop in the Discord Chat or the GitHub Discussions and let us know!"
        },
        {
          "id": "showcase-custom-html-elements-everywhere-executing-the-plan",
          "title": "Custom HTML Elements and Page Layout: Executing the Plan",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "showcase",
          "tags": "",
          "url": "/showcase/custom-html-elements-everywhere-executing-the-plan/",
          "content": "“Make the plan, execute the plan, expect the plan to go off the rails…throw away the plan.”\n– Captain Cold\n\nLast time on the Bridgetown blog, our intrepid web developer (that would be me 😄) boldly ripped the veil off and exposed the shocking truth that this website was—by and large—&lt;div&gt; and &lt;span&gt; tag free. For example (simplified from the production site markup):\n\n&lt;layout-column class=\"column is-three-quarters\"&gt;\n  &lt;h1&gt;Jazz Up Your Site with Themes &amp;amp; Plugins&lt;/h1&gt;\n\n  &lt;article class=\"box\"&gt;\n    &lt;a href=\"https://github.com/bridgetownrb/automations\"&gt;\n      &lt;h2&gt;automations&lt;/h2&gt;\n    &lt;/a&gt;\n\n    &lt;article-content class=\"content\"&gt;\n      A collection of helpful automations that can be applied to Bridgetown websites.\n    &lt;/article-content&gt;\n\n    &lt;article-author class=\"author\"&gt;\n      &lt;img src=\"https://avatars3.githubusercontent.com/u/63275815?v=4\" alt=\"bridgetownrb\" class=\"avatar\" loading=\"lazy\"&gt;\n      &lt;a href=\"https://github.com/bridgetownrb\"&gt;bridgetownrb&lt;/a&gt;\n    &lt;/article-author&gt;\n  &lt;/article&gt;\n&lt;/layout-column&gt;\n\n\nHow was such a feat accomplished? That’s the subject for today’s post.\n\nTime to Execute the Plan\n\nIt’s one thing to claim you can take an existing website and convert it so it only uses semantic and custom HTML elements. It’s another thing to actually do it.\n\nAs I reviewed the usage of various div/span tags on the site, I quickly realized I would need to figure out a way to remember which tags I had already defined as I go along. For example, if I were to convert span in particular contexts to something like ui-label, I’d have to remember to use ui-label again in similar circumstances and not make up something else like widget-caption.\n\nSo one of the first things I did was create a new stylesheet called, appropriately enough, custom_elements.css.  And then, one by one, as I went through every template in the website repository, I would add the elements to this one file. Here’s a snippet from the Bridgetown website:\n\narticle-author,\narticle-content, /* .content */\nimg-container,\nlayout-columns, /* .columns */\nlayout-notice,\nlayout-spacer,\nmain-content /* .content */ {\n  display: block;\n}\n\nui-label {\n  display: inline;\n}\n\n\nBasically this ensures a bunch of custom elements behave the same as using a div (display: block). In addition, while custom elements are display: inline by default, I wanted to enforce and remember the purpose of the ui-label element, so that’s included as well.\n\nI also added comments to indicate that some elements have a 1:1 class name correlation with the CSS framework used by the Bridgetown site (Bulma). If I were writing a stylesheet from scratch, I could hang properties off of an element name selector itself, but on this site I have to work within an existing class-based framework.\n\nBut that’s not all. There are also some elements with 1:1 class name correlations that have their own display properties set by Bulma classes, and I didn’t want to redefine those in my own stylesheet. So I created an additional comment block at the bottom of custom_elements.css:\n\n/*\n# Class names to use for these elements:\nbutton-group = .buttons\nlayout-box = .box\nlayout-column = .column\nlayout-sidebar = .column\nnav-inner = .container\nnav-menu = .navbar-menu\nnav-section = .navbar-brand, .navbar-start, .navbar-end\nui-icon = .icon\n*/\n\n\nNow anytime myself or another contributor is wondering which custom elements to use where, or which class names to use for which element, there’s an obvious reference guide available.\n\nHow Do You Verify Element Names?\n\nThis raises an interesting question: how do you make sure you haven’t added elements and forgotten to include them in the CSS comments? I encountered this conundrum right away, and the solution is simple: use regex!\n\n&lt;[^ !&gt;]*?-\n\n\nIn your text editor, search using this regular expression and it will find all HTML tags with at least one hyphen. Then you can quickly scan through your templates and make sure you didn’t miss anything.\n\nIt’s also a good way to double-back and refactor if you end up deciding on a new element name for a particular use case.\n\nHow Do You Come Up With Useful Element Names?\n\nIf you look at the kinds of names I arrived at during this process, many of them are derived from the parent tag they are associated with. For example, &lt;main&gt; is a builtin HTML5 tag, so &lt;main-content&gt; implies that it contains a subset of the markup within &lt;main&gt;. Similarly, tags like &lt;nav-inner&gt; and &lt;nav-menu&gt; are for use within &lt;nav&gt;, &lt;article-author&gt; within&lt;article&gt;, etc.\n\nOther names are descriptive of the category they represent, for example all the &lt;layout-*&gt; and &lt;ui-*&gt; tags.\n\nSomething that’s important to reiterate is you shouldn’t create a new custom element lightly. Double-check there isn’t a standard HTML element already available. For instance, I don’t need to come up with custom elements to represent headers or footers because HTML already has &lt;header&gt; and &lt;footer&gt; tags.\n\nWhat About Web Components?\n\nOne obvious area for future improvement is to identify which custom elements could possibly be replaced with bona fide web components—either something I write or something already available on NPM. For instance, instead of using &lt;button&gt; tags and then needing &lt;ui-icon&gt; and &lt;ui-label&gt; within the buttons (all using Bulma CSS classes), perhaps I could switch to using &lt;sl-button&gt; web components instead (provided by the Shoelace library).\n\nAs already explained in Part I of this series, web components are by nature custom elements, but custom elements are not web components (unless you upgrade them via JavaScript). So it’s actually not a bad practice to start out with basic markup using custom elements, and then “upgrade” to using a web component if and when the need arises.\n\nLinter Optional\n\nAlso mentioned in Part I, I installed linthtml to enforce a rule of not allowing div/span tags in the codebase. Running this linter was very helpful for finding and correcting all the violations. I’m not necessarily recommending you should take such a drastic measure in your codebase. There’s certainly nothing “wrong” with using div/span tags. I simply felt like it would be a worthwhile exercise to see if you could actually write modern HTML using only builtin semantic or custom elements for the entire website. And the answer of course is: yes you can!\n\nConclusion\n\nWhile it was certainly a chunk of effort for no obviously noticeable gains, I remain very satisfied with the end result of this project, and it’s completely changed how I think about writing HTML and CSS for my websites and web applications. While it’s premature to say I’ll never reach for a &lt;div&gt; or a &lt;span&gt; again, what I can tell you is that it’s quickly becoming habitual not to. And I think that’s a wonderful testament to just how powerful and expressive HTML can be today."
        },
        {
          "id": "release-say-hello-to-ruby-3-and-post-css-in-bridgetown-0.19",
          "title": "Say Hello to Ruby 3 and PostCSS in Bridgetown v0.19 “Arbor Lodge”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/say-hello-to-ruby-3-and-post-css-in-bridgetown-0.19/",
          "content": "Happy Holidays, Merry Christmas, and Joyful Rubyist Tuesday (is that a thing?) as we celebrate the release of Bridgetown 0.19, codenamed “Arbor Lodge”.\n\nTwo main features stand out:\n\n\n  Support for Ruby 3. We’ve fixed bugs and patched gems to make sure Bridgetown works with Ruby 3 right out-of-the-gate. Once you install Ruby 3 (currently in release candidate stage) and upgrade to v0.19, everything should just work*. Support for Ruby 3 is mixed among the prominent Jamstack hosts at present, so we consider this slightly experimental in terms of production readiness. But it’s a perfect time to start tinkering! BTW, if you run into any bugs specific to Ruby 3, or even earlier Ruby versions as a result of our Ruby 3 changes, please file an issue so we can address them right away.\n  Support for PostCSS. With the --use-postcss option added to bridgetown new, you can start your site off with a configuration well-suited to the latest crop of frameworks and “next-gen” CSS methodologies. We know how popular Tailwind has become with some web developers, and now getting it up and running will be a cinch. Sass is still available as the default option.\n\n\nSee our release notes on GitHub for a full changelog.\n\n* There’s a bug in the Thor gem preventing remote automations to run via the bridgetown apply command. However, you can add our patch to your Gemfile while waiting for an official Ruby 3-compatible Thor update.\n\ngem \"thor\", github: \"jaredcwhite/thor\", branch: \"apply-patch-for-ruby-3\"\n\n\nIntroducing Our Latest Core Team Member\n\nIn addition to the new release, we also have a new core team member! Welcome, \nAyush Newatia. Ayush is the driving force behind our new PostCSS integration and has already contributed many super ideas which are helping shape the future of Bridgetown. Ayush is also based in the UK which means our core team is now officially global. 🌍 Hop on over to our Discord chat to say Hello.\n\nWe’re also grateful for our latest crop of sponsors on GitHub! pascalwengerter, DRBragg, and jasoncharnes: you rock! 🤘 (P. S. It’s not too late to join the merry crew!)\n\nA Word on “NEW MAGIC”\n\nToday also marks the day DHH and the intrepid pioneers at Basecamp have released their “NEW MAGIC” — now officially named Hotwire. Comprised of Turbo (the modern successor to Turbolinks) and Stimulus, with full Rails integration, it’s an exciting method of building slick, reactive applications in Ruby with only a “sprinkling” of JavaScript.\n\nWe’re big fans of reactive Ruby. StimulusReflex showed us what’s possible with mere lines of code, and Hotwire is yet another take on how to push Rails to its limits. But there’s just one problem with those approaches as-is: at the end of the day, you’re only building dynamic, server-based applications. What if you also want a static frontend deployed on Jamstack architecture? For some applications, it makes sense to prerender content and serve it up on a global CDN for crazy-fast performance and minimal cost…with a backend only there to assist with some specific interactive bits like a checkout page, a customer portal, or a live stats dashboard.\n\nSome people will tell you you have to switch to a JavaScript-based framework to accomplish that. Perhaps Next.js or Gatsby. We envision a better way, which we jovially refer to as the DREAMstack. Delightful Ruby Expressing APIs &amp; Markup.\n\nOur primary goal in early 2021 is to finish retrofitting Bridgetown to support a dynamic backend integration at any point in your development process. Start out with a basic Bridgetown site, and then add a suitable Rails API whenever you need it. Same repo, same Gemfile, same Ruby. Share a unified view layer between the two. Dynamically re-render Bridgetown pages or components only when required. And now with Hotwire, your Bridgetown site has the possibility become reactive with only a few extra lines of code. Living the DREAM!\n\nStay tuned for our official announcement and “Dreamstack” hosting options soon. In the meantime, enjoy the holidays, have fun with Bridgetown 0.19, and we’ll catch you in the new year. 🥳"
        },
        {
          "id": "showcase-custom-html-elements-everywhere-for-page-layout",
          "title": "Custom HTML Elements and Page Layout: Past, Present, and Future",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "showcase",
          "tags": "",
          "url": "/showcase/custom-html-elements-everywhere-for-page-layout/",
          "content": "As we prepare to enter the year 2021, allow me to make a bold statement: it’s totally unnecessary—perhaps even an anti-pattern—to use &lt;div&gt; and &lt;span&gt; tags in your HTML.\n\nNow before you shake your fist at your smartphone or computer screen and write me off as a veritable nutcase, let us briefly review the history of HTML.\n\nHow We Got Here\n\nHTML stands for “HyperText Markup Language”. It was originally designed as a text format for sharing academic materials. It had virtually no styling abilities, and in fact didn’t even ship with a proper image tag.\n\nThroughout the 1990s, as Netscape rose to prominence and the internet transitioned from higher academia to the public stage, the race was on to make HTML ever more capable in terms of visuals and interactivity.\n\nHTML gave way to DHTML—Dynamic HyperText Markup Language. Netscape had their own idea of how DHTML should work, called the LAYER tag. But that tag never achieved widespread adoption and instead we ended up with a potent combination of CSS and…the DIV tag.\n\nFrom the HTML4 introduction to DIV and SPAN dating from 1999:\n\n\n  The DIV and SPAN elements, in conjunction with the id and class attributes, offer a generic mechanism for adding structure to documents. These elements define content to be inline (SPAN) or block-level (DIV) but impose no other presentational idioms on the content. Thus, authors may use these elements in conjunction with style sheets, the lang attribute, etc., to tailor HTML to their own needs and tastes.\n\n  Suppose, for example, that we wanted to generate an HTML document based on a database of client information. Since HTML does not include elements that identify objects such as “client”, “telephone number”, “email address”, etc., we use DIV and SPAN to achieve the desired structural and presentational effects. We might use the TABLE element as follows to structure the information…\n\n\nWhile it’s true you could use &lt;div&gt; tags in the late 90s, it was slow going in terms of page layout because of the limitations of CSS itself. Many website designs used tables instead, or even resorted to proprietary plugins such as Flash.\n\nBut eventually the industry transitioned away from tables, Flash, etc. to “float-based” layouts (and since flexbox and grid)—and the tag most commonly used to hang styles off of remained the most generic tag of all: DIV.\n\nHowever, that’s not the end of our story! Concurrent with the developing power of CSS to handle complex page layout was the development of XHTML. Everyone in the decade of the 2000s went bananas over XML, and the web community was no different. XHTML was a change that saw HTML switch from being based on SGML (Standard Generalized Markup Language) to being based on XML (eXtensible Markup Language). XML was envisioned as providing a newer/easier/better method of defining new document types/schemas than its predecessor SGML, and one of the benefits of XML provided to XHTML was the ability to mix in other XML schemas. In other words, you could extend HTML itself. (Read this W3C documentation on XHTML modularity.)\n\nYes indeed, you could add new elements to XHTML using externally-defined DTDs (Document Type Definitions), turning HTML itself into nothing more than one building block among many other building blocks for a bigger, more expressive, more structured, semantic web.\n\nOnly that never happened.\n\nXHTML was eventually rejected as The One True Path Forward for the web. In its place, we got HTML5, and HTML5 defined the decade of the 2010s.\n\nHTML5 eventually did away with all its past XML-ness and instead concentrated on making HTML as balanced as possible between familiarity and expressiveness while establishing a clear process for cross-browser collaboration going forward in many related areas (namely, CSS &amp; Javascript).\n\nUnfortunately, in weaning web developers back off of XML, the modular nature of XML largely was lost, and thus enthusiasts of expressive, semantic markup were relegated to the fringe edges of hypertext theory.\n\nOr were they?\n\nIntroducing Custom Elements\n\nWhile everyone was rocking their DIVs and SPANs for accomplishing, well, pretty much anything and everything in HTML page layout, a curious new spec emerged—largely in parallel to HTML5 itself.\n\nFirst drafted in 2013, the Custom Elements spec defined how browsers could “upgrade” a custom element (recognizable by its hypenated nature…i.e. &lt;my-tag&gt;, rather than simply &lt;tag&gt;) and provide it with special interactive powers. The curious thing about the Custom Elements spec is it basically just talks about Javascript and hardly touches on the HTML side of things itself. Why is that?\n\nBecause browsers already allowed virtually unlimited tag names!\n\nSure, it wasn’t “valid” HTML by any means, and you might occasionally run into the odd side-effect, but because browsers were always very liberal about how they interpreted an HTML document, there wasn’t really anything stopping you from using &lt;paragraph&gt; instead of &lt;p&gt; or &lt;navbar&gt; instead of &lt;nav&gt;.\n\nNevertheless, the Custom Elements spec officially “blessed” the usage of custom elements and provided them with full Javascript-based lifecycles. As long as you have at least one hyphen in the tag name, you’re golden. Over time, that spec evolved into the advanced Web Components spec now supported by all modern browsers.\n\nWhat apparently has been lost on many web developers is: you don’t need to use Javascript to use Custom Elements. It’s optional.\n\nThat’s right, all that’s required to add a custom element to your HTML5 document is…to add it. 😄 Here’s how you do it:\n\nTake a bare HTML5 page:\n\n&lt;!doctype html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;My HTML Page&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n\n\nand add &lt;text-greeting&gt;Hello World!&lt;/text-greeting&gt; as a child of &lt;body&gt;.\n\n&lt;!doctype html&gt;\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;My HTML Page&lt;/title&gt;\n  &lt;/head&gt;\n  &lt;body&gt;\n    &lt;text-greeting&gt;Hello World!&lt;/text-greeting&gt;\n  &lt;/body&gt;\n&lt;/html&gt;\n\n\nCongratulations! You’ve just added a custom element to your page. No Javascript required. No need to add customElements.define anywhere. It’s simply not necessary. (Unless you actually want to write a bona fide web component.) You can style your custom element with CSS, query it and manipulate it with vanilla Javascript, and use it in basically any web framework.\n\nEr, So What the Heck is Special About DIV Then?\n\nThe only obvious difference between a custom element and a &lt;div&gt; tag is that custom elements are styled display: inline by default, just like &lt;span&gt;. So in that sense, your use of &lt;span&gt; and a custom element is completely interchangeable.\n\nTo make your custom element behave like a &lt;div&gt;, simply style it with display: block, and voila! Now your custom element is equivalent to &lt;div&gt;. It’s that simple.\n\nAlso, unless you do use the custom elements registry and define a web component, a custom element will be of the HTMLUnknownElement class in Javascript instead of HTMLDivElement or HTMLSpanElement. That’s pretty much it.\n\nSo…if all this is true and custom elements are completely valid in all modern browsers without any Javascript required…then why on earth are we stuck writing 90s-era DIVs everywhere?! It’s semantically meaningless and only serves as a raw vessel for styling purposes!\n\nThe answer is…inertia and ignorance.\n\nEveryone’s already been trained to use &lt;div&gt; and &lt;span&gt; tags everywhere, so that’s just what we do. It’s what everybody does. Nobody ever got fired for adding a &lt;div&gt; tag to a React component or an ERB template or a WordPress theme. And if nobody tells us there’s a better alternative, we’ll never change!\n\nEven within the past month, I’ve been in web dev chat rooms telling some very smart people that you can use custom elements wherever you like without Javascript, and the response is “Wait wut?? Really? Are you kidding me?!”\n\nI don’t know why all the confusion. Maybe it was terrible marketing on the part of the custom element/web component authors. Maybe it was all the deafening noise from the Big Javascript Frameworks (I’m looking at you React!) that simply don’t seem to care.\n\nBut whatever the reason, I’m here to tell you that right here, right now, you can use the Custom Elements spec to your advantage and eliminate virtually all of your DIVs/SPANs in favor of expressive, semantically-useful custom elements.\n\nDon’t believe me? Well, I’ll let you in on a little secret:\n\nThis website is DIV-free. 🤯\n\nThat’s right, all the &lt;div&gt; and &lt;span&gt; tags are gone. (Well, 99.9% of them anyway.) Go ahead, view source. I dare ya. Look in your dev inspector. It’s custom elements (and sure, plenty of regular HTML5 elements) all the way down.\n\nNot only that, but I installed the linthtml linter as part of this site’s build process so that you can’t add any new DIVs/SPANs unless you explicitly bypass the lint rule. Excessive? Probably. Unwise? Maybe. But I’m here to make a point. It’s time to ditch your ancient 90s-era markup and teleport into the future. Custom elements (as well as web components proper) and here and they’re here to stay. We can finally have our HTML cake and eat XML-like extensibility too. Finally.\n\nKeep reading for “part 2” of this story where I do a deep dive into how I converted the Bridgetown website over to using custom elements along with a few neat tips &amp; tricks along the way for organizing and styling your custom element library."
        },
        {
          "id": "videos-easily-configure-template-engines",
          "title": "Easily Configure Template Engines in Bridgetown",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "videos",
          "tags": "",
          "url": "/videos/easily-configure-template-engines/",
          "content": "New for the Bridgetown 0.18 &#8220;Taylor Street&#8221; release, you can now mix-and-match Liquid, ERB, and other template engines freely throughout your site on a per-file basis or as the configured default to be used in conjunction with Markdown and other output formats. Here&#8217;s how!"
        },
        {
          "id": "release-the-future-of-bridgetown-today-in-0.18-taylor-street",
          "title": "The Future of Bridgetown Today in v0.18 “Taylor Street”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/the-future-of-bridgetown-today-in-0.18-taylor-street/",
          "content": "We’re excited to announce the the release of Bridgetown 0.18, codenamed “Taylor Street”. This is an action-packed release which sets the tone for the future of Bridgetown and the broader Ruby web developer community. As always, simply bump up your Gemfile’s version and run bundle update bridgetown to get the latest goodies.\n\nA few of the top highlights, then some additional thoughts:\n\n\n  Choose Your Own Adventure Template Engine. Up ‘till now, the default template engine available in Bridgetown has been Liquid. Don’t get me wrong, Liquid is a perfectly serviceable template language, and it’s certainly not going anywhere. But we feel like there is much to be gained by supporting a variety of languages—most notably the venerable ERB. Bridgetown has supported ERB as an option for several major releases already, but it could only be used on specific files ending in.erb.  Now it’s possible to configure your entire site to use ERB! And not only that, you can mix-and-match template engines on a per-file basis. Have your ERB and drink Liquid too. 😉\n  Render Ruby Components. In addition to adding the render helper as an alternative to the partial helper in Ruby-based templates, you now have the ability to render Ruby objects themselves as components. All an object has to do is provide a render_in method and return a string. That’s it! Ruby files are automatically loaded from src/_components as well as plugins with source manifests, so no additional setup is required. Does this mean…could it be…? Keep reading. 😎\n  link_to and url_for helpers. Now in Ruby-based templates you can use the new link_to and url_for helpers to find pages, posts, and other documents on your site by filename reference or using the content object itself. This will feel familiar to anyone who’s used Rails, and url_for is also aliased to link for those familiar with Liquid.\n  Additional progress on i18n. We’re continuing to add support for internationalization (i18n) in Bridgetown. While it’s as yet undocumented, we recently worked on automatic locale switching from file to file and including a locale in the permalink (for example: /en/my-page, /es/my-page, etc.) when multiple locale-specific pages are present. Layouts and partials can take advantage of the “current” locale to output different content right when a page gets rendered.\n\n\nAnd those are just the highlights! Read the full Bridgetown 0.18 release notes for further details.\n\nThe Future of the Ruby View Layer\n\nIt’s time to talk about where all this is going. In the “render Ruby components” section above, you might be wondering: “wait a moment…if I can return a string from my Ruby object, can’t I just render a template and return that?” And if you’re wondering that, you might also be wondering “gee, that sounds a lot like what ViewComponent does for Rails!”\n\nWell, we’ve been wondering the same thing. While we’re not officially announcing anything today, rest assured it’s a primary goal for Bridgetown to become part of a unified Ruby view layer. This mean whether you’re generating a static site with Bridgetown, serving up a dynamic app with Rails, or using Rails to dynamically render a portion of a Bridgetown page (just a little something our scientists have been cooking up in the lab), we want you to be able to use the same Ruby component library everywhere. And with full Webpack support present in both Bridgetown and Rails, you would be able to write a Ruby component with a “sidecar” stylesheet and Javascript file and get the best of all possible worlds. (Want to write your frontend in Ruby as well? That’s actually a thing!)\n\nSound too good to be true? Well, it is true—all of it. This is what we’ve started to call the DREAMstack:\n\n\n  Delightful\n  Ruby\n  Expressing\n  APIs and\n  Markup\n\n\nWe think Ruby is poised to explode in 2021 with the release of Ruby 3, Rails 6.1 and beyond, as well as the continued adoption of exciting tools which have recently emerged from the Rubyist communities (ViewComponent being one of them, along with StimulusReflex). We think Bridgetown will comprise an important part of the DREAMstack going forward and will bridge the gap (haha) between modern Jamstack deployment techniques and traditional Ruby on Rails apps.\n\nWhen is that day coming? Soon, very soon. 🤞 The future is looking bright indeed."
        },
        {
          "id": "release-whats-new-in-0.17-mount-scott",
          "title": "The Data Cascade, Find Tag, and More in Bridgetown 0.17",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/whats-new-in-0.17-mount-scott/",
          "content": "TGIF! We’ve reached the end of a very challenging work week for those of us on the west coast of the United States. Our hearts go out to everyone who’s been affected by the wildfires.\n\nWhile we don’t have much control over the environment, we do have control over our Rubygems account, and so we’re pleased as Punch to announce the release of Bridgetown 0.17, codenamed “Mount Scott”. Some of the improvements are optimizations at the code level and preparing for bigger features down the road (i18n), but there are also a few goodies you can start using in your projects today.\n\n\n  The Data Cascade &amp; Front Matter Defaults.  Previously, the only way to set front matter defaults for pages, posts, and other documents was to use a powerful but confusing syntax within your site’s configuration file. Now, all you have to do is add a _defaults.yml (or JSON) file to a folder in your source tree, and that data will be applied as front matter for any documents in that folder or subfolders. You can have multiple defaults files in various parts of your source tree or even at the root src folder to affect all documents site-wide. It’s pretty handy! More documentation here.\n  The Find Tag. In any Liquid template, you can now use a single tag to find documents matching a particular set of conditions and assign the first match or all matches to a local variable. For most use cases, this will replace usage of the where_exp filter and make your code more readable. More documentation here.\n  Easily Add Helpers. If you’re using a Ruby-based template language (ERB, Slim, etc.) and you want to write your own helpers, you can now do so with a simple DSL in a Builder plugin. Create expressive new ways to transform and output content in your templates with just a few lines of code! More documentation here.\n  Ruby Front Matter Now On by Default. Whereas in previous releases you had to set an environment variable before you could add Ruby code directly in your front matter, now that feature is available out-of-the-box (although you can still turn it off if you have any security concerns regarding user-submitted content). More documentation here.\n\n\nSo bundle update bridgetown and let us know how it goes! We have some very exciting features in store for the next few releases, so make sure you follow us on Twitter and join our Discord chat so you won’t miss a beat.\n\nAlso, if you’ve benefited at all in any way from Bridgetown, please consider becoming a sponsor on GitHub so we can continue to work extensively on Bridgetown and push the Ruby and Jamstack ecosystems forward. ❤️"
        },
        {
          "id": "release-whats-new-in-0.16-crystal-springs",
          "title": "To ERB and Beyond! What’s New in Bridgetown 0.16 “Crystal Springs”",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "release",
          "tags": "",
          "url": "/release/whats-new-in-0.16-crystal-springs/",
          "content": "It’s the height of summer, and we’re here with a real treat for Rubyists everywhere! At last you can have your cake and eat it too with the release of Bridgetown 0.16 “Crystal Springs”—write site templates in your choice of ERB, Haml, or Slim all while enjoying the benefits of Bridgetown’s Jekyll-inspired ease of configuration and rapid content development process.\n\nTo upgrade your Bridgetown site, simply edit your Gemfile:\n\ngem \"bridgetown\", \"~&gt; 0.16\"\n\n\nAnd run bundle update bridgetown.\n\nERB, Haml, and Slim (oh my!)\n\nOut of the box, Bridgetown 0.16 supports ERB page templates, layouts, and partials, and you can install officially-supported plugins to add handlers for Haml and Slim. This is a deep integration which gives you the ability to:\n\n\n  Freely mix’n’match ERB/Haml/Slim with existing Liquid templates. A page processed with Liquid can use a layout written in ERB, and visa-versa. You can also render Liquid components directly using the liquid_render helper.\n  Add a src/_partials folder and render any number of ERB/Haml/Slim partials with both local and template-level variables from any other template.\n  Use ERB-specific helpers like capture and markdown which provide great flexibility in combining Ruby code with content.\n\n\nNot only that, but ERB/Haml/Slim templates are automatically supplied with the full range of existing Liquid filters as helper methods, so you can write statements like jsonify somevalue and absolute_url post.url (the equivalent of somevalue | jsonify and post.url | absolute_url in Liquid).\n\nWant to add your own helpers? No problem! Simply decorate the Bridgetown::RubyTemplateView::Helpers class or add a mixin.\n\nAll this and more is well-documented in ERB and Beyond. If you run into any problems or have suggestions on how to improve template support, please let us know!\n\nClass Map Liquid Tag\n\nOne of the patterns we’ve noticed that can get really messy when writing Liquid components is trying to toggle on/off CSS classes based on input variables. It requires lots of assign statements and conditionals to get the right string to pass to the class attribute of an HTML element.\n\nBut not anymore! Introducing class_map:\n\n&lt;div class=\"{% class_map has-centered-text: page.centered, is-small: small-var %}\"&gt;\n  …\n&lt;/div&gt;\n\n\nIn this example, the class_map tag will include has-text-centered only if page.centered is truthy, and likewise is-small only if small-var is truthy. If you need to run a comparison with a specific value, you’ll still need to use assign but it’ll still be simpler than in the past:\n\n{% if product.feature_in == \"socks\" %}{% assign should_bold = true %}{% endif %}\n&lt;div class=\"{% class_map product: true, bold-text: should_bold, float-right: true %}\"&gt;\n  …\n&lt;/div&gt;\n\n\nCodebase Grooming\n\nWe’re continuing to invest time and and effort in improving overall codebase quality by refactoring large objects into multiple concerns, adding better YARD documentation, and offering improved stability when there are Webpack build errors.\n\nIn addition, we’ve switched the default branch name of our repo from master to main—and you’re free to use main (or any other name) as well for your automation repos on GitHub as the Bridgetown apply command will now check the GitHub API for the default branch name instead of assuming it’s always master.\n\nFor the full CHANGELOG, read the 0.16.0 release notes.\n\nSpecial Thanks to Our Contributors!\n\nOnce again, we’d like to thank all who contributed to this release (myself, along with MikeRogers0, ParamagicDev, and andrewmcodes), as well as all who supplied feedback and filed issues. You rock!\n\nFinally, if you’ve benefited at all in any way from Bridgetown, please consider becoming a sponsor on GitHub so we can continue to work extensively on Bridgetown and push the Ruby and Jamstack ecosystems forward. ❤️"
        },
        {
          "id": "news-introducing-sponsor-and-core-team-member",
          "title": "Introducing Our Latest Sponsor and a New Core Team Member",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/introducing-sponsor-and-core-team-member/",
          "content": "Today’s post may be short, but it is exciting nonetheless!\n\nFirst of all, I’d like to thank our latest sponsor on GitHub of the Bridgetown project: Martin Tomov. Martin hails from London (hiya! 👋) and has worked with technologies ranging from Ruby on Rails to Elasticsearch to the blockchain! Thanks again Martin for your support. 🙏\n\nAnd now, the moment you’ve all been waiting for: Bridgetown has a new core team member! Please give a warm welcome to Konnor Rogers aka ParamagicDev. Konnor hails from Providence, Rhode Island, and has been very active in our Discord chat for some time now. You might have seen some of the awesome automations and plugins he’s contributed to the Bridgetown ecosystem. Now he’s working on Bridgetown itself and helping to push the project forward in code quality, performance, and capability. We’re thrilled to have you aboard, Konnor!\n\nOne last tidbit: we’re aware of a whole slew of websites-in-development using Bridgetown, so we look forward to getting the word out once some of them go live. So far the feedback has been unanimous: building sites with Bridgetown is a remarkably streamlined and pleasurable experience! And remember, if you have a Bridgetown site you’d like us to showcase here, you can always send us a reply or DM on Twitter!\n\nStay tuned for further news on the next release of Bridgetown and additional video tutorials from me (Jared). Until then, excelsior!"
        },
        {
          "id": "news-bridgetown-around-the-web-late-may-2020",
          "title": "Bridgetown Around the Web (Late May 2020 Edition)",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/bridgetown-around-the-web-late-may-2020/",
          "content": "A lot’s been happening lately in the world of Bridgetown and the Jamstack, and this is our attempt to get caught up in blog form!\n\nFirst of all, a huge round of applause for our awesome crew of “Founding Members”, aka the GitHub sponsors who are helping me (Jared) continue to devote regular time to maintaining and improving Bridgetown. The first ones out of the gate are:\n\n\n  Casper Klenz-Kitenge (@cabgfx)\n  Andrew Mason (@andrewmcodes)\n\n\nThey’re now immortalized in our README file forever. LUV U ❤️\n\n(Pssst…You could be on this list too!)\n\nProduction Websites in the Wild\n\nBridgetown is now being used to power production sites around the web! Kris Bogdanov’s travel and programming blog just relaunched as a Bridgetown site using the TailwindCSS framework, and it looks amazing. Definitely check that out.\n\n\n\nIn other news, I successfully migrated my site at jaredwhite.com from Jekyll to Bridgetown (and jotted down a bunch of notes in the process…tutorial coming soon!). It was no small feat due to the fact that it contains hundreds of articles, podcast episodes, email newsletters, and other content across dozens of tags going back years. Also a good opportunity to upgrade from Gulp to Webpack for the frontend assets. Builds in mere seconds. W00t!\n\nWe’ve heard from a bunch of other projects currently in the works (and are receiving feedback and bug reports accordingly), so it’s exciting to see the community really start to take off.\n\nAMA Fridays\n\nLast week was the first AMA Fridays chat in our new Discord channel, and while only a handful of people attended, we had a blast. It’ll be coming up once again this Friday the 29th at 9:00AM Pacific Time, so make sure you grab the invite link to Discord and hop on over when the time comes to get all of your burning questions about Bridgetown, the Jamstack, and web design/development answered.\n\nPodcasts\n\nI was thrilled to be a guest on the Remote Ruby podcast, episode 78, and talk Bridgetown shop with the rest of the gang. It was loads of fun and a comprehensive overview of how we got here and what’s in store for the project as we look ahead at the next few release cycles.\n\nBridgetown was also a recent topic of conversation on the Ruby Blend podcast, episode 14. Mega appreciations for the shoutout!\n\nMore podcast appearances are in the works, so keep your ears to the grindstone. (Um, that sounded weird. But you get the idea.)\n\nBlog Posts\n\nKonnor Rogers wrote about his journey to building a Docker image for spinning up Bridgetown. If you’re using Docker you can pull the Dockerfile and docker-compose.yml right from his GitHub repository. The article is actually the start of whole blog series, so make sure you add Konnor’s site to your bookmarks.\n\nEmail Newsletters\n\nBridgetown has been featured in recent issues of Ruby Insider, Awesome Ruby, and Jamstacked. Those are also great resources to check out in general!\n\nTweeters Gonna Tweet\n\nThomas Reynolds, the creator of Middleman–another time-honored Ruby-based static site generator–had kind words to say about Bridgetown:\n\n\n\nPlus “Bridgetown rules” according to the Middleman Twitter account. Aww shucks 😊\n\nAndrew Fomera says Bridgetown is “Pretty neat. Feels very 2020” and prefers it over “the other meh JS frameworks”. No comment.\n\nFinally, we got a shoutout from Netlify which was fun to see (and they just so happen to be where we host this site).\n\nThe Merry Merry Month of May\n\nSo that’s it for this month’s installment of Bridgetown Around the Web. Pop back in next month for more tweets, podcasts, links, tutorials, and all that jazz. 🎷\n\nAnd if you’re interesting in trying out Bridgetown or using it for your next project, read the docs to get started."
        },
        {
          "id": "feature-supercharge-your-bridgetown-site-with-ruby-front-matter",
          "title": "Supercharge Your Bridgetown Site with Ruby Front Matter",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "feature",
          "tags": "",
          "url": "/feature/supercharge-your-bridgetown-site-with-ruby-front-matter/",
          "content": "This kind of Ruby front matter has been removed from Bridgetown in favor of a pure Ruby front matter format. This blog post is kept here only for historical reasons.\n\n\nStarting in Bridgetown v0.13, you can now write real Ruby code directly in your Front Matter! 🤯 This feature is available for pages, posts, and other documents–as well as layouts for site-wide access to your Ruby return values.\n\n\n  \n  This requires the environment variable BRIDGETOWN_RUBY_IN_FRONT_MATTER to be set to \"true\" in your development and deployment setups. Otherwise the code will not be executed and will be treated as a raw string.\n\n\nNow you may be wondering, how is this even possible? Front Matter is in YAML format, and the only place you can write actual code is in a custom plugin, right? RIGHT??\n\nWell…it just so happens that there’s this nifty bit of the YAML specification which allows for serialization and deserialization of objects. Normally that functionality is switched off in Bridgetown for security reasons. But we figured out a way to punch a hole through this security barrier to allow for a special type of string that represents Ruby code. It looks like this:\n\n---\ntitle: I'm a page\npermalink: /ruby-demo\ncalculation: !ruby/string:Rb |\n  [2 * 4, 5 + 2].min\n---\n\nTitle: {{ page.title }}\nCalc Result: {{ page.calculation }}\n\n\nIn this example, the value of the calculation variable is the return value of the Ruby code defined by the special string !ruby/string:Rb. The | symbol at the end just means all the indented code in the line(s) below are attached to that front matter variable.\n\nThe value printed out in the rendered page for the calculation will be 7 (since 7 is less than 8 and thus the minimum integer in the array). In fact, on this very page you’re reading, that number 7 is actually being generated by Ruby Front Matter, not hard-coded in this article! Thus you know the system works. ☺️\n\nIn Ruby Front Matter you can return any kind of value that is accessible from a Liquid template. So strings, numbers, arrays, hashes, integers: they all work. Even objects can be returned if they are implemented as a Liquid drop.\n\nYou can also access other Front Matter variables from within the Ruby code itself.\n\n---\ntitle_fragment: Title of a Page\ntitle: !ruby/string:Rb |\n  \"This is the #{data[\"title_fragment\"].sub(\"Title\", \"Name\")}\"\n---\n\n\nNow the page title will read: This is the Name of a Page\n\nUse Cases for Ruby Front Matter\n\nOne particularly compelling use case for Ruby Front Matter is to load data in from a third-party source for inclusion on a page (typically in JSON format, but it could be anything really). Here’s an example of loading a file from a remote GitHub repository and parsing it to obtain useful information:\n\n---\nseo_tag_gem_version: !ruby/string:Rb |\n  url = \"https://raw.githubusercontent.com/bridgetownrb/bridgetown-seo-tag/master/lib/bridgetown-seo-tag/version.rb\"\n  result = Faraday.get(url).body\n  result.match(/VERSION = \"(.*?)\"/)[1]\n---\n\n\nThis will pull the current gem version of the master branch from bridgetown-seo-tag and output it via {{ page.seo_tag_gem_version }}  (it’s 7.0.1).\n\nAnother example is a feature we include on this very website’s footer to show the average number of commits to the Bridgetown project on GitHub over the past month.\n\nHere’s the Ruby Front Matter we include in _layouts/default.html:\n\n---\ngithub_participation: !ruby/string:Rb |\n  endpoint = \"https://api.github.com/repos/bridgetownrb/bridgetown/stats/participation\"\n\n  conn = Faraday.new(\n    url: endpoint,\n    headers: {\"Accept\" =&gt; \"application/vnd.github.v3+json\"}\n  ) do |faraday|\n    if ENV[\"BRIDGETOWN_GITHUB_TOKEN\"]\n      username, token = ENV[\"BRIDGETOWN_GITHUB_TOKEN\"].split(\":\")\n      faraday.request(:basic_auth, username, token)\n    end\n  end\n  json = JSON.parse(conn.get.body)\n  json[\"all\"][-4..].sum\n---\n&lt;!doctype html&gt;\n…etc…\n\n\nThen in our footer (or anywhere on the site), we simply add {{ layout.github_participation }} to output that value.\n\nCaveats and Takeaways\n\nIf you need to write a lot of Ruby code, or write code that is easily customizable or reusable in multiple contexts, we still recommended you write a Bridgetown plugin—either in the plugins folder in your site repo or as a separate Gem-based plugin.\n\nBut if you just need to add a little bit of dynamic functionality to a page or a layout and like being able to see the code and content combined into a single file, Ruby Front Matter is a powerful and immensely flexible solution.\n\nAnd remember, this isn’t a “lite” or stripped-down version of Ruby. This is 100% full Ruby. So you can process page or site data, instantiate objects, require gems, perform network requests, interact with the underlying filesystem, monkeypatch and metaprogram and do anything you’d ever need to do via Ruby.\n\n\n  \n  For security reasons, please do not allow untrusted content into your repository to be executed in an unsafe environment (aka outside of a Docker container or similar). Just like with custom plugins, a malicious content contributor could potentially introduce harmful code into your site and thus any computer system used to build that site. Enable Ruby Front Matter only if you feel confident in your ability to control and monitor all on-going updates to repository files and data.\n\n\nIn summary, if you’re excited to give Ruby Front Matter a try, all you have to do is install Bridgetown v0.13 or later, set BRIDGETOWN_RUBY_IN_FRONT_MATTER to \"true\" in your development environment, and go to town (Bridge…town 😋). And be sure to share your sweet solutions on Twitter with the hashtag #SpinUpBridgetown to let the community know what you’ve built with Ruby Front Matter!\n\n(Also learn which internal Bridgetown objects are made available to your Ruby code in the documentation here."
        },
        {
          "id": "news-time-to-visit-bridgetown",
          "title": "It's Time to Visit Bridgetown",
          "collection": {
            "label": "posts",
            "name": "Posts"
          },
          "categories": "news",
          "tags": "",
          "url": "/news/time-to-visit-bridgetown/",
          "content": "I almost titled this first post Holy Mother Forking Shirt Balls!, but cooler heads prevailed. 😆\n\nSo, introducing Bridgetown. What is it?\n\nIt’s a static site generator.\n\nYes, like Jekyll.\n\nIn fact…\n\n…the reason it’s a lot like Jekyll is because…\n\n…it is Jekyll. (Well, kind of.)\n\nLet me explain. Or rather, let our About page do the talking:\n\n\n  Bridgetown started life as a fork of the granddaddy of static site generators, Jekyll. Jekyll came to prominence in the early 2010s due to its slick integration with GitHub, powering thousands of websites for developer tools. In the years since it has grown to provide a popular foundation for a wide variety of sites across the web.\n\n  But as the concepts of modern static site generation and the Jamstack came to the forefront, a whole new generation of tools rose up, like Hugo, Eleventy, Gatsby, and many more. In the face of all this new competition, Jekyll chose to focus on maintaining extensive backwards-compatibility and a paired-down feature set—noble goals for an open source project generally speaking, but ones that were at odds with meaningful portions of the web developer community.\n\n  So in March 2020, Portland-based web studio Whitefusion started on Bridgetown, a fork of Jekyll with a brand new set of project goals and a future roadmap. Whitefusion’s multi-year experience producing and deploying numerous Jekyll-based websites furnishes a seasoned take on the unique needs of web agencies and their clients.\n\n\nThat’s a fairly long-winded way of saying: I (Jared) have been building a plethora of advanced websites with Jekyll for quite a while now—yet as much as I have loved working with it, it’s definitely started to show its age. After an amicable conversation with the Jekyll core team, I decided to take on the exciting (but incredibly daunting!) task of “forking” Jekyll and using it as the starting point for a reimagined Ruby-based website framework: Bridgetown. And not just me, but I’m betting the entire future of my web studio Whitefusion on this technology.\n\nAlready Going Places\n\nIn a short amount of time, Bridgetown has introduced a slew of new features, cleaned out deprecated or confusing configuration options, and laid the groundwork for major improvements to the manner in which static sites get built for Rubyists and beyond. Our premise is simple: we don’t just want Bridgetown to be a good Ruby-based tool for generating sites. We want it to be good, period.\n\nThat’s why all these changes being made to the codebase now, while perhaps painful in the short term for anyone wanting to quickly migrate from Jekyll to Bridgetown, are vital and necessary, because we’re planning for the next ten years of Jamstack technology innovation.\n\nThis includes our whole-hearted embrace of Webpack. Webpack (and similar Javascript tools like it) has in fairly short order become absolutely indispensable to modern frontend web development—to the point that I would argue any website framework which doesn’t use a tool like Webpack to manage frontend dependencies (along with NPM/Yarn) is actively harming its developer community.\n\nPart of the reason people turn to software frameworks to build things is to get good defaults. You want something that comes with everything you need to start off right so you don’t have to reinvent the wheel or get lost in an industry dead end. This is an active and ongoing focus for Bridgetown, from how the software gets installed, to configuring typical settings and plugins, to best practices in building and deploying the final site.\n\nBridgetown, Not “Crazytown”\n\nIn the year 2020, as the Jamstack phenomenon has taken off like a rocket along with all the ways the web community is pushing the tech forward, a sane person might  argue that it’s time to give up using a Ruby-based framework entirely and switch to using Eleventy, or Gatsby, or Hugo, or Next.js, or Nuxt, or…the list goes on. Listen, I get that, I really do! There are already too many static site generators out there.\n\nBut I’m crazy enough to believe in the bones of the Jekyll software and essential stack choices: Ruby as a delightful, productive language; the power of Liquid templates for rapid layout and prototyping (and soon components!); Kramdown with all its awesome enhancements to Markdown; Gem-based plugins, convention over configuration, etc.). In fact, having now read through every code file and test in the process of making substantial changes and adding new features to Bridgetown, the strength of this technology stack is clearer to me than ever before.\n\nToday, this has become a reality:\n\n\n  \n    gem install bridgetown -N\n  \n  \n    bridgetown new amazing_website\n  \n  \n    cd amazing_website\n  \n  \n    yarn start\n  \n\n\nAnd instantly you have a forward-looking, functioning website foundation with full Webpack support for adding CSS frameworks like Tailwind and Bulma, Javascript frameworks like Stimulus, Vue, or React, and virtually any module on NPM.\n\nAnd you don’t have to abandon Ruby to do it.\n\nGet started today.\n\nGo Bridgetown\n\n(or find out how you can become a contributor…or perhaps join the Bridgetown core team!)\n\n\n  \n  P. S. Let us know if you plan to build something awesome with Bridgetown! And be sure to use the hashtag #SpinUpBridgetown and spread the word! 😃"
        },
        {
          "id": "404",
          "title": "Whoops! 404 Not Found",
          "collection": {
            "label": "pages",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "/404",
          "content": "Welp, that didn't work!\n\nCheck out the rest of the site or use the search box above. Hope you find what you're looking for. 😃"
        },
        {
          "id": "authors-index.json",
          "title": "Index",
          "collection": {
            "label": "pages",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "/authors/index.json",
          "content": "{\n  \"jared\": {\"name\":\"Jared White\",\"avatar\":\"/images/jared-white-avatar-2025.jpg\",\"mastodon\":\"indieweb.social/@jaredwhite\",\"website\":\"https://jaredwhite.com\"}\n,  \"margaret\": {\"name\":\"Margaret Berry\",\"avatar\":\"/images/margaret-berry-avatar.jpg\",\"website\":\"https://www.linkedin.com/in/code-margaret-berry/\",\"github\":\"codemargaret\"}\n}"
        },
        {
          "id": "community",
          "title": "Community",
          "collection": {
            "label": "pages",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "/community/",
          "content": "Where to Get Help and Share Ideas\n\nOfficial Channels\n\n\n  Start by reading the Bridgetown Documentation.\n  Kick off a discussion, ask for assistance, share code &amp; ideas, and enjoy chatting with Bridgetowners on our Discord server.\n  We also have a Matrix chat room. Embrace open protocols and chat with fellow Bridgetowners on Matrix! (Note: if you haven’t used Matrix before, the most recent Element X app is an excellent first-party client on multiple platforms. You’ll be using Element on the web when you directly link to the chat room, and you can install Element on mobile platforms like iOS &amp; Android.)\n  📢 New! We’ve teamed up with the growing Ruby Users Forum which is now providing a Bridgetown tag as a discussion space within their community. Check it out! (➡️ Not all core team members will check regularly for support questions.)\n\n\nThere are a bunch of helpful core team and community members available that should be able to point you in the right direction.\n\nCommercial Support\n\nThere’s also a growing number of companies which provide commercial support for advanced Bridgetown site builds. These include:\n\n\n  Whitefusion\n  Radioactive Toy\n\n\nBecome a Sponsor\n\nIf you’d like to participate in the Bridgetown community by making a huge impact, please consider becoming a financial supporter. Your sponsorship of the continued development of Bridgetown and its ecosystem will ensure our long-term health and sustainability for years to come.\n\nSay Hello to the Core Team\n\n\n  \n\n  \n\n  \n\n  \n\n  \n\n\n@jaredcwhite •\n@KonnorRogers •\n@ayushn21 •\n@adrianvalenz •\n@fpsvogel\n\nBridgetown in the Wild\n\nPeople are using Bridgetown to build amazing things! Here are just a few of the projects launched in recent months:\n\n\n  abnoumaru.com\n  Sidekiq\n  RuboCop\n  Rocky Mountain Ruby\n  Bump.sh Documentation\n  Blue Ridge Ruby\n  Lookbook\n  JaredWhite.com\n  805 Babies\n  Felipe Vogel\n  William Kennedy\n  Fullstack Ruby\n  fslash42\n  Helena’s Nursery\n  Emailable\n\n\nWays to Contribute\n\nWhether you’re a developer, designer, or overall Bridgetown enthusiast, there are lots of ways to contribute to our open source project. Here’s a few ideas:\n\n\n  Comment on some of the project’s open issues. Have you experienced the same problem? Know a work around? Do you have a suggestion for how the feature could be better?\n  Read through the documentation and file a PR by forking the Bridgetown repo on GitHub and adding your changes or suggestions for something that could be improved.\n  Browse through the latest chat activity and lend a hand answering questions or proposing awesome new features. There’s a good chance other folks are in the same boat as you.\n  Help evaluate open pull requests by testing the changes locally and reviewing what’s proposed. Or if you have a PR you’d like to submit for a new feature or a bugfix, we’d be thrilled to evaluate it and guide it through the release cycle if it adheres to our project goals.\n\n\n\n  \n  Reminder: Bridgetown&#8217;s issue tracker on GitHub is not a support forum. Only file issues for reproducible problems you&#8217;ve identified while using Bridgetown.\n\n\nBridgetown Contributor Code of Conduct\n\nAs contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.\n\nRead our full code of conduct.\n\n\n  \n  Want to give us a public shoutout? Check out our official Mastodon account!\n\n\n\n\n\n  \n  Children silhouette designed by Kjpargeter / Freepik"
        },
        {
          "id": "plugins-center",
          "title": "What is Bridgetown Center & How to Apply",
          "collection": {
            "label": "pages",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "/plugins/center/",
          "content": "Bridgetown Center is a new community DevRel program and “seal of quality” for a choice group of plugins &amp; themes built for the Bridgetown web framework.\n\nIt of course remains true that anyone can write a plugin or a theme (which is a type of plugin) for Bridgetown and publish it online, with the code hosted on the Git forge of their choosing (Codeberg, GitLab, and GitHub being popular options). We provide documentation here to help you get started, and you can file a PR in the Bridgetown repo to include your project in our Plugins Directory.\n\nBut if you’re keen to take your efforts to the next level, and are perhaps in search of an extra degree of mentorship in the development &amp; promotion of your plugin(s), we are pleased to offer Bridgetown Center. This brand-new program presents the opportunity to have an outsized impact on developers &amp; users alike. Developers are incentivized to grow their presence and innovate within the Bridgetown ecosystem, and users of Bridgetown will be likely to gravitate towards Center plugins as they craft their own websites and web applications due to the justified expectation of quality.\n\nHow the Program Works\n\nPlugin developers who have been accepted into the Bridgetown Center program and benefit from its support &amp; engagement are committed to ensuring a heightened degree of care over their published work. Such commitments include:\n\n\n  Ensuring plugins are kept updated within a reasonable timeframe whenever there’s a new Bridgetown release which might include breaking changes (which we do try to keep to a minimum).\n  Publishing plugins using an open source or ethical source license. (A “source available” license broadly prohibiting commercial redistribution would not qualify.)\n  Being responsive to community feedback (questions, bug reports, feature requests, etc.), and abiding by the Bridgetown Code of Conduct as they engage with the community.\n    \n      This includes assurance the artifacts they publish (or accept in PRs) within their theme or plugin is free of AI-generated assets (aka code, written materials, artwork, etc.). We won’t police anyone’s personal use of Generative AI. All we ask is a good faith promise that all publicly available material in plugins &amp; themes is created by a human.\n      We make exceptions for traditional “ML” use cases. Just ask the core team if you’re unsure about your use case!\n    \n  \n  Caring about the stability and UX/DX of their software. This can be difficult to quantify, but we evaluate questions like:\n    \n      Is the plugin easy to configure or customize in ways users might expect?\n      Is the plugin validated to be fit to purpose via automated tests and manual QA?\n      Does the plugin refrain from pulling in significant components of other frameworks or specialized build processes? (For example, no surprise inclusion of Active Support, or a requirement to install Tailwind.) If it’s a theme, does it offer design system customization via CSS custom properties?\n      Does the plugin respect locally-executing, free (or libre) computing, and refrain from calling out to third-party APIs &amp; services unless explicitly noted and justified? (For example, a plugin integrating a hosted CMS would only function by calling the API for that CMS which is understandable, whereas a plugin to determine closely-related posts to a given post shouldn’t slyly farm out text analysis to a cloud service and thereby “leak” personal data).\n      Is plugin installation and usage documented well, with instructions written in a friendly tone?\n    \n  \n\n\nAnd remember, being part of Center doesn’t have to be a super long-term commitment. We can help pair developers with people who might be excited to join their projects as new maintainers to free up their time, and if necessary we can assist in offloading projects to other leads or simply remove a project from the program in the worst-case scenario.\n\nIf this sounds like a potential fit for your aspirations as a plugin developer, fabulous! Keep reading to learn more about how Center can supercharge your efforts.\n\nBenefits of Joining Bridgetown Center\n\n\n\nAs part of Center, your projects will be enthusiastically recommended and promoted by the Bridgetown team in official marketing channels as well as during community support. For example:\n\n\n  Your plugin(s) will be featured in a special highlighted section of the Plugins Directory.\n  Upcoming documentation or tutorials we publish may incorporate the use of your plugin.\n  Questions from community members of how to add features or solve problems in their site builds are more likely to be addressed to preference Center plugins.\n  We will use our blog, social media, and other channels to feature Center plugins as well their maintainers.\n  In your own promotion, you can use the Bridgetown Center Badge to indicate to users you’re a member of the program and stand by the quality and humanity of your work.\n\n\nIn addition, joining this program will afford you a greater degree of access to the Bridgetown core team in the form of mentorship &amp; support as you work through issues in the development of your projects.\n\n\n  We offer a dedicated Discord channel and/or Matrix room where you can ask questions and get feedback from core team members.\n  Roughly once each quarter, we will schedule a live video conference where Bridgetown developers &amp; Center participants can touch base and feel a sense of camaraderie.\n  In rare cases when changes to Bridgetown may affect plugin authors in significant ways, we will ping Center participants to ask for comments and suggestions.\n\n\nHow to Apply to Bridgetown Center\n\nThe only principle requirement is that you have at least one plugin developed—either published or in a private beta stage—and you agree to abide by the rules of the program as described previously.\n\n📋 To get started, fill out this form with some brief information about:\n\n\n  Why you wish to join Center\n  Your experience (if any) in working on open source projects\n  What degree of mentorship (if any) you may be hoping to access\n  Your willingness to adhere to the Bridgetown Code of Conduct\n  Link(s) to your project(s) for our evaluation (if they are publicly available).\n\n\nUnless there are any unexpected red flags, we are very likely to be pleased to accept you into the program!\n\nIf you have any additional questions about the program, please ask us in our Discord or Matrix room. 💬\n\nBridgetown Center Badges\n\n\n\nYou may use the badge in your plugin repo and promotional materials to indicate you are a member of the Bridgetown Center program. You may not use the badge if you are not a member of the program, unless you are using it an journalistic sense to feature the program and/or link to plugins or plugin authors participating in the program. Or perhaps you don’t need no stinkin’ badges. That’s fine too! 😜\n\n\n  SVG format (preferred)\n  PNG (768px)\n  PNG (384px)\n\n\n\n  \n  Center iconography based upon Phosphor"
        },
        {
          "id": "sponsor",
          "title": "Become a Bridgetown Sponsor Today",
          "collection": {
            "label": "pages",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "/sponsor/",
          "content": "Q2 2026 Monthly Goal: $400 (USD)\n\n\n  12%\n\n\n\n  Special thanks to andrewmcodes, wout, and additional private donors for your contributions to our Q2 2026 pledge!\n\n\n\n\nA word from the founder &amp; lead maintainer of Bridgetown:\n\n\nBridgetown is the only “hybrid” Ruby website framework which is successfully competing head-to-head with popular JavaScript-based website frameworks, featuring component-based views, a robust plugin layer, world-class esbuild integration, and file-based server routes. And six years into this project, I’m as committed as ever.\n\nThe Bridgetown Core Team &amp; I care greatly about expanding the influence of the “alt Ruby” community (aka the world of frameworks outside of a certain train-themed behemoth 😉), as well as promoting the values of vanilla-first, HTML-first web development—an approach to craft which is made for humans, by humans.\n\n🙏 Your direct financial assistance is greatly appreciated! Every penny goes towards enabling steady and comprehensive improvement and promotion of the Bridgetown framework and ensuring its success for years to come.\n\nThanks for your willingness to sponsor me. Let’s build a better web together.\n\nJared White 🙏\n\n\n\n\n\nSee a past list of GitHub Sponsors here."
        },
        {
          "id": "",
          "title": "Bridgetown",
          "collection": {
            "label": "data",
            "name": "Posts"
          },
          "categories": "",
          "tags": "",
          "url": "",
          "content": ""
        }
]
