# Introduction ## What is Bloggrify? Bloggrify is a **Nuxt Content starter specialized for blogging.** Built on the Nuxt ecosystem: - [Nuxt Content](https://content.nuxt.com/){rel=""nofollow""} — Markdown-based content management - [Nuxt UI](https://ui.nuxt.com/){rel=""nofollow""} — Beautiful components - [Nuxt SEO modules](https://nuxtseo.com/){rel=""nofollow""} — Sitemap, robots, schema.org With blog-specific additions: - 🔌 **Pre-integrated services** (6 analytics providers, 2 newsletter services, comments) - 📝 **Blog-focused features** (multi-author, tags, archives, pagination) - 🎨 **Ready-made themes** designed for blogging - ⚙️ **Unified configuration** (one config file for everything) ## What does "setup work already done" mean? Instead of spending time on: - ❌ Selecting which Nuxt SEO modules to use and configuring them - ❌ Integrating analytics providers (Google Analytics, Plausible, Pirsch, etc.) - ❌ Setting up comment systems, newsletter forms, social sharing - ❌ Building responsive layouts and dark mode - ❌ Configuring mermaid, KaTeX, syntax highlighting You get: - ✅ **Nuxt SEO modules** (`@nuxtjs/sitemap`, `@nuxtjs/robots`, `nuxt-schema-org`) pre-configured - ✅ **6 analytics providers** with unified config (Google Analytics, Plausible, Pirsch, Umami, Fathom, Hakanai) - ✅ **2 newsletter services** (MailerLite, Hakanai) - ✅ **Comment systems** integrated (Hakanai Connect, Hyvor Talk) - ✅ **Production-ready themes** with dark mode - ✅ **Content components** for embeds, diagrams, math ## Why Nuxt Content? Nuxt Content provides the foundation: - **Static site generation** (fast, secure, cheap hosting) - **Markdown-based content** with Vue components (MDC) - **Full-text search**, code highlighting, and more Bloggrify adds the integrations and polish to make it production-ready for blogging. ## When to use Bloggrify vs vanilla Nuxt Content? **Use Bloggrify if you want to:** - Start blogging immediately without configuration - Leverage pre-built integrations (analytics, comments, newsletters) - Use a professional theme out-of-the-box - Have a single config file for everything **Use vanilla Nuxt Content if you:** - Need documentation, changelog, or other content types - Want full control over every aspect - Enjoy building integrations yourself - Have very custom requirements ## Bloggrify in the Nuxt Content ecosystem Bloggrify is a advanced Nuxt Content starters but you may also try: - **[Docus](https://docus.dev/){rel=""nofollow""}** — Documentation sites - **[Alpine](https://github.com/nuxt-themes/alpine){rel=""nofollow""}** — Minimalist blogs Each starter specializes in a different use case. ## Play online You can see a live demo of Bloggrify here : ::button-link --- blank: true href: https://mistral.bloggrify.com/ icon: IconStackBlitz size: small --- Live demo of Bloggrify :: # Installation ## Easy installation First, you need to create a new Nuxt application. You can do this by running the following command: ```bash npx nuxi@latest init myblog ``` Then, you need to install the dependencies: ```bash cd myblog # the blog you just created npm install @bloggrify/core npm install -D sass-embedded ``` Then you have to explicitly say to Nuxt that you are using Bloggrify as an extended module. You can do this by adding the following line in your `nuxt.config.js` file: ```json extends: [ '@bloggrify/core', ], ``` ## Create a basic configuration file You should create a default configuration file in the root of your project: `app.config.ts`. The first configuration you should add is the `theme` of your website. ```typescript export default defineAppConfig({ theme: 'minimalist', }) ``` It's enough to test locally, but you should read more about the configuration options [here](https://bloggrify.com/introduction/configuration). ::note ✨ You can start with the "Minimalist" theme. You can also [change the theme later](https://bloggrify.com/recipes/change-theme) or [create your own theme](https://bloggrify.com/recipes/theme-recipe) . :: ## Create your first content By default, there is no content in your project. Start by creating a new folder called `content` in the root of your project. This folder will contain all the content of your website. Create your index page by creating a new file called `index.md` in the `content` folder. ```markdown --- title: Welcome to Bloggrify description: A simple blog with Bloggrify layout: home listed: false --- ``` Later, you'll add more [content](https://bloggrify.com/content/writing-pages). ## Remove the default app.vue You should remove the default `app.vue` file provided by Nuxt init. ```bash rm app.vue ``` ## Test your blog You can run the development server on {rel=""nofollow""} and start with the default template. ::code-group ```bash [npm] npm run dev ``` ```bash [yarn] yarn dev ``` ```bash [pnpm] pnpm run dev ``` :: ::tip ✨ Well done! A browser window should automatically open for {rel=""nofollow""} :: # Getting Started Once your blog is deployed, you can start writing your content. ## Creating Your First Post You have two options to create a new blog post: ### Option 1: Using the CLI (Recommended) The easiest way to create a new post is using the Bloggrify CLI: ```bash npx bloggrify new post "My first blog post" ``` This will create a properly structured markdown file with all required frontmatter in `content/YYYY/MM/DD/my-first-blog-post.md`. ::note Learn more about the [CLI commands](https://bloggrify.com/introduction/cli) . :: ### Option 2: Manual Creation Alternatively, create a file named `your-first-blog-post.md` manually in the `content/` directory. You can use the following content as a starting point. ```markdown --- title: "My first blog post" description: "This is my first blog post" date: "2024-02-24" categories: - blog - opensource tags: - tag1 - tag2 --- # My first blog post This is my first blog post. I hope you will enjoy it. ``` And then you can appreciate the result on {rel=""nofollow""} More information about writing pages can be found [here](https://bloggrify.com/content/writing-pages). # Deployment ## Manual deployment To deploy your blog, you should first generate the static files with the following command: ```bash npm run generate ``` Then, you can deploy the `dist` folder to your favorite hosting provider. ## Netlify deployment If you have an account on Netlify, you can deploy in one click : [![Netlify deploy button](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/bloggrify/demo-mistral.git){rel=""nofollow""} This will create a new repository on your github account and deploy the blog on Netlify. Alternatively, especially if you want to deploy another template, you can deploy manually on Netlify by following these steps: [Netlify documentation](https://docs.netlify.com/site-deploys/create-deploys/){rel=""nofollow""} See also this blog post about [how to deploy a blog on Netlify](https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/){rel=""nofollow""}. ## Vercel deployment If you have an account on Vercel, you can deploy in one click : [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/bloggrify/demo-mistral.git){rel=""nofollow""} Alternatively, especially if you want to deploy another template, you can deploy manually on Vercel by following these steps: [Vercel documentation](https://vercel.com/docs/deployments/git/vercel-for-github){rel=""nofollow""} ## Github Pages deployment Github Pages serves static files, so it only needs the result of `npm run generate`. The simplest setup builds the blog in a Github Actions workflow and publishes the artifact. First, in your repository, go to **Settings → Pages** and set **Source** to **Github Actions**. Then add the following workflow: ```yaml [.github/workflows/deploy.yml] name: Deploy to Github Pages on: push: branches: [main] permissions: contents: read pages: write id-token: write concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - run: npm run generate env: NITRO_PRESET: github-pages - uses: actions/upload-pages-artifact@v3 with: path: .output/public deploy: needs: build runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - id: deployment uses: actions/deploy-pages@v4 ``` The `github-pages` Nitro preset adds the `.nojekyll` file that keeps Github from ignoring the `_nuxt` directory. If you build without it, create that file at the root of your generated site yourself. ::note If your blog is published on `https://username.github.io/reponame/` rather than on a custom domain, it lives in a subfolder. Set `baseURL` and `url` as described in [Deploying on a subfolder](https://bloggrify.com/#deploying-on-a-subfolder) , otherwise every asset returns a 404. :: ## Codeberg Pages deployment [Codeberg](https://codeberg.org){rel=""nofollow""} is a non profit, EU based forge running on Forgejo. Its Pages service serves static files from a git branch and runs no build of its own, so you build the blog first and publish the result. Two layouts are possible: - a repository named `pages`, served at `https://username.codeberg.page/` - any other repository with a branch named `pages`, served at `https://username.codeberg.page/reponame/` In both cases the published files live on the `pages` branch. The manual deploy is a build followed by a push of the generated folder to that branch. If you have access to [Forgejo Actions](https://docs.codeberg.org/ci/actions/){rel=""nofollow""}, the official action publishes it for you: ```yaml [.forgejo/workflows/deploy.yml] on: push: branches: [main] jobs: deploy: runs-on: docker container: image: node:22 steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run generate - uses: https://codeberg.org/git-pages/action@v2 with: site: https://username.codeberg.page/ token: ${{ forge.token }} source: .output/public/ ``` ::note Hosted CI on Codeberg is a shared resource: Forgejo Actions are available in a limited fashion, and Woodpecker CI is granted on request. Building on your machine and pushing the result is a valid answer while you wait, and the [Codeberg documentation](https://docs.codeberg.org/codeberg-pages/){rel=""nofollow""} covers the custom domain setup. :: The second layout puts your blog in a subfolder, so read [Deploying on a subfolder](https://bloggrify.com/#deploying-on-a-subfolder) before you push. A step by step walkthrough, including the DNS records for a custom domain and what Codeberg does not offer compared to the other hosts, is available in [this blog post](https://minimalist.bloggrify.com/host-your-static-blog-on-codeberg){rel=""nofollow""}. ## Cloudflare Pages deployment If you have an account on Cloudflare, you can deploy in one click : [![Deploy with Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/bloggrify/demo-mistral.git){rel=""nofollow""} Alternatively, especially if you want to deploy another template, you can deploy manually on Cloudflare by following these steps: [Cloudflare documentation](https://developers.cloudflare.com/pages/configuration/git-integration/){rel=""nofollow""} ## Deploying on a subfolder If you want to deploy your blog on a subfolder, like `https://bloggrify.com/subfolder`, you can set the `baseURL` property in the `nuxt.config.ts` file: ```typescript export default defineNuxtConfig({ app: { baseURL: '/subfolder/' } }) ``` Then you have to change the `url` in the `app.config.ts` file: ```typescript export default defineAppConfig({ url: 'https://bloggrify.com/subfolder', }) ``` # Configuration There are three configuration files in the blog: - `app.config.ts` in the root of the project - `nuxt.config.ts` in the root of the project - `.env` in the root of the project ## app.config.ts This is the main configuration file, and where you configure almost everything about your blog. You can configure - the url and SEO settings (indexing, AI crawlers), - the title, - the description, - the logo, - the social networks (twitter, bluesky, mastodon, youtube, linkedin, facebook, instagram, github) for the website - the top menu, - the list of authors | **Key** | **Type** | **Default** | **Description** | | --------------------------------- | --------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `url` | `string` | {rel=""nofollow""} | The public url of the website. Drives the canonical tags, `og:url`, the sitemap and the RSS feed. Takes precedence over `BASE_URL`. | | `language` | `string` | en | The default language of this blog | | **SEO** | | | | | `seo.indexable` | `boolean` | `false` | Allow search engines to index the site. Takes precedence over `SITE_INDEXABLE`. See [Robots.txt](https://bloggrify.com/reference/robots). | | `seo.ai.allowCrawlers` | `boolean` | `true` | Allow AI crawlers (GPTBot, ClaudeBot, ...) to access the site. See [AI crawlers](https://bloggrify.com/reference/ai). | | `seo.ai.llms` | `boolean` | `false` | Publish a `/llms.txt` index of your posts for AI tools. See [AI crawlers](https://bloggrify.com/reference/ai). | | `logo` | `string` | /images/logo.svg | The logo to be used on the header | | `avatar` | `string` | /images/avatar.jpg | The logo of the main author if there is only one author. | | `description` | `string` | lorem ipsum | Website description | | `theme` | `string` | default | The name of the theme to be used (experimental feature) | | `name` | `string` | Bloggrify | Name of your website (used as a title on the index page) | | `table_of_contents` | `boolean` | false | wether you display the table of content on each blog post per default or not | | `toc.showChildren` | `boolean` | false | wether you display the children of the table of content on each blog post or not | | **Socials** | | | | | `socials` | `object` | `{}` | Social links | | `socials.github` | `string` | | The full URL of the GitHub profile | | `socials.linkedin` | `string` | | The full URL of the LinkedIn profile | | `socials.twitter` | `string` | | The full URL of the X/Twitter profile | | `socials.bluesky` | `string` | | The full URL of the Bluesky profile | | `socials.mastodon` | `string` | | The full URL of the Mastodon profile | | `socials.youtube` | `string` | | The full URL of the YouTube channel | | `socials.instagram` | `string` | | The full URL of the Instagram profile | | `socials.facebook` | `string` | | The full URL of the Facebook profile | | `sharing.networks` | `array` | | The list of network to display in the "share" section (if supported by the theme) | | **Authors** | | | | | `authors` | `array` | `[]` | if you have multiple authors, you can set them here | | `authors.username` | `string` | | the username | | `authors.name` | `string` | | The full name | | `authors.description` | `string` | | A text describing this author | | `authors.avatar` | `string` | | An avatar for this author | | `author.socials.github` | `string` | | The full URL of the author's GitHub profile | | `author.socials.linkedin` | `string` | | The full URL of the author's LinkedIn profile | | `author.socials.twitter` | `string` | | The full URL of the author's X/Twitter profile | | `author.socials.bluesky` | `string` | | The full URL of the author's Bluesky profile | | `author.socials.mastodon` | `string` | | The full URL of the author's Mastodon profile | | `author.socials.youtube` | `string` | | The full URL of the author's YouTube channel | | `author.socials.instagram` | `string` | | The full URL of the author's Instagram profile | | `author.socials.facebook` | `string` | | The full URL of the author's Facebook profile | | `author.socials.twitter_username` | `string` | | The X/Twitter handle, without the leading `@`. Not a URL and not rendered as a link: it sets the `twitter:creator` meta tag on the author's posts | | `authors_page.enabled` | `boolean` | `false` | Publish an `/authors` page listing every author. Off by default. See the note below | ::note An author's `name` and `socials` are not only shown in the author bio. They also feed the SEO: the post is credited to its author in the RSS feed ( `` ) and in the schema.org `Person` ( `sameAs` from the socials). See [SEO](https://bloggrify.com/reference/seo#schemaorg) . :: ::note Each author has an individual page at `/authors/{username}`, linked from the byline of their posts. `authors_page.enabled` adds a separate `/authors` index page listing every author, with a link to each individual page. It is off by default: on a single-author blog it is redundant, and listing everyone is a choice rather than a sensible default. When enabled, link it yourself by adding `/authors` to the `menu`. The individual author pages work either way. This page is provided by the `minimalist` theme. A theme that does not implement it returns a 404 for `/authors`. :: ::note Some module can also have their own configuration. For example, the **comment** system, the robots.txt module, etc... Look at their respective documentation to see how to configure them. :: ## nuxt.config.ts Bloggrify is based on Nuxt.js. You can configure your blog by modifying the `nuxt.config.ts` file. We won't detail all the configuration options of Nuxt.js here. You can find more information on the [official documentation](https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-extend){rel=""nofollow""}. ## .env The `.env` file holds two optional environment variables, `BASE_URL` and `SITE_INDEXABLE`. They are fallbacks for `url` and `seo.indexable`, only used when those keys are absent from your `app.config.ts`. They are handy when a value has to differ per deployment, for example to keep a staging build out of search results while production is indexed: ```env SITE_INDEXABLE=false ``` ::note Between versions 2.0 and 3.1, `BASE_URL` was the only way to set the site URL. Since 3.2 it lives in `app.config.ts` again. See the [migration guide](https://bloggrify.com/introduction/migration#migrating-from-v31-to-v32) . :: # Project Structure ## Directory Structure All the files you need to edit are in the `content/` directory, plus the `app.config.ts` file. ```plaintext app.config.ts content/ index.md ``` The `content/` directory is where you [write Markdown pages](https://bloggrify.com/content/writing-pages). The `app.config.ts` is where you [configure Bloggrify](https://bloggrify.com/introduction/configuration) to fit your branding and design. # Migration ## Migrating from v3.1 to v3.2 ### The site URL moves back to `app.config.ts` This is the only breaking change of this release, and it only concerns you if you set `BASE_URL`. The public URL of your blog is now read from the `url` key of your `app.config.ts`, and it takes precedence over the `BASE_URL` environment variable: ```typescript [app.config.ts] export default defineAppConfig({ url: 'https://www.example.com', }) ``` `BASE_URL` still works as a fallback when `url` is absent, so a project that never set `url` keeps working untouched. But if you set **both** and they disagree, `app.config.ts` now wins where `BASE_URL` used to. The build warns you when it spots the disagreement, so you are not left guessing. **What to do:** check that `url` in your `app.config.ts` really is your public URL. Once it is, you can drop `BASE_URL` from your `.env` and your deployment settings. ::warning This value is not cosmetic: it drives the canonical tags, `og:url` , the sitemap and the RSS feed. Pointing it at the wrong domain tells search engines your content belongs to somebody else. :: #### Why the change Version 2.0 moved this setting out of `app.config.ts` and into `BASE_URL`. That was a workaround rather than a design decision: the SEO modules Bloggrify integrates could only read the site URL from the environment at the time. They now expose a Nitro hook, so the setting can live next to the rest of your site identity, which is where it belonged in the first place. ## Migrating from v1.8 to v2.0 Version 2.0 of Bloggrify introduces a few breaking changes. This guide will help you migrate your existing project to the latest version. There is two versions of this migration guide: - one for general users - one for theme developers ### For general users #### Configuration ##### Base URL The url of the website is now configured in the `.env` file. You have to define the `BASE_URL` variable in this file, or available in the environment variables. It replaces the `url` property in the `app.config.ts` file. ::note This was reverted in 3.2: the site URL lives in `app.config.ts` again, and `BASE_URL` is only a fallback. See [Migrating from v3.1 to v3.2](https://bloggrify.com/#migrating-from-v31-to-v32) . :: ##### SITE\_INDEXABLE By default, the whole website is not indexable by search engines. The robots.txt disallows all robots to crawl the website and the sitemap is empty. You can change this by setting the `SITE_INDEXABLE` option to `true` in the `.env` file. ```env SITE_INDEXABLE=false ``` #### Analytics It is now possible to add several analytics services to your website by adding them in the `analytics` property of the `app.config.ts` file. Before: ```json analytics: { provider: 'fathom', fathom: { code: 'YOUR_CODE' } } ``` After: ```json analytics: { providers: [ { provider: 'hakanai', code: 'YOUR_CODE' }] } ``` ##### Alternate links The format of the alternate links in the frontmatter of the markdown file has changed. Before: ```yaml --- language: "fr" alternates: - en: "/2025/01/2024-review" --- ``` After: ```yaml --- alternates: - hreflang: "en" href: "/2025/01/2024-review" - hreflang: "fr" href: "/fr//2025/01/2024-review" --- ``` ### For theme developers #### layouts All layouts have been simplified and needs less props. `default.vue` and `home.vue` layout: ```js defineProps<{ doc: unknown; }>() ``` `archives.vue` layout don't need any props. You may use the `MinimalistListing` component to display the list of articles, or you can create your own component. `tag.vue` layout: ```js defineProps<{ tag: string; }>() ``` and `category.vue` layout: ```js defineProps<{ category: string; }>() ``` Both can use the `MinimalistListing` component to display the list of articles. Example of `tag.vue` layout: ```html ``` # Comparison ## Why Consider Bloggrify? Bloggrify is an open-source Nuxt Content starter specialized for blogging. With its focus on developer experience and pre-configured integrations, Bloggrify strikes the perfect balance between simplicity and functionality. ## What Makes Bloggrify Different? In a landscape filled with blogging platforms and static site generators, Bloggrify stands out by offering: - **Blog-Specialized:** Built on Nuxt Content but focused solely on blogging (vs docs, changelogs, etc.) - **Developer-First Experience:** Vue.js + Nuxt, familiar to frontend developers - **Integrations Included:** Analytics, newsletters, comments pre-configured - **Performance-Oriented:** Nuxt SEO modules configured, 90+ Lighthouse scores by default - **Zero-Maintenance:** No security patches, database backups, or complex updates - **Git-Based Workflow:** Version control and collaboration built into your content process ## Bloggrify vs Vanilla Nuxt Content | Aspect | Bloggrify | Nuxt Content | | ----------------- | ----------------------------------------------------------------------- | ----------------------------------------------- | | **Use case** | Blogs only | Blogs, docs, changelogs, knowledge bases, etc. | | **Setup time** | 5 minutes | 1-2 days (with module selection + integrations) | | **SEO modules** | Pre-configured (`@nuxtjs/sitemap`, `@nuxtjs/robots`, `nuxt-schema-org`) | Choose and configure yourself | | **Analytics** | 6 providers ready (Google, Plausible, Pirsch, Umami, Fathom, Hakanai) | DIY integration | | **Comments** | 2 providers (Hakanai Connect, Hyvor Talk) | DIY integration | | **Newsletter** | 2 services (MailerLite, Hakanai) | DIY integration | | **Themes** | Multiple ready-made | Start from scratch or use community themes | | **Flexibility** | Opinionated for blogs | Total freedom | | **Configuration** | Single `app.config.ts` | Multiple config files | **Bottom line:** Bloggrify is Nuxt Content + opinionated choices + integrations. Choose based on whether you value quick setup or full control. ## Feature Comparison ### vs Static Site Generators These are Bloggrify's closest peers: file-based, Git-friendly static site generators. The comparison here is apples-to-apples — same category, same deployment model (build to static files, host anywhere). | Feature | Bloggrify | Astro | Hugo | Jekyll | Eleventy (11ty) | | ---------------------------- | -------------- | ------------------ | ------------- | ------------- | --------------- | | Language / skills | Vue.js | JS / JSX | Go | Ruby | JS | | Blog-focused out of the box | Yes | Via theme | Yes | Yes | Via starter | | Build time | Fast | Fast | Fastest | Slow at scale | Fast | | Components in Markdown | Built-in (MDC) | Built-in (MDX) | Shortcodes | Limited | Shortcodes | | Syntax highlighting | Built-in | Built-in | Built-in | Built-in | Plugin | | Search | Built-in | Theme / Pagefind | Integration | Plugin | Plugin | | RSS feed | Built-in | Official package | Built-in | Plugin | Plugin | | Sitemap | Built-in | Official package | Built-in | Plugin | Plugin | | Structured data (schema.org) | Built-in | Theme | Template | Plugin | Plugin | | OG images (dynamic) | Built-in | Community (Satori) | Manual | Plugin | Plugin | | Image optimization | Built-in | Built-in | Built-in | Plugin | Plugin | | Tags & categories | Built-in | Built-in | Built-in | Built-in | Data files | | Related posts | Built-in | Manual | Built-in | Plugin | Manual | | Reading time | Built-in | Plugin | Built-in | Plugin | Plugin | | Diagrams (mermaid) | Built-in | Plugin | Plugin | Plugin | Plugin | | Multilingual (i18n) | Partial | Built-in | Built-in | Plugin | Plugin | | Ready-made themes | Multiple | Many | \~550 curated | \~1500 | A few | ### vs Hosted / CMS Platforms WordPress and Ghost belong to a **different category**: they are database-driven, server-hosted publishing platforms with an admin dashboard. Bloggrify has **no database, no server to run, and no admin UI** — content lives in Markdown files under Git version control. So this is not an apples-to-apples comparison of architecture. We still include it because, **feature by feature**, it's a fair question to ask: "can a static blog do what a hosted CMS does?" For most blogging needs the answer is yes — and it does so with lower cost, better performance, and zero maintenance — while trading away the built-in admin UI, native email sending, and membership/paywall features that a hosted product like Ghost provides natively. | Feature | Bloggrify | WordPress | Ghost | | ------------------------------- | ------------------- | ----------------- | ---------------------- | | Architecture | Static files (Git) | PHP + MySQL | Node.js + database | | Hosting cost | $0-5/mo | $10-50/mo | $30+/mo (or self-host) | | Database | None | MySQL | MySQL / SQLite | | Admin dashboard / visual editor | No (Git + Markdown) | Yes | Yes | | Maintenance & security updates | None | Frequent | Regular | | Non-technical authoring | Limited | Yes | Yes | | Comments | Integration | Plugin | Integration | | Search | Built-in | Plugin | Built-in | | Newsletter (email delivery) | Integration | Plugin | Built-in (native) | | Memberships / paywall | No | Plugin | Built-in | | SEO optimizations | Built-in | Built-in / Plugin | Built-in | | Performance (Lighthouse) | 90+ by default | Variable | Good | ## Ideal Use Cases for Bloggrify ### Perfect For: - **Developer Blogs:** Personal or team technical blogs where content is written in Markdown - **Performance-Critical Sites:** When page load times and SEO are top priorities - **Git-Based Publishing:** Teams using pull requests for content review and publishing - **Cost-Conscious Projects:** Sites that need professional features without expensive hosting - **Quick Launch:** Need a blog running in minutes, not days ### When to Consider Alternatives 1. **Documentation Sites** - Need: Multi-version docs, API references - Better alternatives: [Docus](https://docus.dev/){rel=""nofollow""}, [VitePress](https://vitepress.dev/){rel=""nofollow""} 2. **Non-Developer Content Teams** - Need: Visual editor, admin dashboard - Better alternatives: WordPress, Ghost 3. **Polyvalent Content** - Need: Mix of blog, docs, changelog, knowledge base - Better alternative: Vanilla Nuxt Content with custom setup 4. **Complex Plugin Requirements** - Need: E-commerce, forums, membership - Better alternative: WordPress ## Bloggrify's Core Advantages 1. **Developer Experience** - Zero config for blog essentials - Vue.js templating - Hot reload development - Markdown with frontmatter - Single config file 2. **Performance** - 90+ Lighthouse scores by default - Nuxt SEO modules configured - Automatic image optimization 3. **Maintenance** - No security updates needed - No database backups - Simple content versioning via Git 4. **Cost-Effective** - Free hosting on Netlify/Vercel/Gh Pages/Bunny.net - No database costs - CDN included Ready to deploy in 2 minutes? [Get started with Bloggrify](https://bloggrify.com/introduction/getting-started) # CLI Commands Bloggrify includes a command-line interface to help you manage your blog content, authors, and validate your posts. ## Quick Start After installing Bloggrify, you can use the CLI via `npx`: ```bash npx bloggrify --help ``` ## Creating Posts Create a new blog post with frontmatter: ```bash npx bloggrify new post "My Article Title" ``` The CLI will create a markdown file with all required metadata in `content/YYYY/MM/DD/my-article-title.md` and a corresponding image folder in `public/YYYY/MM/DD/my-article-title/`. ::note Run `bloggrify new --help` to see all available options (description, tags, author, flat mode, etc.). :: ### Common Options ```bash # Create a published post with tags npx bloggrify new post "TypeScript Guide" \ --tags "typescript,tutorial" \ --description "Complete TypeScript guide" \ --published # Create in flat mode (content/slug.md instead of dated path) npx bloggrify new post "About" --flat ``` ## Managing Drafts List all draft posts: ```bash npx bloggrify draft list ``` Publish a draft post: ```bash npx bloggrify publish my-article-slug ``` The CLI will find your post even with partial paths: ```bash # All of these work: npx bloggrify publish my-article npx bloggrify publish 2025/01/15/my-article npx bloggrify publish content/my-article.md ``` ## Author Management Add a new author interactively: ```bash npx bloggrify author add ``` The CLI will prompt you for all required information (username, name, description, avatar, socials) and update `app/app.config.ts` automatically. List all configured authors: ```bash npx bloggrify author list ``` ## Validation Validate all your posts for common errors and SEO issues: ```bash npx bloggrify validate ``` This checks for: - **Errors**: Missing title, invalid dates, non-existent authors - **Warnings**: Missing descriptions, SEO issues (title/description length), missing tags, missing cover images ::warning The validation command exits with code 1 if errors are found, making it perfect for CI/CD pipelines. :: ## Statistics View blog statistics: ```bash npx bloggrify stats ``` Shows: - Post count (total, published, drafts) - Timeline (posts per year) - Most used tags - Content quality issues ## Getting Help Every command has a `--help` flag with detailed documentation: ```bash npx bloggrify --help # List all commands npx bloggrify new --help # Options for creating posts npx bloggrify author --help # Author management commands ``` ::tip The CLI is designed to be self-documenting. Use `--help` to discover all available options and stay up-to-date with new features. :: # Markdown For a live demo of Bloggrify in action, check out [our demo blog](https://mistral.bloggrify.com){rel=""nofollow""}. ## Standard Markdown Support Bloggrify supports all standard Markdown formatting you would expect: - **Text formatting**: bold, italic, strikethrough - **Headings**: from H1 to H6 - **Lists**: ordered and unordered - **Links**: internal and external - **Images**: with alt text and captions - **Code blocks**: with syntax highlighting and copy button - **Blockquotes**: for quotes and callouts - **Footnotes**: for citations and references - **Tables**: for data and comparisons ## Enhanced Markdown Features Beyond basic Markdown, Bloggrify supports several advanced features: ### Mathematics Bloggrify supports mathematical expressions using KaTeX: ```markdown $E = mc^2$ $$ \frac{n!}{k!(n-k)!} = \binom{n}{k} $$ ``` [Show demo](https://minimalist.bloggrify.com/2024/katex){rel=""nofollow""}. ### Diagrams with Mermaid Create flowcharts, sequence diagrams, class diagrams and more: ````markdown ```mermaid graph TD; A[Start] --> B[Process]; B --> C[End]; ``` ```` [Show demo](https://minimalist.bloggrify.com/2024/mermaid){rel=""nofollow""}. # Shortcodes It uses the [Nuxt Content module](https://content.nuxt.com/docs/files/markdown#vue-components){rel=""nofollow""} under the hood to parse Markdown files and render Vue components. This allows you to create custom components and use them in your content. Here is a list of some of the built-in components you can use in your Markdown files: - Alerts - Code blocks - YouTube videos - Twitter tweets - Instagram posts - Vimeo videos - Instagram posts [Show demo](https://minimalist.bloggrify.com/2024/markdown){rel=""nofollow""}. ### Using Custom Components You can create your own [custom components](https://content.nuxt.com/usage/markdown#vue-components){rel=""nofollow""} to extend Markdown functionality: 1. Create a Vue component in your `components/content` directory 2. Use it directly in your Markdown content 3. Pass props and content as needed ```markdown ::custom-alert{type="warning"} This is a custom alert :: ``` # Writing Pages Each Markdown pages in the `content/` folder will be mapped to a route. | File | Generated route | | ---------------------- | :------------------- | | `index.md` | `/` | | `about.md` | `/about` | | `2024-02-24/post01.md` | `/2024-02-02/post01` | | `2024/02/24/post01.md` | `/2024/02/02/post01` | ::note It is highly recommended to use a hierarchical structure for the files, to make the blog more organized and to make the urls more meaningful. :: # Frontmatter All the files are markdown files with a frontmatter at the top of the file. They are all listed on the index page, except the ones - with the `draft: true` property. - with the `listed: false` property. Bloggrify supports multiple Front-matter attributes for pages. ## Controlling visibility Three attributes decide who sees a page, and they answer three different questions. Mixing them up is easy, so it is worth being explicit: - `draft` decides **whether the page exists**. A draft is never built, so its URL returns a 404. - `listed` decides **whether the blog points at it**. An unlisted page is built and reachable, it just never shows up in the listings, the feed or the related posts. - `robots` decides **whether Google indexes it**. Independent of the two above. The last two are deliberately separate, because the useful combinations differ: | Frontmatter | Use case | In the listings | In Google | | --------------------------------- | -------------------------------- | --------------- | --------- | | *(nothing)* | a normal post | yes | yes | | `listed: false` | an about page | no | **yes** | | `robots: false` | a post you would rather not rank | yes | no | | `listed: false` + `robots: false` | a page you share by link only | no | no | An about page is exactly why `listed: false` does not imply `noindex`: it is not a blog post, so it has no place in the listings, but you certainly want people to find it. ::warning `robots: false` is a request, not a lock. It keeps the page out of search results, it does not make it private: the page is still built and anyone with the URL can read it. :: It also supports frontmatter to add metadata to your markdown files. ```yaml --- pageid: "2" title: "Use markdown power" description: "Use markdown at its full potential." date: "2024-02-09" tags: - markdown cover: "doc/markdown.png" --- ``` Here is the list of all the frontmatter attributes: | **Key** | **Default** | **Type** | **Description** | | ---------------------- | ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pageid` | | `string` | mandatory if you want to use the comment system. It is used to identify the article. | | `title` | | `string` | Defines the page title | | `description` | | `string` | Defines the page description. Important for SEO | | `date` | | `string` | is used to sort the articles. Date in the future will not be displayed. (format: YYYY-MM-DD) | | `cover` | | `string` | Image for the blog post, also used as an OpenGraph cover image | | `comments` | false | `boolean` | Disable comments on this page | | `listed` | true | `boolean` | `false` keeps the page out of the listings, the RSS feed and the related posts. The page is still built, reachable and indexed. Example: an about page | | `robots` | | `boolean` \| `string` | `false` means noindex/nofollow, a string sets the directive verbatim. See [Controlling indexing](https://bloggrify.com/reference/seo#controlling-indexing-per-page) | | `draft` | false | `boolean` | `true` means the page is never built: no URL, no sitemap entry, no feed item. It stays fully visible in `npm run dev` | | `hidden` | false | `boolean` | **Deprecated**, use `listed: false`. Kept as a synonym of it and reported at build time | | `table_of_contents` | | `boolean` | `true` shows the table of contents on this post (if the theme renders one). It can also be enabled for every post site-wide with the `table_of_contents` option in `app.config.ts`. Use `notoc` to hide it on a single post | | `notoc` | false | `boolean` | `true` hides the table of contents on this post, even when it is enabled site-wide | | `tags` | | `array` | List of tags related to the article | | `categories` | | `array` | List of categories related to the article | | `author` | | `string` | The `username` of one of the authors defined in `app.config.ts`. It sets the post byline, the RSS `` and the schema.org author. Omit it and the post falls back to the author flagged `default: true` | | `redirect_to_domain` | | `string` | Redirects this page to another domain, keeping the **same** path. See [documentation](https://bloggrify.com/reference/redirections) | | `redirect_to_full_url` | | `string` | Redirects this page to another url. See [documentation](https://bloggrify.com/reference/redirections) | | `language` | | `string` | The language of the blog post (useless if it's the same as the whole blog) | | `alternates` | | `array` | (for multilingual website. See [sitemap reference documentation](https://bloggrify.com/reference/sitemap)) | | `alternates.hreflang` | | `string` | The language of the alternative version of this page | | `alternates.href` | | `string` | The url of a page with the same content in another language (to avoid duplicate content and explicit the relation between the two pages) | # Comments You can enable comments on your blog. Two providers are supported: [Hakanai Connect](https://connect.hakanai.io/){rel=""nofollow""} and [Hyvor Talk](https://talk.hyvor.com/){rel=""nofollow""}. The provider is selected with the `provider` option (defaults to `hyvor_talk` when omitted). ## Hakanai Connect If you want to use it, **you'll need to create an account** on [Hakanai Connect](https://connect.hakanai.io/){rel=""nofollow""} and get a key. Then, you can enable it by adding the following code to the `app.config.ts` file: ```typescript comments: { enabled: true, provider: 'hakanai', hakanai: { key: "YOUR_HAKANAI_KEY", }, }, ``` ::note Replace `YOUR_HAKANAI_KEY` with your Hakanai Connect key. :: The current post slug is automatically passed as the thread id (`data-page-id`), just like with Hyvor Talk. ## Hyvor Talk If you want to use it, **you'll need to create an account** and get a website id. Then, you can enable it by adding the following code to the `app.config.ts` file: ```typescript comments: { enabled: true, provider: 'hyvor_talk', hyvor_talk: { website_id: "YOUR_HYVOR_TALK_WEBSITE_ID", }, }, ``` ::note Replace `YOUR_HYVOR_TALK_WEBSITE_ID` with your Hyvor Talk website id. :: # Search Bloggrify comes with a search feature that allows you to search through all your content. It uses the Nuxt-content and Nuxt-UI integration to provide a fast and efficient search. The content is indexed at build time, so the search is fast and doesn't require any server-side processing nor any external service. ## How to use it The search is available on the top right corner of the blog. You can click on the search icon to open the search bar **IF** the theme you are using has the search bar enabled. # SEO By default the language of the blog is set to "en" in the `app.config.ts` file. You can change it to your language. ```typescript export default defineAppConfig({ ... language: 'en' ... }) ``` ## Open Graph and Twitter cards Bloggrify uses the [useHead](https://nuxt.com/docs/api/composables/use-head){rel=""nofollow""} composable to add metadata to the head of the pages. All pages have a default title and description. You can customize the image, title and description of each page in the frontmatter of the markdown file. ### Auto-generated OG Images Bloggrify automatically generates Open Graph images for your blog posts when no custom cover image is specified. **How it works:** 1. **With a custom cover** (recommended for important posts): ```yaml --- title: My Blog Post cover: my-custom-image.png --- ``` 2. **Without a cover** (auto-generation): ```yaml --- title: My Blog Post description: A short description --- ``` When no `cover` is specified, Bloggrify generates an OG image with: - Your post title - Description (if provided) - The post author (when one is resolved from the `author` field and your `authors` config) **Why this matters:** - Better social media sharing (Twitter, LinkedIn, Facebook) - Professional appearance without manual image creation - Consistent branding across all posts ### Customizing the generated OG image Bloggrify ships a default template for these auto-generated images. Since `@bloggrify/core` is a Nuxt layer, you can replace it with your own design: create a component with the same name in your project, and Nuxt gives yours priority over the one provided by the layer. Create `app/components/OgImage/BlogPost.satori.vue`: ```vue ``` Three rules to respect: - The component must live in `app/components/OgImage/` and keep the name `BlogPost`, otherwise it replaces nothing. - The `.satori.vue` suffix is mandatory. It tells the renderer which engine to use, and the build fails without it. - Three props are passed: `title`, `description` and `author`. `title` and `description` come from the frontmatter; `author` is the resolved author's name, present only when the post has one. Use whichever you need, they are all optional in your template. Images are generated at 1200x600 and are built once at generation time, never at runtime. ::warning The image is rendered by [Satori](https://github.com/vercel/satori){rel=""nofollow""} , which supports only a subset of CSS. Flexbox works, CSS grid does not, and any element with several children must set `display: flex` explicitly. Stick to the layout style of the example above and check the result with `npm run generate` . :: ::note This template is only used when a post has no `cover` in its frontmatter. A post with a `cover` uses that image directly and never goes through the template. :: ## Controlling indexing per page Whether the whole blog is indexed is covered in [Robots.txt](https://bloggrify.com/reference/robots). A single page can opt out on its own with the `robots` frontmatter attribute: ```yaml --- title: My Post robots: false --- ``` `false` emits `noindex, nofollow`. A string is passed through verbatim, which is handy when you want to be more precise: ```yaml --- title: My Post robots: "noindex, follow" --- ``` Bloggrify also keeps such a page out of `sitemap.xml`. Submitting a URL you have marked `noindex` is contradictory, and Search Console reports it as an error. ::note Do not confuse this with `listed: false` , which only hides a page from the blog's listings and feed while keeping it indexable. The two are independent, see [Frontmatter](https://bloggrify.com/content/frontmatter#controlling-visibility) . :: ## Performance Performance are an important part of SEO. Here is the lighthouse score of this blog: | index page | blog post | | -------------------------------------------------------------- | ----------------------------------------------------------------------- | | ![index page](https://bloggrify.com/images/doc/lighthouse.jpg) | ![blog post](https://bloggrify.com/images/doc/lighthouse-blog-post.jpg) | Performance could vary depending on the server and the network and images you use in your blog. ## Schema.org Bloggrify uses the [schema.org](https://schema.org/){rel=""nofollow""} vocabulary to add structured data to the blog and use the [Nuxt SEO module](https://nuxtseo.com/docs/schema-org){rel=""nofollow""} to generate the metadata. Everything is done automatically, you don't have to do anything. When a post resolves to an author, the article's `Person` gains a `url` pointing to that author's page and a `sameAs` list built from the author's `socials`. Filling in an author's `name` and `socials` is what turns this into useful structured data. ## SEO Validation Bloggrify includes a CLI command to validate your posts for common SEO issues: ```bash npx bloggrify validate ``` This command checks for: - **Missing or empty descriptions** (important for search engines and social media) - **Title length** (recommended max 60 characters for optimal display in search results) - **Description length** (recommended 50-160 characters) - **Missing cover images** (improves social media sharing) - **Missing metadata** (author, tags, dates) ::tip Run `bloggrify validate` before deploying to catch SEO issues early. :: The validation command is particularly useful in CI/CD pipelines as it exits with code 1 when errors are found. Learn more about [CLI commands](https://bloggrify.com/introduction/cli). # Robots.txt ## Indexing By default, indexation is disabled. You can enable it from your `app.config.ts`: ```typescript [app.config.ts] export default defineAppConfig({ seo: { indexable: true, }, }) ``` You can also use the `SITE_INDEXABLE` environment variable, which is handy when you want a staging deployment to stay out of search results while production is indexed: ```env SITE_INDEXABLE=true ``` When both are set, `app.config.ts` wins. Leave `seo.indexable` out entirely to rely on the environment variable alone. When the site is not indexable, the generated `robots.txt` disallows everything. When it is, all robots are allowed to crawl the blog and the `sitemap.xml` is advertised. ::note Don't forget to configure [your site URL](https://bloggrify.com/introduction/configuration) in your `app.config.ts` !! It is what the `sitemap.xml` advertised here is built from. :: ## AI crawlers Blocking AI crawlers is a dedicated option, since it is the most common thing people want to change here. See [AI crawlers](https://bloggrify.com/reference/ai): ```typescript [app.config.ts] export default defineAppConfig({ seo: { ai: { allowCrawlers: false, }, }, }) ``` ## Advanced configuration Bloggrify uses the [@nuxtjs/robots](https://nuxtseo.com/docs/robots/getting-started/introduction){rel=""nofollow""} module to generate the robots.txt file. Anything the module supports can be set in the `robots` key of your own `nuxt.config.ts`. For example, to disallow Yandex from crawling your blog: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ extends: ['@bloggrify/core'], robots: { groups: [ { userAgent: ['Yandex'], disallow: ['/'], }, ], }, }) ``` Or to keep a section private: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ extends: ['@bloggrify/core'], robots: { disallow: ['/drafts'], }, }) ``` ::warning `robots.txt` is a request, not an access control mechanism. Well-behaved crawlers honour it, but nothing stops the others. Do not use it to hide anything sensitive: a disallowed path is still public, and listing it in `robots.txt` actually advertises it. :: ## Before version 2.0.0 By default, the robots.txt allows all robots to crawl the website. It's possible to modify the robots.txt file in the `app.config.ts` file. For example if you don't want to block yandex: ```typescript robots: [ { UserAgent: "Yandex", Disallow: ["/"], }, ], ``` The default configuration allows all robots to crawl the website. ::note Don't forget to configure URL with the url of your website at the top of the app.config.ts file. :: # Sitemap By default, the sitemap includes all articles and pages of the blog. It is generated automatically by the [`@nuxtjs/sitemap` module](https://nuxtseo.com/docs/sitemap){rel=""nofollow""}. However, there is two exceptions: - pages with the `hidden: false` frontmatter are not included in the sitemap - pages with the `draft: false` frontmatter are not included in the sitemap The sitemap is generated at the URL `/sitemap.xml`. You can disable it by setting the `seo.indexable` option to `false` in your `app.config.ts` file: ```typescript [app.config.ts] export default defineAppConfig({ seo: { indexable: false, }, }) ``` The URLs it lists are built from the `url` key of your `app.config.ts`, so make sure it points at your own domain. See [Configuration](https://bloggrify.com/introduction/configuration). ## Multilingual blog If you have a multilingual blog, you should split your urls by language. For instance, if you have a blog in English and French, you should have the following urls: - `https://www.example.com/en/` - `https://www.example.com/fr/` It is important to configure the alternate links for each page to tell the search engines that the page is available in multiple languages and to avoid duplicate content. You can configure the alternate links in the frontmatter of the markdown file: ```yaml --- alternates: - hreflang: "en" href: "/en/2024-02-24/post01" - hreflang: "fr" href: "/fr/2024-02-24/post01" --- ``` ::note Please note that you have to list all the alternate links in the frontmatter of the markdown file, INCLUDING the current page. :: The `hreflang` attribute is the language of the alternative version of this page. The `href` attribute is the url of a page with the same content in another language. You can read more about the `hreflang` attribute on the [Google documentation](https://developers.google.com/search/docs/advanced/crawling/localized-versions){rel=""nofollow""}. ::note The href can be an absolute URL to another website or a relative URL. If it is a relative URL, it should start with a `/` . :: # Newsletter You can add a newsletter to your blog to keep your readers updated when you publish new content. The tricky part here is that this blog is a static site, so you can't use a traditional newsletter service, or you'll have to manually send out emails every time you publish a new post and that's not very practical. Instead, you can use a RSS-to-email service to create a newsletter and then add a form to your blog to allow your readers to sign up. [Hakanai](https://broadcast.hakanai.io/){rel=""nofollow""} was built for this purpose, it's a SAAS that sends an email every time you publish a new post. Of course you can use another service, here you'll find an example of how to do it with Hakanai but please feel free to use any other service you like. ### Enable RSS feed The RSS feed is enabled by default in this blog, you can find it at `/rss.xml`. You can check it by going to `https://yourblog.com/feed.xml`. ### Create a newsletter on Hakanai - Create an [Hakanai](https://broadcast.hakanai.io/){rel=""nofollow""} account. - Create a [new campaign](https://broadcast.hakanai.io/campaigns){rel=""nofollow""} from your RSS feed. - Go to the [forms section](https://broadcast.hakanai.io/forms){rel=""nofollow""} and create a new form. You can create a simple form with just an email field. Don't worry about the design, but copy and paste the form action URL. You should use the **API action URL**, which looks like this: ```text https://broadcast.hakanai.io/api/campaign/YOUR_CAMPAIGN_ID/subscribe ``` ### Add the form to your blog You just have to enable the newsletter section in the `app/app.config.ts` file and add the form action URL you copied before. ```typescript newsletter: { enabled: true, provider: "hakanai", form_action: "YOUR_FORM_ACTION_URL", } ``` The provider can be: - `hakanai`: the email is sent as a query parameter to the form action URL (`POST {form_action}?email=...`). - `mailerlite`: the email is posted as form data. This is also the fallback for any unknown value. - `demo`: a fake provider that will just show a success message when the form is submitted, without calling anything. ::note The details of how to configure mailerlite is not available. If you want to use mailerlite, you should check the mailerlite documentation to understand how to get the form action URL. :: And voilà! You have a newsletter for your static blog. People can sign up, and they'll receive an email every time you publish a new post. # Analytics You can add **any** analytics tool to your blog. By default, Bloggrify comes with built-in integrations to work with: - [Hakanai Pulse](https://pulse.hakanai.io/){rel=""nofollow""}, - [Fathom](https://usefathom.com/){rel=""nofollow""}, - [Google Analytics](https://analytics.google.com/){rel=""nofollow""}, - [OpenPanel](https://openpanel.dev/){rel=""nofollow""}, - [Pirsch](https://pirsch.io/){rel=""nofollow""}, - [Plausible](https://plausible.io/){rel=""nofollow""}, but you can add any other analytics tool. ## How to configure Hakanai Pulse Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'hakanai', code: 'YOUR_HAKANAI_CODE' }] } ``` ::note Replace `YOUR_HAKANAI_CODE` with the code provided by Hakanai. :: ## How to configure Google Analytics Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'google', code: 'YOUR_GOOGLE_ANALYTICS_ID' }] } ``` ::note Replace `YOUR_GOOGLE_ANALYTICS_ID` with your Google Analytics ID. :: ## How to configure Fathom Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'fathom', code: 'YOUR_FATHOM_CODE' }] } ``` ::note Replace `YOUR_FATHOM_CODE` with your Fathom code. :: ## How to configure OpenPanel Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'openpanel', code: 'YOUR_OPENPANEL_CLIENT_ID' }] } ``` ::note Replace `YOUR_OPENPANEL_CLIENT_ID` with your OpenPanel client ID. :: If you run a self-hosted OpenPanel instance, add `apiUrl` to point the tracker at it instead of the OpenPanel cloud, and `scriptUrl` to load `op1.js` from your own domain: ```json analytics: { providers: [ { provider: 'openpanel', code: 'YOUR_OPENPANEL_CLIENT_ID', apiUrl: 'https://api.your-openpanel-instance.example.com', scriptUrl: 'https://your-openpanel-instance.example.com/op1.js' }] } ``` The two are usually different hosts: the API only receives events, the script is served by the dashboard. `scriptUrl` works with every provider, not just OpenPanel. ## How to configure Pirsch Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'pirsch', code: 'YOUR_PIRSCH_CODE' }] } ``` ::note Replace `YOUR_PIRSCH_CODE` with your Pirsch code. :: ## How to configure Plausible Open the file `app.config.ts` and configure the `analytics` property: ```json analytics: { providers: [ { provider: 'plausible', code: 'YOUR_PLAUSIBLE_CODE' }] } ``` ::note Replace `YOUR_PLAUSIBLE_CODE` with your domain (Plausible identifies sites by domain). :: ::note Note that you can add multiple analytics tools by adding more objects to the `providers` array. :: ## How to add another analytics tool You have to modify the `nuxt.config.ts` file to add the script of the analytics tool. For example, let's say GA is not supported by Bloggrify (it is, but let's pretend it's not). To add [Google Analytics](https://analytics.google.com/){rel=""nofollow""}, you can add the following script: ```typescript export default { app: { head: { script: [ { src: 'https://www.googletagmanager.com/gtag/js?id=YOUR_GOOGLE_ANALYTICS_ID', async: true, }, { innerHTML: ` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'YOUR_GOOGLE_ANALYTICS_ID'); `, }, ], }, }, } ``` # AI crawlers AI and your blog are really two separate questions, and Bloggrify keeps them separate: 1. **Do you want AI crawlers to use your content at all?** That is a `robots.txt` question. 2. **Do you want to make your content easy for AI tools to read?** That is what `llms.txt` is for. They are independent, and answering "no" to the first makes the second pointless. Both live under the `seo.ai` key of your `app.config.ts`: ```typescript [app.config.ts] export default defineAppConfig({ seo: { ai: { llms: false, allowCrawlers: true, }, }, }) ``` ## Blocking AI crawlers Set `allowCrawlers` to `false` to ask AI crawlers to leave your content alone: ```typescript [app.config.ts] export default defineAppConfig({ seo: { ai: { allowCrawlers: false, }, }, }) ``` This adds a `Disallow: /` group to your `robots.txt` covering the AI crawlers of the major vendors: `GPTBot` and `OAI-SearchBot` (OpenAI), `ClaudeBot` (Anthropic), `Google-Extended` and `Google-NotebookLM` (Google), `meta-externalagent` (Meta), `Amazonbot` and `bedrockbot` (Amazon), `PerplexityBot`, `MistralAI-User`, `DeepSeekBot`, `Bytespider`, `CCBot` (Common Crawl, which feeds many training sets), and around thirty others. The names follow the [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt){rel=""nofollow""} reference list. Your blog stays indexed in Google and Bing: search crawlers are deliberately left alone. `Google-Extended` controls Gemini training and is distinct from `Googlebot`, so you refuse the former while keeping the latter. For the same reason `Applebot` (Siri, Spotlight) and `facebookexternalhit` (Open Graph link previews) are not blocked, since blocking them would break features you probably want. Only `Applebot-Extended`, Apple's AI-training agent, is. Bloggrify ships a curated list, not every scraper in existence. If you want to go further, add your own group in your `nuxt.config.ts`: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ extends: ['@bloggrify/core'], robots: { groups: [ { userAgent: ['SomeOtherBot'], disallow: ['/'], }, ], }, }) ``` ::warning This is a polite request, not a protection. `robots.txt` is voluntary: well-behaved crawlers honour it, others have been caught ignoring it. Anything published on a static, public blog can be copied. If you need real enforcement, block at the network level instead, for example with Cloudflare's "Block AI Scrapers" toggle. That happens at your host, outside of Bloggrify. :: ## Publishing llms.txt Set `llms` to `true` to publish a `/llms.txt` route: a markdown index of your posts, with a title, a description and a link for each one. See [llmstxt.org](https://llmstxt.org){rel=""nofollow""} for the format. ```typescript [app.config.ts] export default defineAppConfig({ seo: { ai: { llms: true, }, }, }) ``` The file is generated at build time, alongside `sitemap.xml` and `rss.xml`. Drafts and hidden posts are excluded. When `llms` is `false`, which is the default, the route is not generated at all. ::note `llms.txt` is an invitation, not a restriction. It does not tell crawlers what they may do, it only makes your content easier to consume. If you want AI tools to stay away, `allowCrawlers: false` is the option you are looking for, and enabling both at once is contradictory. Bloggrify prints a warning at build time if you do. :: ### Is it worth enabling? Be aware that the value is speculative today. No major AI provider has confirmed that its crawlers read `llms.txt`, and Google has publicly compared it to the old `keywords` meta tag. The case that does work is a human pasting your URL into a chat: the tool fetches the page, and clean markdown beats HTML full of navigation and scripts. For a documentation site, that is a real win. For a blog, where each post is already a clean page, the gain is smaller. It costs one small file, so enable it if you like, but do not expect traffic from it. ## Indexing Whether search engines index your blog at all is a separate setting, documented in [Robots.txt](https://bloggrify.com/reference/robots). # Theming It is possible to change the look and feel of your blog by changing the theme. By default, Bloggrify comes with the "Minimalist" theme, but you can create your own theme or use a theme created by the community. You can find some templates [here](https://bloggrify.com/templates). The theming system use extensively the [concept of layers](https://nuxt.com/docs/examples/advanced/config-extends){rel=""nofollow""} and [layouts](https://nuxt.com/docs/guide/directory-structure/layouts){rel=""nofollow""} in Nuxt. A theme `extends` [Bloggrify](https://github.com/bloggrify/bloggrify){rel=""nofollow""} and brings its own configuration and components. ## Structure of a theme A theme is a Nuxt module. It is a folder that contains : - an `app/app.config.ts` file. The `app.config.ts` file contains the configuration of the theme. - an `app/components/themes/MYTHEME` folder. The `components` folder contains the components of the theme. - an `app/layouts/themes/MYTHEME` folder. The `layout` that can be used into each page. - a `content` folder. It is where you write your articles. - an `app/assets` folder (OPTIONAL). It is where you put your images and other assets (css files, etc.) - a `public` folder (OPTIONAL). It is where you put your static files (like a favicon etc.) Each theme **should** have the following components (with the exact same name): - `app/layouts/themes/MYTHEME/default.vue`: The default layout for all pages. Of course you can create other layouts if you want for specific pages (like the home page) And then you can select the theme in the `app/app.config.ts` file. ```typescript export default defineAppConfig({ theme: "MYTHEME", // ... }) ``` ## Create a new theme To create a new theme, you can either: - start from the ["Mistral"](https://github.com/bloggrify/bloggrify-mistral){rel=""nofollow""} theme. It is a simple and clean template. You can clone the repository and start modifying it. - start from scratch. You can follow the [theme creation guide](https://bloggrify.com/recipes/theme-recipe) to create your own theme. This is a nuxt-application so you can benefit from all the features of Nuxt. You can use the [Nuxt documentation](https://nuxt.com/){rel=""nofollow""} to help you. But most of the time, you'll only need to modify the files in the `app/components` folder and the `app/app.config.ts` file. ## Supporting tags and categories If you want to support tags and categories, you should create the following layouts in your theme: - `app/layouts/themes/MYTHEME/tag.vue` - `app/layouts/themes/MYTHEME/category.vue` These layouts will be used to display the list of articles for a specific tag or category. You can check the [Mistral theme](https://github.com/bloggrify/bloggrify-mistral){rel=""nofollow""} for an example. ## Supporting the authors pages Two layouts back the author pages: - `app/layouts/themes/MYTHEME/author.vue` receives an `author` prop and displays one author's bio and posts, at `/authors/{username}`. - `app/layouts/themes/MYTHEME/authors.vue` receives an `authors` prop (the array from `app.config.ts`) and lists every author, at `/authors`. This page is opt-in through `authors_page.enabled`, see [Configuration](https://bloggrify.com/introduction/configuration). A theme that does not implement one of these returns a 404 for the matching URL. ## Custom error page (404) Bloggrify ships a default error page. It handles 404s (an unknown URL, or a page a theme chose not to implement) and any other error, without leaking internals to the visitor. A theme can replace it with its own by adding: - `app/layouts/themes/MYTHEME/error.vue` If the theme does not provide one, the framework default is used. The layout receives the `error` object as a prop. The `useErrorPage()` composable derives everything you render from it, so the theme only writes markup: ```vue ``` `statusCode` and `title`/`description` are the visitor-safe values (the raw error message is never exposed in production). `isNotFound` is `true` for a 404, so you can word a missing-page case differently from a server error. `handleError` clears the error and returns to the home page. # Redirections When a page moves, you can keep its old url working and send visitors to the new one. Two frontmatter keys declare a redirection: - `redirect_to_full_url`: the complete destination url. A path starting with `/` is also accepted, for a page that moved inside the same blog. - `redirect_to_domain`: another domain, keeping the current path. `example.com` and `https://example.com` both work, and a missing protocol is assumed to be `https`. Example: ```yaml --- title: My old page listed: false redirect_to_full_url: "https://www.example.com/2024/01/my-new-page" --- ``` The markdown file has to stay at the old path: it is what makes the old url exist. Setting `listed: false` keeps that leftover file out of the listings, the RSS feed and the related posts, while the url stays reachable and gets generated. ## What Bloggrify generates For a page that declares a redirection, the generated html contains: - a ``, which redirects immediately and works with JavaScript disabled, - a `location.replace()` on the destination, which covers navigation from inside the blog and leaves no entry in the browser history, so the Back button does not come back to the redirecting page, - a `` pointing at the destination, not at the page itself, - a `noindex, follow` robots directive. The page is also left out of the sitemap, and no article metadata (`og:` tags, schema.org, generated social image) is emitted for it. ## This is not an HTTP 301 Bloggrify only generates static files, so there is no server to answer with a real `301`. An instant meta refresh is the closest portable equivalent, and Google documents it as a permanent redirect. If your host lets you declare redirect rules (`_redirects` on Netlify and Cloudflare Pages, a `redirects` block on Vercel, a rewrite rule on nginx), those give a real `301` and are worth using for large batches of moved urls. The frontmatter keys remain the option that travels with the content and works everywhere. # Social Sharing The social sharing feature allows you to share your blog posts on social networks. It uses the [Nuxt-Social-Share](https://github.com/stefanobartoletti/nuxt-social-share){rel=""nofollow""} under the hood The list of social networks to display in the "share" section can be configured in the `app.config.ts` file. ```typescript sharing: { networks: [ 'bluesky', 'linkedin', 'facebook', 'reddit', 'whatsapp', 'telegram', 'email' ] } ``` ::note The older `socials.sharing_networks` key is deprecated but still honoured (with a build-time warning). Use `sharing.networks` instead. :: ::note Each theme can override the list of social networks to display in the "share" section or even disable it. :: # Change the theme To start a new blog with Bloggrify, **you can start from a [theme](https://bloggrify.com/reference/theming)** to start with. By default, if you follow [the installation guide](https://bloggrify.com/introduction/installation), you will start with the "Minimalist" theme. You can choose to install another theme like "Mistral", "Bento", or "Epoxia", or you can also look at the [other templates available](https://bloggrify.com/templates). 1. Once you have chosen a template, you have to include it in your project. Example with the Mistral theme: ::code-group ```bash [npm] npm install @bloggrify/mistral ``` ```bash [yarn] yarn add @bloggrify/mistral ``` ```bash [pnpm] pnpm add @bloggrify/mistral ``` :: Then you have to explicitly say to Nuxt that you are using Bloggrify as an extended module. You can do this by adding the following line in your `nuxt.config.js` file: ```json extends: [ '@bloggrify/core', '@bloggrify/mistral', ], ``` 2. Next, you have to specify the theme in the `app/app.config.ts` file: ```json theme: 'mistral', ``` 3. Run the `dev` command to start Bloggrify in development mode: ::code-group ```bash [npm] npm run dev ``` ```bash [yarn] yarn dev ``` ```bash [pnpm] pnpm run dev ``` :: ::tip ✨ Well done! A browser window should automatically open for {rel=""nofollow""} :: ## Advanced method, start from scratch Alternatively, you can start from scratch and create your own theme. To do so, follow the [theme creation guide](https://bloggrify.com/recipes/theme-recipe) to create your own theme. # Create a new theme from scratch In this recipe, we will create a new [theme](https://bloggrify.com/reference/theming) from scratch. We will (re)create the theme called "Minimalist" that is included in the Bloggrify core repository. ## Getting Started First, you need to create a new Nuxt application. You can do this by running the following command: ```bash npx nuxi@latest init neo-minimalist ``` Then, you need to install the dependencies: ```bash npm install @bloggrify/core npm install -D sass-embedded ``` Then you have to explicitly say to Nuxt that you are using Bloggrify as an extended module. You can do this by adding the following line in your `nuxt.config.js` file: ```typescript extends: [ '@bloggrify/core', ], ``` ## Create a basic configuration file Create a new file called `app.config.ts` in the `app` folder of your project. This file will contain the configuration of your theme. This file will override the default configuration of Bloggrify. If you don't specify a configuration, the default configuration will be used. Here is an example of a basic configuration file: ```typescript export default defineAppConfig({ url: 'https://neo-minimalist.bloggrify.com/', theme: 'neo-minimalist', name: 'Neo-minimalist Demo', description: 'A minimalist theme for Bloggrify', }) ``` That's probably not enough for a complete theme, but it's a good start. You can add more configuration options later. Don't forget to read the [official documentation](https://bloggrify.com/introduction/configuration){rel=""nofollow""} to know more about the configuration options. ## Create the layout for the home page Create a new folder called `app/layouts/themes/neo-minimalist` in your project. This folder will contain the layouts of your theme. Create a new file called `home.vue` in the `app/layouts/themes/neo-minimalist` folder. This file will be the layout for the home page. Here is an example of a basic layout file: ```vue ``` Let's explain the code above: - The `