[{"data":1,"prerenderedAt":2618},["ShallowReactive",2],{"\u002F":3,"content-listing":29,"navigation":2567},{"id":4,"title":5,"alternates":6,"author":6,"body":7,"categories":6,"comments":14,"cover":6,"date":6,"description":15,"draft":14,"extension":16,"hidden":14,"language":6,"layout":17,"links":6,"listed":14,"meta":18,"navigation":19,"nocomments":14,"notoc":14,"pageid":20,"path":21,"readingTime":22,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":25,"sitemap":26,"stem":27,"table_of_contents":14,"tags":6,"__hash__":28},"page\u002Findex.md","Bloggrify",null,{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],false,"Official Bloggrify blog.","md","home",{},true,"10","\u002F",{"text":23,"minutes":24,"time":24,"words":24},"0 min read",0,{"title":5,"description":15},{"loc":21},"index","0zL1MUDTFiW4Of6GvpTIP36zskIjAGPSfS8Bzi3uuyU",[30,967,1231,1817,2194,2290],{"id":31,"title":32,"alternates":6,"author":33,"body":34,"categories":6,"comments":14,"cover":6,"date":950,"description":951,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":952,"navigation":19,"nocomments":14,"notoc":14,"pageid":953,"path":954,"readingTime":955,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":960,"sitemap":961,"stem":962,"table_of_contents":19,"tags":963,"__hash__":966},"page\u002Fhost-your-static-blog-on-codeberg.md","Hosting your static blog on Codeberg Pages","hlassiege",{"type":8,"value":35,"toc":941},[36,48,51,59,64,67,70,73,98,109,113,116,127,135,143,149,158,165,263,270,310,316,320,330,333,399,402,422,425,503,506,510,513,523,709,723,732,748,754,758,765,768,806,820,833,843,846,859,863,866,876,882,900,906,909,913,916,937],[37,38,39,40,47],"p",{},"Codeberg comes up in most conversations about leaving GitHub. It is run by Codeberg e.V., a non profit association registered in Berlin, it runs on ",[41,42,46],"a",{"href":43,"rel":44},"https:\u002F\u002Fforgejo.org\u002F",[45],"nofollow","Forgejo"," (a soft fork of Gitea), it is funded by memberships and donations rather than venture capital, and it is hosted in the EU. For a personal blog, those reasons are usually enough to make you look.",[37,49,50],{},"The practical question is whether you can actually host the blog there, not just the repository. You can. Codeberg Pages serves static files over HTTPS, supports custom domains, and asks nothing in return.",[37,52,53,54,58],{},"This post covers the whole path: how Pages works, how to lay out the repository, the first deploy, how to automate it, the custom domain, and what you give up compared to GitHub Pages or Netlify. The examples use ",[41,55,5],{"href":56,"rel":57},"https:\u002F\u002Fbloggrify.com",[45],", a static blog generator built on Nuxt, but the deployment part is the same for Hugo, Astro or Jekyll: only the build command and the output directory change.",[60,61,63],"h2",{"id":62},"what-codeberg-pages-does-and-what-it-does-not","What Codeberg Pages does, and what it does not",[37,65,66],{},"Codeberg Pages does one thing: it serves the files it finds in a git branch. There is no build step on their side.",[37,68,69],{},"That is the main difference with what you may be used to. GitHub Pages ships with Jekyll built in. Netlify, Vercel and Cloudflare Pages run your build command for you. Codeberg does not. The build happens somewhere else, on your machine or in a CI job, and Pages only publishes the result.",[37,71,72],{},"For a Bloggrify blog that constraint is not a problem, because the output is already a plain folder:",[74,75,79],"pre",{"className":76,"code":77,"language":78,"meta":11,"style":11},"language-bash shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","npm run generate\n","bash",[80,81,82],"code",{"__ignoreMap":11},[83,84,87,91,95],"span",{"class":85,"line":86},"line",1,[83,88,90],{"class":89},"sY6Vb","npm",[83,92,94],{"class":93},"sk5mZ"," run",[83,96,97],{"class":93}," generate\n",[37,99,100,101,104,105,108],{},"This writes the whole site to ",[80,102,103],{},".output\u002Fpublic"," (Nuxt also links it as ",[80,106,107],{},"dist","), as HTML, CSS, JS and images. No server, no runtime, nothing to install on the host. That folder is what Codeberg will serve.",[60,110,112],{"id":111},"choose-your-url-shape-first","Choose your URL shape first",[37,114,115],{},"Two layouts are possible, and the choice affects your Bloggrify configuration, so make it before you push anything.",[37,117,118,126],{},[119,120,121,122,125],"strong",{},"A repository named ",[80,123,124],{},"pages","."," The site is served at the root of your subdomain:",[74,128,133],{"className":129,"code":131,"language":132},[130],"language-text","https:\u002F\u002Fyourname.codeberg.page\u002F\n","text",[80,134,131],{"__ignoreMap":11},[37,136,137,142],{},[119,138,139,140,125],{},"Any other repository, with a branch named ",[80,141,124],{}," The site is served under a path:",[74,144,147],{"className":145,"code":146,"language":132},[130],"https:\u002F\u002Fyourname.codeberg.page\u002Fmyblog\u002F\n",[80,148,146],{"__ignoreMap":11},[37,150,151,152,154,155,157],{},"In both cases the published files live on a branch named ",[80,153,124],{},". In a repository named ",[80,156,124],{},", you can simply make it the default branch.",[37,159,160,161,164],{},"If you go with the second option, your blog lives in a subfolder, and a static site needs to know that when it generates links, assets and canonical URLs. In Bloggrify that means two settings. First the base path, in ",[80,162,163],{},"nuxt.config.ts",":",[74,166,170],{"className":167,"code":168,"filename":163,"language":169,"meta":11,"style":11},"language-typescript shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","export default defineNuxtConfig({\n  extends: ['@bloggrify\u002Fcore'],\n  app: {\n    baseURL: '\u002Fmyblog\u002F',\n  },\n})\n","typescript",[80,171,172,193,219,230,248,254],{"__ignoreMap":11},[83,173,174,178,181,185,189],{"class":85,"line":86},[83,175,177],{"class":176},"sTQaL","export",[83,179,180],{"class":176}," default",[83,182,184],{"class":183},"s2H7O"," defineNuxtConfig",[83,186,188],{"class":187},"sHr2s","(",[83,190,192],{"class":191},"s9dvb","{\n",[83,194,195,199,201,204,208,211,213,216],{"class":85,"line":12},[83,196,198],{"class":197},"s3cY9","  extends",[83,200,164],{"class":191},[83,202,203],{"class":187}," [",[83,205,207],{"class":206},"s03E9","'",[83,209,210],{"class":93},"@bloggrify\u002Fcore",[83,212,207],{"class":206},[83,214,215],{"class":187},"]",[83,217,218],{"class":191},",\n",[83,220,222,225,227],{"class":85,"line":221},3,[83,223,224],{"class":197},"  app",[83,226,164],{"class":191},[83,228,229],{"class":191}," {\n",[83,231,233,236,238,241,244,246],{"class":85,"line":232},4,[83,234,235],{"class":197},"    baseURL",[83,237,164],{"class":191},[83,239,240],{"class":206}," '",[83,242,243],{"class":93},"\u002Fmyblog\u002F",[83,245,207],{"class":206},[83,247,218],{"class":191},[83,249,251],{"class":85,"line":250},5,[83,252,253],{"class":191},"  },\n",[83,255,257,260],{"class":85,"line":256},6,[83,258,259],{"class":191},"}",[83,261,262],{"class":187},")\n",[37,264,265,266,269],{},"Then the public URL, in ",[80,267,268],{},"app.config.ts",", which feeds the sitemap, the RSS feed, the canonical tags and the OG images:",[74,271,273],{"className":167,"code":272,"filename":268,"language":169,"meta":11,"style":11},"export default defineAppConfig({\n  url: 'https:\u002F\u002Fyourname.codeberg.page\u002Fmyblog',\n})\n",[80,274,275,288,304],{"__ignoreMap":11},[83,276,277,279,281,284,286],{"class":85,"line":86},[83,278,177],{"class":176},[83,280,180],{"class":176},[83,282,283],{"class":183}," defineAppConfig",[83,285,188],{"class":187},[83,287,192],{"class":191},[83,289,290,293,295,297,300,302],{"class":85,"line":12},[83,291,292],{"class":197},"  url",[83,294,164],{"class":191},[83,296,240],{"class":206},[83,298,299],{"class":93},"https:\u002F\u002Fyourname.codeberg.page\u002Fmyblog",[83,301,207],{"class":206},[83,303,218],{"class":191},[83,305,306,308],{"class":85,"line":221},[83,307,259],{"class":191},[83,309,262],{"class":187},[37,311,312,313,315],{},"Getting this wrong is the most common way to end up with a site that loads a blank page and 404s on every asset. If you plan to put a custom domain on the blog anyway, take the ",[80,314,124],{}," repository option and skip the subfolder entirely.",[60,317,319],{"id":318},"the-first-deploy-by-hand","The first deploy, by hand",[37,321,322,323,326,327,329],{},"The sources stay on ",[80,324,325],{},"main",", the build goes to ",[80,328,124],{},". Keeping the two apart is easier with a worktree, so you never have to switch branches in the middle of a build.",[37,331,332],{},"Create the branch once, with no shared history:",[74,334,336],{"className":76,"code":335,"language":78,"meta":11,"style":11},"git switch --orphan pages\ngit commit --allow-empty -m \"init pages\"\ngit push -u origin pages\ngit switch main\n",[80,337,338,353,375,390],{"__ignoreMap":11},[83,339,340,343,346,350],{"class":85,"line":86},[83,341,342],{"class":89},"git",[83,344,345],{"class":93}," switch",[83,347,349],{"class":348},"sH-Ap"," --orphan",[83,351,352],{"class":93}," pages\n",[83,354,355,357,360,363,366,369,372],{"class":85,"line":12},[83,356,342],{"class":89},[83,358,359],{"class":93}," commit",[83,361,362],{"class":348}," --allow-empty",[83,364,365],{"class":348}," -m",[83,367,368],{"class":206}," \"",[83,370,371],{"class":93},"init pages",[83,373,374],{"class":206},"\"\n",[83,376,377,379,382,385,388],{"class":85,"line":221},[83,378,342],{"class":89},[83,380,381],{"class":93}," push",[83,383,384],{"class":348}," -u",[83,386,387],{"class":93}," origin",[83,389,352],{"class":93},[83,391,392,394,396],{"class":85,"line":232},[83,393,342],{"class":89},[83,395,345],{"class":93},[83,397,398],{"class":93}," main\n",[37,400,401],{},"Then attach it as a worktree next to your project:",[74,403,405],{"className":76,"code":404,"language":78,"meta":11,"style":11},"git worktree add ..\u002Fmyblog-pages pages\n",[80,406,407],{"__ignoreMap":11},[83,408,409,411,414,417,420],{"class":85,"line":86},[83,410,342],{"class":89},[83,412,413],{"class":93}," worktree",[83,415,416],{"class":93}," add",[83,418,419],{"class":93}," ..\u002Fmyblog-pages",[83,421,352],{"class":93},[37,423,424],{},"From now on, publishing is three commands:",[74,426,428],{"className":76,"code":427,"language":78,"meta":11,"style":11},"npm run generate\nrsync -a --delete --exclude .git .output\u002Fpublic\u002F ..\u002Fmyblog-pages\u002F\ncd ..\u002Fmyblog-pages && git add -A && git commit -m \"deploy\" && git push\n",[80,429,430,438,461],{"__ignoreMap":11},[83,431,432,434,436],{"class":85,"line":86},[83,433,90],{"class":89},[83,435,94],{"class":93},[83,437,97],{"class":93},[83,439,440,443,446,449,452,455,458],{"class":85,"line":12},[83,441,442],{"class":89},"rsync",[83,444,445],{"class":348}," -a",[83,447,448],{"class":348}," --delete",[83,450,451],{"class":348}," --exclude",[83,453,454],{"class":93}," .git",[83,456,457],{"class":93}," .output\u002Fpublic\u002F",[83,459,460],{"class":93}," ..\u002Fmyblog-pages\u002F\n",[83,462,463,467,469,472,475,477,480,482,484,486,488,490,493,496,498,500],{"class":85,"line":221},[83,464,466],{"class":465},"seg6N","cd",[83,468,419],{"class":93},[83,470,471],{"class":191}," &&",[83,473,474],{"class":89}," git",[83,476,416],{"class":93},[83,478,479],{"class":348}," -A",[83,481,471],{"class":191},[83,483,474],{"class":89},[83,485,359],{"class":93},[83,487,365],{"class":348},[83,489,368],{"class":206},[83,491,492],{"class":93},"deploy",[83,494,495],{"class":206},"\"",[83,497,471],{"class":191},[83,499,474],{"class":89},[83,501,502],{"class":93}," push\n",[37,504,505],{},"Wait a few seconds and the site is live. This is the whole mechanism, and it is worth doing manually once even if you intend to automate it, because everything that follows is just a machine running these same commands.",[60,507,509],{"id":508},"automating-it","Automating it",[37,511,512],{},"Here you need to know how Codeberg works, and it is different from GitHub. CI on Codeberg is a shared resource paid for by donations, so it is not handed out by default.",[37,514,515,518,519,522],{},[119,516,517],{},"Forgejo Actions."," The syntax is close to GitHub Actions, and there is an official action to publish to Pages. A workflow in ",[80,520,521],{},".forgejo\u002Fworkflows\u002Fdeploy.yml"," looks like this:",[74,524,528],{"className":525,"code":526,"filename":521,"language":527,"meta":11,"style":11},"language-yaml shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","on:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    runs-on: docker\n    container:\n      image: node:22\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - run: npm ci\n      - run: npm run generate\n      - uses: https:\u002F\u002Fcodeberg.org\u002Fgit-pages\u002Faction@v2\n        with:\n          site: https:\u002F\u002Fyourname.codeberg.page\u002F\n          token: ${{ forge.token }}\n          source: .output\u002Fpublic\u002F\n","yaml",[80,529,530,539,547,561,566,573,580,591,599,610,618,632,644,656,668,676,687,698],{"__ignoreMap":11},[83,531,532,536],{"class":85,"line":86},[83,533,535],{"class":534},"s0wY3","on",[83,537,538],{"class":191},":\n",[83,540,541,545],{"class":85,"line":12},[83,542,544],{"class":543},"si4D-","  push",[83,546,538],{"class":191},[83,548,549,552,554,556,558],{"class":85,"line":221},[83,550,551],{"class":543},"    branches",[83,553,164],{"class":191},[83,555,203],{"class":191},[83,557,325],{"class":93},[83,559,560],{"class":191},"]\n",[83,562,563],{"class":85,"line":232},[83,564,565],{"emptyLinePlaceholder":19},"\n",[83,567,568,571],{"class":85,"line":250},[83,569,570],{"class":543},"jobs",[83,572,538],{"class":191},[83,574,575,578],{"class":85,"line":256},[83,576,577],{"class":543},"  deploy",[83,579,538],{"class":191},[83,581,583,586,588],{"class":85,"line":582},7,[83,584,585],{"class":543},"    runs-on",[83,587,164],{"class":191},[83,589,590],{"class":93}," docker\n",[83,592,594,597],{"class":85,"line":593},8,[83,595,596],{"class":543},"    container",[83,598,538],{"class":191},[83,600,602,605,607],{"class":85,"line":601},9,[83,603,604],{"class":543},"      image",[83,606,164],{"class":191},[83,608,609],{"class":93}," node:22\n",[83,611,613,616],{"class":85,"line":612},10,[83,614,615],{"class":543},"    steps",[83,617,538],{"class":191},[83,619,621,624,627,629],{"class":85,"line":620},11,[83,622,623],{"class":191},"      -",[83,625,626],{"class":543}," uses",[83,628,164],{"class":191},[83,630,631],{"class":93}," actions\u002Fcheckout@v4\n",[83,633,635,637,639,641],{"class":85,"line":634},12,[83,636,623],{"class":191},[83,638,94],{"class":543},[83,640,164],{"class":191},[83,642,643],{"class":93}," npm ci\n",[83,645,647,649,651,653],{"class":85,"line":646},13,[83,648,623],{"class":191},[83,650,94],{"class":543},[83,652,164],{"class":191},[83,654,655],{"class":93}," npm run generate\n",[83,657,659,661,663,665],{"class":85,"line":658},14,[83,660,623],{"class":191},[83,662,626],{"class":543},[83,664,164],{"class":191},[83,666,667],{"class":93}," https:\u002F\u002Fcodeberg.org\u002Fgit-pages\u002Faction@v2\n",[83,669,671,674],{"class":85,"line":670},15,[83,672,673],{"class":543},"        with",[83,675,538],{"class":191},[83,677,679,682,684],{"class":85,"line":678},16,[83,680,681],{"class":543},"          site",[83,683,164],{"class":191},[83,685,686],{"class":93}," https:\u002F\u002Fyourname.codeberg.page\u002F\n",[83,688,690,693,695],{"class":85,"line":689},17,[83,691,692],{"class":543},"          token",[83,694,164],{"class":191},[83,696,697],{"class":93}," ${{ forge.token }}\n",[83,699,701,704,706],{"class":85,"line":700},18,[83,702,703],{"class":543},"          source",[83,705,164],{"class":191},[83,707,708],{"class":93}," .output\u002Fpublic\u002F\n",[37,710,711,712,715,716,718,719,722],{},"The action commits the contents of ",[80,713,714],{},"source"," to the ",[80,717,124],{}," branch and handles the token for you. Adjust ",[80,720,721],{},"runs-on"," to the labels your runner actually exposes.",[37,724,725,726,731],{},"The catch: hosted Actions on Codeberg are still limited, for security and maintenance reasons. Check ",[41,727,730],{"href":728,"rel":729},"https:\u002F\u002Fcodeberg.org\u002Factions\u002Fmeta",[45],"the current status"," before you count on them. You can always attach your own runner, which does not need a public IP.",[37,733,734,737,738,743,744,747],{},[119,735,736],{},"Woodpecker CI."," Codeberg hosts a Woodpecker instance at ",[41,739,742],{"href":740,"rel":741},"https:\u002F\u002Fci.codeberg.org",[45],"ci.codeberg.org",". Access is granted on request, through a form, project by project, and only ",[80,745,746],{},"linux\u002Famd64"," is available. It works well once you are in, but it is not something you enable in a settings toggle.",[37,749,750,753],{},[119,751,752],{},"Your machine."," Which leaves the option people underestimate: keep the three commands above in a script, or a git hook, and run them when you publish. A static blog is not a service, it does not need a build server to stay up. Deploying from your laptop is a legitimate answer, and it is the fastest way to get the blog online today while you sort out CI access.",[60,755,757],{"id":756},"a-custom-domain","A custom domain",[37,759,760,761,764],{},"You do not want your blog to live on ",[80,762,763],{},"codeberg.page"," forever. Two DNS records are needed.",[37,766,767],{},"The first points the domain at Codeberg. On a subdomain, a CNAME:",[769,770,771,787],"table",{},[772,773,774],"thead",{},[775,776,777,781,784],"tr",{},[778,779,780],"th",{},"Domain",[778,782,783],{},"Type",[778,785,786],{},"Value",[788,789,790],"tbody",{},[775,791,792,798,801],{},[793,794,795],"td",{},[80,796,797],{},"www.example.org",[793,799,800],{},"CNAME",[793,802,803],{},[80,804,805],{},"codeberg.page.",[37,807,808,809,811,812,815,816,819],{},"On an apex domain, where CNAME is not allowed, use an ALIAS or ANAME record to ",[80,810,763],{}," if your DNS provider supports it. If it does not, or if you use DNSSEC, fall back to the addresses documented by Codeberg (",[80,813,814],{},"217.197.84.141"," and ",[80,817,818],{},"2a0a:4580:103f:c0de::2","), keeping in mind that hardcoded IPs can change.",[37,821,822,823,826,827,832],{},"The second record proves that you own the domain and authorises a specific repository to publish on it. It is a TXT record on a ",[80,824,825],{},"_git-pages"," subdomain, and its exact name depends on whether you deploy through the webhook or through Forgejo Actions. The ",[41,828,831],{"href":829,"rel":830},"https:\u002F\u002Fdocs.codeberg.org\u002Fcodeberg-pages\u002Fusing-custom-domain\u002F",[45],"custom domain documentation"," gives the current value for both cases, and it is one of the pages that has changed recently, so read it rather than an old tutorial.",[37,834,835,836,839,840,842],{},"Which brings up a detail worth knowing. Most guides you will find, including good ones, tell you to add a ",[80,837,838],{},".domains"," file at the root of your repository, the equivalent of GitHub's ",[80,841,800],{}," file. The current Pages server no longer needs it. If you are migrating an old site, you can delete it.",[37,844,845],{},"Certificates are issued automatically through Let's Encrypt, so HTTPS works without you doing anything. One exception: if your domain has CAA records, you have to allow Let's Encrypt explicitly, otherwise the issuance fails silently and you end up staring at a certificate error.",[37,847,848,849,852,853,855,856,858],{},"Finally, update ",[80,850,851],{},"url"," in ",[80,854,268],{}," to the real domain, and rebuild. Otherwise your sitemap, your RSS feed and your social preview images keep advertising the ",[80,857,763],{}," address.",[60,860,862],{"id":861},"what-you-are-trading-away","What you are trading away",[37,864,865],{},"An honest comparison, because Codeberg is not trying to be Netlify.",[37,867,868,871,872,875],{},[119,869,870],{},"No preview deploys."," No per branch URL, no preview on a pull request. You have your local ",[80,873,874],{},"npm run dev",", and production.",[37,877,878,881],{},[119,879,880],{},"No global CDN."," Pages is served from Codeberg's infrastructure in Europe, not from a hundred edge locations. For a text blog whose pages are a few dozen kilobytes, this is mostly invisible. If your readers are in Europe, it is arguably an advantage, and it keeps the whole stack under EU jurisdiction.",[37,883,884,887,888,893,894,899],{},[119,885,886],{},"Availability is not a commercial SLA."," Codeberg has been the target of repeated DDoS waves, and Pages has taken collateral damage. Nicolas Grisey Demengel ",[41,889,892],{"href":890,"rel":891},"https:\u002F\u002Fn.gridem.fr\u002F2025\u002F02\u002F19\u002Fpasser-de-github-a-codeberg\u002F",[45],"measured about 80% availability"," on his site during one of these episodes, and Jean-Rémy Praud ended up ",[41,895,898],{"href":896,"rel":897},"https:\u002F\u002Fwww.jeanremypraud.com\u002Fnotes\u002Fquitter-github-codeberg-infomaniak\u002F",[45],"keeping the sources on Codeberg while serving the site from Infomaniak",". That split is a reasonable pattern, and it is available to you precisely because the output is a folder of static files: the same build can be pushed to a second host without changing a line of the blog.",[37,901,902,905],{},[119,903,904],{},"CI is requested, not given."," See above. Plan for the manual deploy first.",[37,907,908],{},"None of this disqualifies Codeberg for a personal blog. It does mean you should pick it for what it is, a community funded forge with a simple static host attached, rather than expecting a free replacement for a commercial platform.",[60,910,912],{"id":911},"summary","Summary",[37,914,915],{},"Codeberg Pages serves a git branch. Bloggrify produces a folder of static files. The connection between them is one rsync and one push, and everything else in this post is polish on top of that: the URL shape, the automation, the domain.",[37,917,918,919,924,925,930,931,936],{},"If you are starting from scratch, the ",[41,920,923],{"href":921,"rel":922},"https:\u002F\u002Fbloggrify.com\u002Fintroduction\u002Finstallation",[45],"installation guide"," gets you a blog running locally in a few minutes, and the ",[41,926,929],{"href":927,"rel":928},"https:\u002F\u002Fbloggrify.com\u002Fintroduction\u002Fdeployment",[45],"deployment page"," covers the other hosts, since nothing here locks you into one. Bloggrify itself is open source, ",[41,932,935],{"href":933,"rel":934},"https:\u002F\u002Fgithub.com\u002Fbloggrify\u002Fbloggrify",[45],"on GitHub"," for now, which is an irony we are aware of.",[938,939,940],"style",{},"html pre.shiki code .sY6Vb, html code.shiki .sY6Vb{--shiki-light:#E2931D;--shiki-default:#E2931D;--shiki-dark:#B392F0}html pre.shiki code .sk5mZ, html code.shiki .sk5mZ{--shiki-light:#91B859;--shiki-default:#91B859;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTQaL, html code.shiki .sTQaL{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#39ADB5;--shiki-default-font-style:italic;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .s2H7O, html code.shiki .s2H7O{--shiki-light:#6182B8;--shiki-default:#6182B8;--shiki-dark:#B392F0}html pre.shiki code .sHr2s, html code.shiki .sHr2s{--shiki-light:#90A4AE;--shiki-default:#90A4AE;--shiki-dark:#E1E4E8}html pre.shiki code .s9dvb, html code.shiki .s9dvb{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#E1E4E8}html pre.shiki code .s3cY9, html code.shiki .s3cY9{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#E1E4E8}html pre.shiki code .s03E9, html code.shiki .s03E9{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#9ECBFF}html pre.shiki code .sH-Ap, html code.shiki .sH-Ap{--shiki-light:#91B859;--shiki-default:#91B859;--shiki-dark:#79B8FF}html pre.shiki code .seg6N, html code.shiki .seg6N{--shiki-light:#6182B8;--shiki-default:#6182B8;--shiki-dark:#79B8FF}html pre.shiki code .s0wY3, html code.shiki .s0wY3{--shiki-light:#FF5370;--shiki-default:#FF5370;--shiki-dark:#79B8FF}html pre.shiki code .si4D-, html code.shiki .si4D-{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#85E89D}",{"title":11,"searchDepth":12,"depth":12,"links":942},[943,944,945,946,947,948,949],{"id":62,"depth":12,"text":63},{"id":111,"depth":12,"text":112},{"id":318,"depth":12,"text":319},{"id":508,"depth":12,"text":509},{"id":756,"depth":12,"text":757},{"id":861,"depth":12,"text":862},{"id":911,"depth":12,"text":912},"2026-07-27","Codeberg Pages serves static files and runs no build of its own. Repository layout, first deploy, automation, custom domain, and what you trade away compared to GitHub.",{},"16","\u002Fhost-your-static-blog-on-codeberg",{"text":956,"minutes":957,"time":958,"words":959},"8 min read",7.31,438600,1462,{"title":32,"description":951},{"loc":954},"host-your-static-blog-on-codeberg",[964,965],"hosting","deployment","_IJsRY2Gzt2aM0PsVn3ueXTuPjb-Lbdy3rfHbJBHej8",{"id":968,"title":969,"alternates":6,"author":33,"body":970,"categories":6,"comments":14,"cover":6,"date":1215,"description":1216,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":1217,"navigation":19,"nocomments":14,"notoc":14,"pageid":1218,"path":1219,"readingTime":1220,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":1225,"sitemap":1226,"stem":1227,"table_of_contents":19,"tags":1228,"__hash__":1230},"page\u002F2026\u002Fversion-3-2.md","Announcing Bloggrify 3.2: redesigned minimalist theme, AI crawler controls, and faster pages",{"type":8,"value":971,"toc":1204},[972,975,979,982,1008,1011,1015,1026,1033,1037,1040,1054,1061,1065,1072,1076,1079,1091,1095,1098,1122,1129,1133,1163,1167,1182,1186,1193],[37,973,974],{},"Bloggrify 3.2.0 is out. This release redesigns the default minimalist theme, adds a set of SEO controls aimed at AI crawlers, makes pages lighter for the average reader, and fixes a number of correctness issues around drafts, authors and error pages.",[60,976,978],{"id":977},"a-redesigned-minimalist-theme","A redesigned minimalist theme",[37,980,981],{},"The minimalist theme, the one Bloggrify ships with, has been rebuilt after the Nuxt UI blog template. It is no longer just a list of posts:",[983,984,985,997,1002],"ul",{},[986,987,988,989,992,993,996],"li",{},"a ",[119,990,991],{},"table of contents"," on posts, which you can hide per post with the ",[80,994,995],{},"notoc"," frontmatter",[986,998,988,999],{},[119,1000,1001],{},"newsletter signup form",[986,1003,1004,1007],{},[119,1005,1006],{},"social share buttons"," on articles",[37,1009,1010],{},"The redesign leans on Nuxt UI components throughout, so it stays consistent with the rest of the stack and inherits its dark mode.",[60,1012,1014],{"id":1013},"controls-for-ai-crawlers-and-llmstxt","Controls for AI crawlers and llms.txt",[37,1016,1017,1018,1021,1022,1025],{},"A new ",[80,1019,1020],{},"seo"," config key lets you decide how AI tools interact with your blog, without touching your search engine indexing. Blocking the major AI crawlers is one line, and publishing an ",[80,1023,1024],{},"llms.txt"," index is another. Both default to leaving your blog exactly as it is today.",[37,1027,1028,1029,125],{},"We wrote a full post on what these switches do, what they actually protect, and whether they are worth turning on: ",[41,1030,1032],{"href":1031},"\u002F2026\u002Fai-crawlers-and-llms-txt","Should you block AI crawlers on your blog?",[60,1034,1036],{"id":1035},"lighter-pages-by-default","Lighter pages by default",[37,1038,1039],{},"Two changes move cost off the readers who were paying for features they did not use:",[983,1041,1042,1048],{},[986,1043,1044,1047],{},[119,1045,1046],{},"Article images are served as WebP",", which are smaller than the equivalent JPEG or PNG.",[986,1049,1050,1053],{},[119,1051,1052],{},"The search index loads on first search intent only."," Until now every page quietly downloaded the search engine and content database, even for readers who never searched. It now loads when a reader hovers the search button or opens it with ⌘K.",[37,1055,1056,1057,125],{},"The search change turned out to be more subtle than flipping a flag, so it also has its own write-up: ",[41,1058,1060],{"href":1059},"\u002F2026\u002Floading-search-only-when-readers-need-it","The search box that downloaded itself on every page",[60,1062,1064],{"id":1063},"a-new-comment-provider-hakanai-connect","A new comment provider: Hakanai Connect",[37,1066,1067,1068,1071],{},"Bloggrify gains ",[119,1069,1070],{},"Hakanai Connect"," as an alternative to Hyvor Talk for comments. It is now the provider running on the official Bloggrify blog. Hyvor remains supported, so switching is a config choice, not a migration you are forced into.",[60,1073,1075],{"id":1074},"more-analytics-providers","More analytics providers",[37,1077,1078],{},"Two providers join the existing options, and analytics config is now fully optional:",[983,1080,1081,1086],{},[986,1082,1083],{},[119,1084,1085],{},"OpenPanel",[986,1087,1088],{},[119,1089,1090],{},"Hakanai Pulse",[60,1092,1094],{"id":1093},"authors-and-error-pages","Authors and error pages",[37,1096,1097],{},"Author handling gets more predictable:",[983,1099,1100,1106,1112,1119],{},[986,1101,1102,1103],{},"an ",[119,1104,1105],{},"opt-in authors directory page",[986,1107,1108,1109],{},"the post author is now part of the ",[119,1110,1111],{},"SEO metadata",[986,1113,1114,1115,1118],{},"Bloggrify ",[119,1116,1117],{},"warns in dev"," when a post references an author you never defined",[986,1120,1121],{},"posts without an explicit author show up on the default author page instead of disappearing",[37,1123,1124,1125,1128],{},"Themes can now ship ",[119,1126,1127],{},"their own error pages",", with a per-theme override and a sensible fallback when a theme does not provide one.",[60,1130,1132],{"id":1131},"correctness-fixes-worth-calling-out","Correctness fixes worth calling out",[983,1134,1135,1141,1147,1154],{},[986,1136,1137,1138],{},"drafts and unlisted pages ",[119,1139,1140],{},"no longer leak into the RSS feed",[986,1142,1143,1144],{},"unknown URLs and pages with no matching layout return a proper ",[119,1145,1146],{},"404 instead of a 500",[986,1148,1149,1150,1153],{},"previous\u002Fnext article navigation is now ordered ",[119,1151,1152],{},"by date"," rather than by file path",[986,1155,1156,815,1159,1162],{},[80,1157,1158],{},"draft",[80,1160,1161],{},"listed"," frontmatter behave as the documentation describes",[60,1164,1166],{"id":1165},"under-the-hood","Under the hood",[37,1168,1169,1170,1173,1174,1177,1178,1181],{},"Bloggrify 3.2 moves to ",[119,1171,1172],{},"Nuxt 4.4.8"," and upgrades the Nuxt SEO modules (sitemap v8, robots v6, schema-org v6). Lucide icons are now bundled locally with ",[80,1175,1176],{},"@iconify-json\u002Flucide",", and ",[80,1179,1180],{},"nuxt-og-image"," was upgraded to v6 to restore OG image generation.",[60,1183,1185],{"id":1184},"contribute","Contribute",[37,1187,1188,1189,164],{},"Bloggrify is open source. ",[41,1190,1192],{"href":933,"rel":1191},[45],"Help us on GitHub",[983,1194,1195,1198,1201],{},[986,1196,1197],{},"Report issues",[986,1199,1200],{},"Submit PRs",[986,1202,1203],{},"Join discussions",{"title":11,"searchDepth":12,"depth":12,"links":1205},[1206,1207,1208,1209,1210,1211,1212,1213,1214],{"id":977,"depth":12,"text":978},{"id":1013,"depth":12,"text":1014},{"id":1035,"depth":12,"text":1036},{"id":1063,"depth":12,"text":1064},{"id":1074,"depth":12,"text":1075},{"id":1093,"depth":12,"text":1094},{"id":1131,"depth":12,"text":1132},{"id":1165,"depth":12,"text":1166},{"id":1184,"depth":12,"text":1185},"2026-07-18","The default theme gets a full redesign with table of contents, newsletter and share buttons, alongside new SEO controls for AI crawlers, WebP images and on-demand search.",{},"15","\u002F2026\u002Fversion-3-2",{"text":1221,"minutes":1222,"time":1223,"words":1224},"3 min read",2.555,153300,511,{"title":969,"description":1216},{"loc":1219},"2026\u002Fversion-3-2",[1229],"release","zwGWQHp-71ktUSXiO4t6qy_Zj8h3Ga8FW3dM450alNQ",{"id":1232,"title":1233,"alternates":6,"author":33,"body":1234,"categories":6,"comments":14,"cover":6,"date":1801,"description":1802,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":1803,"navigation":19,"nocomments":14,"notoc":14,"pageid":1804,"path":1059,"readingTime":1805,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":1810,"sitemap":1811,"stem":1812,"table_of_contents":19,"tags":1813,"__hash__":1816},"page\u002F2026\u002Floading-search-only-when-readers-need-it.md","The Search Box That Downloaded Itself on Every Page",{"type":8,"value":1235,"toc":1793},[1236,1239,1243,1246,1249,1252,1256,1259,1353,1368,1371,1375,1378,1474,1484,1487,1490,1494,1497,1500,1550,1553,1556,1639,1645,1648,1728,1737,1741,1744,1763,1773,1777,1780,1790],[37,1237,1238],{},"Bloggrify ships an instant, fuzzy search: hit ⌘K, start typing, and results appear as you go. It feels free. It was not: the download that powers it was being paid by every reader, including the majority who never search. This post looks at why, and how we changed it so the cost falls only on the readers who actually use it.",[60,1240,1242],{"id":1241},"how-search-works-when-there-is-no-server","How search works when there is no server",[37,1244,1245],{},"Bloggrify blogs are static. There is no backend, no database sitting behind an API, nothing to send a query to. So when a reader searches, the searching has to happen in their browser.",[37,1247,1248],{},"That is a lovely property. It also has a consequence: if the browser is going to do the searching, the browser needs the thing to search. Nuxt Content builds that \"thing\" for you, and under the hood it is not small. To run queries client-side, it loads a WebAssembly build of SQLite and a dump of your content database. The browser boots a tiny database engine and asks it questions locally.",[37,1250,1251],{},"That is what makes the search feel instant once it is running. It is also a real download, and downloads are never free.",[60,1253,1255],{"id":1254},"the-catch-everyone-paid-whether-they-searched-or-not","The catch: everyone paid, whether they searched or not",[37,1257,1258],{},"Here is the code we started from, roughly:",[74,1260,1262],{"className":167,"code":1261,"language":169,"meta":11,"style":11},"const { data: files } = useLazyAsyncData(\n  'search',\n  () => queryCollectionSearchSections('page'),\n  { server: false }\n)\n",[80,1263,1264,1296,1308,1333,1349],{"__ignoreMap":11},[83,1265,1266,1270,1273,1277,1279,1283,1286,1290,1293],{"class":85,"line":86},[83,1267,1269],{"class":1268},"s4TJI","const",[83,1271,1272],{"class":191}," {",[83,1274,1276],{"class":1275},"sCjJA"," data",[83,1278,164],{"class":191},[83,1280,1282],{"class":1281},"swMNj"," files",[83,1284,1285],{"class":191}," }",[83,1287,1289],{"class":1288},"sQVg8"," =",[83,1291,1292],{"class":183}," useLazyAsyncData",[83,1294,1295],{"class":187},"(\n",[83,1297,1298,1301,1304,1306],{"class":85,"line":12},[83,1299,1300],{"class":206},"  '",[83,1302,1303],{"class":93},"search",[83,1305,207],{"class":206},[83,1307,218],{"class":191},[83,1309,1310,1313,1316,1319,1321,1323,1326,1328,1331],{"class":85,"line":221},[83,1311,1312],{"class":191},"  ()",[83,1314,1315],{"class":1268}," =>",[83,1317,1318],{"class":183}," queryCollectionSearchSections",[83,1320,188],{"class":187},[83,1322,207],{"class":206},[83,1324,1325],{"class":93},"page",[83,1327,207],{"class":206},[83,1329,1330],{"class":187},")",[83,1332,218],{"class":191},[83,1334,1335,1338,1341,1343,1346],{"class":85,"line":232},[83,1336,1337],{"class":191},"  {",[83,1339,1340],{"class":197}," server",[83,1342,164],{"class":191},[83,1344,1345],{"class":534}," false",[83,1347,1348],{"class":191}," }\n",[83,1350,1351],{"class":85,"line":250},[83,1352,262],{"class":187},[37,1354,1355,1356,1359,1360,1363,1364,1367],{},"Read that carefully. ",[80,1357,1358],{},"server: false"," says \"do not run this during the static build, run it in the browser\". Sensible: the search index is only useful client-side. But ",[80,1361,1362],{},"useLazyAsyncData"," fetches ",[119,1365,1366],{},"immediately"," on mount by default. And the search component lives in the navigation bar, which is on every single page.",[37,1369,1370],{},"So the chain was: every page mount, on every visit, quietly kicked off the fetch of the WASM engine and the database dump. For a reader who lands on one post, reads it, and leaves, that is a download of the entire search machinery to power a feature they never touched. Most readers never search. We were charging all of them for the few who do.",[60,1372,1374],{"id":1373},"why-you-cannot-just-load-it-later","Why you cannot just \"load it later\"",[37,1376,1377],{},"The obvious fix is to stop loading it eagerly. Nuxt gives you exactly that switch:",[74,1379,1381],{"className":167,"code":1380,"language":169,"meta":11,"style":11},"const { data: files, status, execute: loadSearchIndex } = useLazyAsyncData(\n  'search',\n  () => queryCollectionSearchSections('page'),\n  { server: false, immediate: false }\n)\n",[80,1382,1383,1419,1429,1449,1470],{"__ignoreMap":11},[83,1384,1385,1387,1389,1391,1393,1395,1398,1401,1403,1406,1408,1411,1413,1415,1417],{"class":85,"line":86},[83,1386,1269],{"class":1268},[83,1388,1272],{"class":191},[83,1390,1276],{"class":1275},[83,1392,164],{"class":191},[83,1394,1282],{"class":1281},[83,1396,1397],{"class":191},",",[83,1399,1400],{"class":1281}," status",[83,1402,1397],{"class":191},[83,1404,1405],{"class":1275}," execute",[83,1407,164],{"class":191},[83,1409,1410],{"class":1281}," loadSearchIndex",[83,1412,1285],{"class":191},[83,1414,1289],{"class":1288},[83,1416,1292],{"class":183},[83,1418,1295],{"class":187},[83,1420,1421,1423,1425,1427],{"class":85,"line":12},[83,1422,1300],{"class":206},[83,1424,1303],{"class":93},[83,1426,207],{"class":206},[83,1428,218],{"class":191},[83,1430,1431,1433,1435,1437,1439,1441,1443,1445,1447],{"class":85,"line":221},[83,1432,1312],{"class":191},[83,1434,1315],{"class":1268},[83,1436,1318],{"class":183},[83,1438,188],{"class":187},[83,1440,207],{"class":206},[83,1442,1325],{"class":93},[83,1444,207],{"class":206},[83,1446,1330],{"class":187},[83,1448,218],{"class":191},[83,1450,1451,1453,1455,1457,1459,1461,1464,1466,1468],{"class":85,"line":232},[83,1452,1337],{"class":191},[83,1454,1340],{"class":197},[83,1456,164],{"class":191},[83,1458,1345],{"class":534},[83,1460,1397],{"class":191},[83,1462,1463],{"class":197}," immediate",[83,1465,164],{"class":191},[83,1467,1345],{"class":534},[83,1469,1348],{"class":191},[83,1471,1472],{"class":85,"line":250},[83,1473,262],{"class":187},[37,1475,1476,1479,1480,1483],{},[80,1477,1478],{},"immediate: false"," means nothing is fetched until you call ",[80,1481,1482],{},"execute()"," yourself. Now the reader who never searches downloads nothing. Good.",[37,1485,1486],{},"But this is where the interesting part starts, because the naive version of \"load it later\" is worse than the problem it solves. If you wait until the reader clicks the search button to start the download, they open the box, see an empty field, and wait. The engine and the dump have to arrive before they can get a single result. You have taken a feature that felt instant and made it stutter for the exact people who wanted to use it.",[37,1488,1489],{},"The index needs to be there the moment they want it, and not a moment before they show they want it. The whole trick is reading that intent early enough.",[60,1491,1493],{"id":1492},"reading-intent-from-two-directions","Reading intent, from two directions",[37,1495,1496],{},"The challenge is that there are two completely different ways to open search, and they give you different amounts of warning.",[37,1498,1499],{},"The mouse is generous. Nobody clicks a button without first moving the pointer to it, and hovering is a near-certain signal that a click is coming. That gap of a few hundred milliseconds between hover and click is exactly enough to start the download early. So we prefetch on hover and on keyboard focus:",[74,1501,1505],{"className":1502,"code":1503,"language":1504,"meta":11,"style":11},"language-html shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","\u003CUContentSearchButton\n  @pointerenter=\"ensureSearchIndex\"\n  @focus=\"ensureSearchIndex\"\n\u002F>\n","html",[80,1506,1507,1516,1532,1545],{"__ignoreMap":11},[83,1508,1509,1512],{"class":85,"line":86},[83,1510,1511],{"class":191},"\u003C",[83,1513,1515],{"class":1514},"siaRy","UContentSearchButton\n",[83,1517,1518,1522,1525,1527,1530],{"class":85,"line":12},[83,1519,1521],{"class":1520},"sxHeq","  @pointerenter",[83,1523,1524],{"class":191},"=",[83,1526,495],{"class":206},[83,1528,1529],{"class":93},"ensureSearchIndex",[83,1531,374],{"class":206},[83,1533,1534,1537,1539,1541,1543],{"class":85,"line":221},[83,1535,1536],{"class":1520},"  @focus",[83,1538,1524],{"class":191},[83,1540,495],{"class":206},[83,1542,1529],{"class":93},[83,1544,374],{"class":206},[83,1546,1547],{"class":85,"line":232},[83,1548,1549],{"class":191},"\u002F>\n",[37,1551,1552],{},"By the time the click lands, the index is usually already on its way, often already there.",[37,1554,1555],{},"The keyboard shortcut is the opposite. ⌘K gives no warning at all: intent and action are the same instant. There is no hover to catch. So we cover it from the other side, by watching the state that actually represents \"search is open\", whichever path opened it:",[74,1557,1559],{"className":167,"code":1558,"language":169,"meta":11,"style":11},"const { open } = useContentSearch()\n\nwatch(open, (isOpen) => {\n  if (isOpen) {\n    ensureSearchIndex()\n  }\n})\n",[80,1560,1561,1580,1584,1607,1621,1628,1633],{"__ignoreMap":11},[83,1562,1563,1565,1567,1570,1572,1574,1577],{"class":85,"line":86},[83,1564,1269],{"class":1268},[83,1566,1272],{"class":191},[83,1568,1569],{"class":1281}," open",[83,1571,1285],{"class":191},[83,1573,1289],{"class":1288},[83,1575,1576],{"class":183}," useContentSearch",[83,1578,1579],{"class":187},"()\n",[83,1581,1582],{"class":85,"line":12},[83,1583,565],{"emptyLinePlaceholder":19},[83,1585,1586,1589,1592,1594,1597,1601,1603,1605],{"class":85,"line":221},[83,1587,1588],{"class":183},"watch",[83,1590,1591],{"class":187},"(open",[83,1593,1397],{"class":191},[83,1595,1596],{"class":191}," (",[83,1598,1600],{"class":1599},"snOSf","isOpen",[83,1602,1330],{"class":191},[83,1604,1315],{"class":1268},[83,1606,229],{"class":191},[83,1608,1609,1612,1614,1616,1619],{"class":85,"line":232},[83,1610,1611],{"class":176},"  if",[83,1613,1596],{"class":197},[83,1615,1600],{"class":187},[83,1617,1618],{"class":197},") ",[83,1620,192],{"class":191},[83,1622,1623,1626],{"class":85,"line":250},[83,1624,1625],{"class":183},"    ensureSearchIndex",[83,1627,1579],{"class":197},[83,1629,1630],{"class":85,"line":256},[83,1631,1632],{"class":191},"  }\n",[83,1634,1635,1637],{"class":85,"line":582},[83,1636,259],{"class":191},[83,1638,262],{"class":187},[37,1640,1641,1644],{},[80,1642,1643],{},"open"," is a shared piece of state toggled by the button and by the shortcut alike. Watching it means we do not have to enumerate every possible trigger. Anything that opens search, present or future, flows through here.",[37,1646,1647],{},"Finally, all of these paths funnel through one small guard so the index is fetched once and not on every hover:",[74,1649,1651],{"className":167,"code":1650,"language":169,"meta":11,"style":11},"function ensureSearchIndex() {\n  if (status.value === 'idle' || status.value === 'error') {\n    loadSearchIndex()\n  }\n}\n",[80,1652,1653,1666,1712,1719,1723],{"__ignoreMap":11},[83,1654,1655,1658,1661,1664],{"class":85,"line":86},[83,1656,1657],{"class":1268},"function",[83,1659,1660],{"class":183}," ensureSearchIndex",[83,1662,1663],{"class":191},"()",[83,1665,229],{"class":191},[83,1667,1668,1670,1672,1675,1677,1680,1683,1685,1688,1690,1693,1695,1697,1699,1701,1703,1706,1708,1710],{"class":85,"line":12},[83,1669,1611],{"class":176},[83,1671,1596],{"class":197},[83,1673,1674],{"class":187},"status",[83,1676,125],{"class":191},[83,1678,1679],{"class":187},"value",[83,1681,1682],{"class":1288}," ===",[83,1684,240],{"class":206},[83,1686,1687],{"class":93},"idle",[83,1689,207],{"class":206},[83,1691,1692],{"class":1288}," ||",[83,1694,1400],{"class":187},[83,1696,125],{"class":191},[83,1698,1679],{"class":187},[83,1700,1682],{"class":1288},[83,1702,240],{"class":206},[83,1704,1705],{"class":93},"error",[83,1707,207],{"class":206},[83,1709,1618],{"class":197},[83,1711,192],{"class":191},[83,1713,1714,1717],{"class":85,"line":221},[83,1715,1716],{"class":183},"    loadSearchIndex",[83,1718,1579],{"class":197},[83,1720,1721],{"class":85,"line":232},[83,1722,1632],{"class":191},[83,1724,1725],{"class":85,"line":250},[83,1726,1727],{"class":191},"}\n",[37,1729,1730,1731,1733,1734,1736],{},"It loads when nothing has happened yet (",[80,1732,1687],{},"), and it retries if a previous attempt failed (",[80,1735,1705],{},"), but it stays out of the way while a fetch is in flight or already done. The reader can wave the pointer over the button ten times and only the first one does anything.",[60,1738,1740],{"id":1739},"a-small-tuning-while-we-were-here","A small tuning while we were here",[37,1742,1743],{},"Once search actually loads on demand, it is worth making sure the results are good. Fuzzy matching is handled by Fuse.js, and its defaults are tuned for short strings, not blog content. Two options made a real difference:",[74,1745,1747],{"className":167,"code":1746,"language":169,"meta":11,"style":11},":fuse=\"{ resultLimit: 42, fuseOptions: { threshold: 0.3, ignoreLocation: true } }\"\n",[80,1748,1749],{"__ignoreMap":11},[83,1750,1751,1754,1756,1758,1761],{"class":85,"line":86},[83,1752,1753],{"class":187},":fuse",[83,1755,1524],{"class":1288},[83,1757,495],{"class":206},[83,1759,1760],{"class":93},"{ resultLimit: 42, fuseOptions: { threshold: 0.3, ignoreLocation: true } }",[83,1762,374],{"class":206},[37,1764,1765,1768,1769,1772],{},[80,1766,1767],{},"ignoreLocation"," tells Fuse to stop caring where in the text a match occurs. By default it favours matches near the start of a field, which makes sense for names and titles but punishes a term that appears in the middle of a paragraph. ",[80,1770,1771],{},"threshold: 0.3"," tightens how fuzzy a match is allowed to be, so a search returns things that are genuinely relevant rather than everything vaguely alike.",[60,1774,1776],{"id":1775},"what-changed-concretely","What changed, concretely",[37,1778,1779],{},"Nothing about the experience got slower, and for most visitors a good chunk of every page load simply disappeared. A reader who never searches now downloads none of the search machinery. A reader who does gets it warmed up before they have finished reaching for it, whether by mouse or by keyboard.",[37,1781,1782,1783,1786,1787,1789],{},"This pattern is not specific to search. Eager data fetching is the comfortable default, and it is very often the wrong one. ",[80,1784,1785],{},"useAsyncData"," will happily load everything the moment a component mounts, and that convenience quietly adds up when the component is everywhere and the data is heavy. Flipping to ",[80,1788,1478],{}," and spending a little effort on reading intent is not much code. Here it moved a cost off every reader who was never going to use it.",[938,1791,1792],{},"html pre.shiki code .s4TJI, html code.shiki .s4TJI{--shiki-light:#9C3EDA;--shiki-default:#9C3EDA;--shiki-dark:#F97583}html pre.shiki code .s9dvb, html code.shiki .s9dvb{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#E1E4E8}html pre.shiki code .sCjJA, html code.shiki .sCjJA{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#FFAB70}html pre.shiki code .swMNj, html code.shiki .swMNj{--shiki-light:#90A4AE;--shiki-default:#90A4AE;--shiki-dark:#79B8FF}html pre.shiki code .sQVg8, html code.shiki .sQVg8{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#F97583}html pre.shiki code .s2H7O, html code.shiki .s2H7O{--shiki-light:#6182B8;--shiki-default:#6182B8;--shiki-dark:#B392F0}html pre.shiki code .sHr2s, html code.shiki .sHr2s{--shiki-light:#90A4AE;--shiki-default:#90A4AE;--shiki-dark:#E1E4E8}html pre.shiki code .s03E9, html code.shiki .s03E9{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#9ECBFF}html pre.shiki code .sk5mZ, html code.shiki .sk5mZ{--shiki-light:#91B859;--shiki-default:#91B859;--shiki-dark:#9ECBFF}html pre.shiki code .s3cY9, html code.shiki .s3cY9{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#E1E4E8}html pre.shiki code .s0wY3, html code.shiki .s0wY3{--shiki-light:#FF5370;--shiki-default:#FF5370;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .siaRy, html code.shiki .siaRy{--shiki-light:#E53935;--shiki-light-font-style:inherit;--shiki-default:#E53935;--shiki-default-font-style:inherit;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .sxHeq, html code.shiki .sxHeq{--shiki-light:#9C3EDA;--shiki-default:#9C3EDA;--shiki-dark:#B392F0}html pre.shiki code .snOSf, html code.shiki .snOSf{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#90A4AE;--shiki-default-font-style:italic;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sTQaL, html code.shiki .sTQaL{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#39ADB5;--shiki-default-font-style:italic;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}",{"title":11,"searchDepth":12,"depth":12,"links":1794},[1795,1796,1797,1798,1799,1800],{"id":1241,"depth":12,"text":1242},{"id":1254,"depth":12,"text":1255},{"id":1373,"depth":12,"text":1374},{"id":1492,"depth":12,"text":1493},{"id":1739,"depth":12,"text":1740},{"id":1775,"depth":12,"text":1776},"2026-07-17","How Bloggrify's instant search worked, why it was quietly costing every reader a download they never used, and how we made it load only on intent.",{},"14",{"text":1806,"minutes":1807,"time":1808,"words":1809},"6 min read",5.125,307500,1025,{"title":1233,"description":1802},{"loc":1059},"2026\u002Floading-search-only-when-readers-need-it",[1814,1815],"performance","nuxt","F308DK-JAkY4wkmi9EEF5YQChjbZOaE7vaKfRWkFbi4",{"id":1818,"title":1819,"alternates":6,"author":33,"body":1820,"categories":6,"comments":14,"cover":6,"date":2180,"description":2181,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":2182,"navigation":19,"nocomments":14,"notoc":14,"pageid":2183,"path":1031,"readingTime":2184,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":2188,"sitemap":2189,"stem":2190,"table_of_contents":19,"tags":2191,"__hash__":2193},"page\u002F2026\u002Fai-crawlers-and-llms-txt.md","Should you block AI crawlers on your blog? And does it even work?",{"type":8,"value":1821,"toc":2173},[1822,1824,1827,1830,1833,1836,1839,1842,1864,1873,1877,1882,1888,1893,1954,1970,1973,1982,1988,1991,1995,2000,2013,2016,2075,2089,2094,2097,2102,2112,2115,2119,2122,2152,2164,2170],[37,1823,1032],{},[37,1825,1826],{},"It is a real question for a lot of writers online right now. Should we let LLMs feed off our content? Is there a risk to our visibility if, tomorrow, readers get their answers straight from ChatGPT?",[37,1828,1829],{},"Some articles are indeed alarmed by the fairly broad drop in traffic across sites.",[37,1831,1832],{},"Or the other way round: by becoming invisible to the LLMs, are we not cutting ourselves off from a potential source of traffic?",[37,1834,1835],{},"For my part, I tend to think that we, as readers, are changing the way we browse and search, and that this drop in traffic is unavoidable. So the question is whether to block the crawlers entirely to slow the trend down, or instead to try to be part of this new ecosystem. You be the judge.",[37,1837,1838],{},"Say you pick one path or the other. How do you go about it? How do you block the crawlers, or encourage them to read your blog? And does it actually work?",[37,1840,1841],{},"Before we start, it helps to separate two things that often get mixed up:",[1843,1844,1845,1855],"ol",{},[986,1846,1847,1850,1851,1854],{},[119,1848,1849],{},"Do you want AI crawlers to use your content at all?"," That is a ",[80,1852,1853],{},"robots.txt"," question",[986,1856,1857,1860,1861,1863],{},[119,1858,1859],{},"Do you want to make your content easy for AI tools to read?"," That is what ",[80,1862,1024],{}," is for",[37,1865,1866,1867,1869,1870,1872],{},"They are independent. You can block crawlers and skip ",[80,1868,1024],{},", publish ",[80,1871,1024],{}," and welcome crawlers, or do neither. What does not make sense is answering \"no\" to the first and \"yes\" to the second, and we will come back to that.",[60,1874,1876],{"id":1875},"do-you-want-ai-to-read-your-blog","Do you want AI to read your blog?",[37,1878,1879,1880,125],{},"First thing first: the major AI crawlers do respect ",[80,1881,1853],{},[37,1883,1884,1885,1887],{},"OpenAI, Anthropic, Google, Perplexity and the rest publish the names of their crawlers, and they document that those crawlers honour ",[80,1886,1853],{}," directives. This is not a folk belief, it is written policy. So if you decide you would rather not feed your writing into a training set or an answer engine, you can say so, and the well-behaved bots will listen.",[37,1889,1890,1891,164],{},"In Bloggrify that decision is one line in your ",[80,1892,268],{},[74,1894,1896],{"className":167,"code":1895,"filename":268,"language":169,"meta":11,"style":11},"export default defineAppConfig({\n  seo: {\n    ai: {\n      allowCrawlers: false,\n    },\n  },\n})\n",[80,1897,1898,1910,1919,1928,1939,1944,1948],{"__ignoreMap":11},[83,1899,1900,1902,1904,1906,1908],{"class":85,"line":86},[83,1901,177],{"class":176},[83,1903,180],{"class":176},[83,1905,283],{"class":183},[83,1907,188],{"class":187},[83,1909,192],{"class":191},[83,1911,1912,1915,1917],{"class":85,"line":12},[83,1913,1914],{"class":197},"  seo",[83,1916,164],{"class":191},[83,1918,229],{"class":191},[83,1920,1921,1924,1926],{"class":85,"line":221},[83,1922,1923],{"class":197},"    ai",[83,1925,164],{"class":191},[83,1927,229],{"class":191},[83,1929,1930,1933,1935,1937],{"class":85,"line":232},[83,1931,1932],{"class":197},"      allowCrawlers",[83,1934,164],{"class":191},[83,1936,1345],{"class":534},[83,1938,218],{"class":191},[83,1940,1941],{"class":85,"line":250},[83,1942,1943],{"class":191},"    },\n",[83,1945,1946],{"class":85,"line":256},[83,1947,253],{"class":191},[83,1949,1950,1952],{"class":85,"line":582},[83,1951,259],{"class":191},[83,1953,262],{"class":187},[37,1955,1956,1957,1960,1961,1963,1964,1969],{},"That adds a ",[80,1958,1959],{},"Disallow: \u002F"," group to your generated ",[80,1962,1853],{}," covering the AI crawlers of the major vendors. Your blog stays perfectly indexed in Google and Bing, because search crawlers are deliberately left alone: you refuse the AI training agents without breaking the features you actually want. If you would like the full details of what gets blocked and what does not, the ",[41,1965,1968],{"href":1966,"rel":1967},"https:\u002F\u002Fbloggrify.com\u002Freference\u002Fai",[45],"AI crawlers reference"," spells it out.",[37,1971,1972],{},"Two things are worth being clear about.",[37,1974,1975,1978,1979,1981],{},[119,1976,1977],{},"It is a request, not a wall."," ",[80,1980,1853],{}," is voluntary by design. Most crawlers honour it, some have been caught ignoring it, and anything you publish on a public static blog can ultimately be copied by someone who does not care about the rules. If you need real enforcement, you block at the network level, for example with your host's \"block AI scrapers\" feature. That happens outside Bloggrify, at your CDN or hosting provider.",[37,1983,1984,1987],{},[119,1985,1986],{},"Doing nothing is a valid answer too."," If you do not mind AI tools reading your posts, you change nothing. That is the default. Your content stays open, and the crawlers come and go as they always have. Not every blogger wants to fight this, and that is completely fine.",[37,1989,1990],{},"The point is simply that it is your content and your call. Bloggrify gives you the switch so the choice is yours to make, not one made for you by omission.",[60,1992,1994],{"id":1993},"making-your-blog-easy-to-read-llmstxt","Making your blog easy to read: llms.txt",[37,1996,1997,1998,125],{},"If you land on the \"sure, AI can read my blog\" side, there is a second, gentler thing you can offer: ",[80,1999,1024],{},[37,2001,2002,2004,2005,2007,2008,125],{},[80,2003,1024],{}," was proposed by Jeremy Howard, co-founder of Answer.AI, in September 2024. The idea is small and reasonable. A web page is full of navigation, scripts and markup that an AI has to wade through to find the actual content. ",[80,2006,1024],{}," is a single markdown file at the root of your site that hands over a clean, structured index instead: a title, a short description, and a link for each post. You can read the full specification at ",[41,2009,2012],{"href":2010,"rel":2011},"https:\u002F\u002Fllmstxt.org",[45],"llmstxt.org",[37,2014,2015],{},"Turning it on is, again, one line:",[74,2017,2019],{"className":167,"code":2018,"filename":268,"language":169,"meta":11,"style":11},"export default defineAppConfig({\n  seo: {\n    ai: {\n      llms: true,\n    },\n  },\n})\n",[80,2020,2021,2033,2041,2049,2061,2065,2069],{"__ignoreMap":11},[83,2022,2023,2025,2027,2029,2031],{"class":85,"line":86},[83,2024,177],{"class":176},[83,2026,180],{"class":176},[83,2028,283],{"class":183},[83,2030,188],{"class":187},[83,2032,192],{"class":191},[83,2034,2035,2037,2039],{"class":85,"line":12},[83,2036,1914],{"class":197},[83,2038,164],{"class":191},[83,2040,229],{"class":191},[83,2042,2043,2045,2047],{"class":85,"line":221},[83,2044,1923],{"class":197},[83,2046,164],{"class":191},[83,2048,229],{"class":191},[83,2050,2051,2054,2056,2059],{"class":85,"line":232},[83,2052,2053],{"class":197},"      llms",[83,2055,164],{"class":191},[83,2057,2058],{"class":534}," true",[83,2060,218],{"class":191},[83,2062,2063],{"class":85,"line":250},[83,2064,1943],{"class":191},[83,2066,2067],{"class":85,"line":256},[83,2068,253],{"class":191},[83,2070,2071,2073],{"class":85,"line":582},[83,2072,259],{"class":191},[83,2074,262],{"class":187},[37,2076,2077,2078,2081,2082,815,2085,2088],{},"Bloggrify then generates a ",[80,2079,2080],{},"\u002Fllms.txt"," route at build time, right next to your ",[80,2083,2084],{},"sitemap.xml",[80,2086,2087],{},"rss.xml",". Drafts and hidden posts are excluded, and when the option is off, which is the default, the file is not generated at all.",[2090,2091,2093],"h3",{"id":2092},"should-you-bother","Should you bother?",[37,2095,2096],{},"Here I want to be straight with you rather than sell it.",[37,2098,2099,2101],{},[80,2100,1024],{}," is an invitation, not an instruction. It does not tell anyone what they may or may not do with your content, it only makes the content easier to consume. That is exactly why enabling it while also blocking AI crawlers is contradictory, and Bloggrify prints a warning at build time if you try to do both.",[37,2103,2104,2105,2107,2108,2111],{},"The format has picked up real momentum, and you will find it on plenty of sites, especially documentation. But its actual usefulness today is still speculative. No major AI provider has confirmed that its crawlers read ",[80,2106,1024],{},", and Google has publicly compared it to the old ",[80,2109,2110],{},"keywords"," meta tag, which is not a flattering comparison. The one case that genuinely works is a human pasting your URL into a chat: the tool fetches the page, and clean markdown beats a page full of navigation every time.",[37,2113,2114],{},"For a documentation site, where structure is everything, that is a clear win. For a blog, where each post is already a clean, focused page, the gain is smaller. It costs you one tiny file. So enable it if the idea appeals to you, but do not expect traffic to appear because of it. It is proposed, it is standardised, and everyone gets to do what they like with it. Bloggrify simply gives you the switch.",[60,2116,2118],{"id":2117},"you-decide","You decide",[37,2120,2121],{},"None of this is Bloggrify taking a side in the AI debate. It is two independent switches with defaults that respect you:",[983,2123,2124,2138],{},[986,2125,2126,2129,2130,2133,2134,2137],{},[119,2127,2128],{},"Crawlers are allowed by default."," Flip ",[80,2131,2132],{},"allowCrawlers"," to ",[80,2135,2136],{},"false"," the day you decide otherwise, and the major vendors will honour it.",[986,2139,2140,2129,2145,2133,2148,2151],{},[119,2141,2142,2144],{},[80,2143,1024],{}," is off by default.",[80,2146,2147],{},"llms",[80,2149,2150],{},"true"," if you want to publish a clean index, knowing it is an offer, not a guarantee.",[37,2153,2154,2155,2158,2159,125],{},"If you want to go deeper, both are documented in the ",[41,2156,1968],{"href":1966,"rel":2157},[45],", and indexing in search engines, which is a separate topic again, lives in the ",[41,2160,2163],{"href":2161,"rel":2162},"https:\u002F\u002Fbloggrify.com\u002Freference\u002Frobots",[45],"robots.txt reference",[37,2165,2166,2167,125],{},"Bloggrify is open source. You can find the project ",[41,2168,935],{"href":933,"rel":2169},[45],[938,2171,2172],{},"html pre.shiki code .sTQaL, html code.shiki .sTQaL{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#39ADB5;--shiki-default-font-style:italic;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .s2H7O, html code.shiki .s2H7O{--shiki-light:#6182B8;--shiki-default:#6182B8;--shiki-dark:#B392F0}html pre.shiki code .sHr2s, html code.shiki .sHr2s{--shiki-light:#90A4AE;--shiki-default:#90A4AE;--shiki-dark:#E1E4E8}html pre.shiki code .s9dvb, html code.shiki .s9dvb{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#E1E4E8}html pre.shiki code .s3cY9, html code.shiki .s3cY9{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#E1E4E8}html pre.shiki code .s0wY3, html code.shiki .s0wY3{--shiki-light:#FF5370;--shiki-default:#FF5370;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":11,"searchDepth":12,"depth":12,"links":2174},[2175,2176,2179],{"id":1875,"depth":12,"text":1876},{"id":1993,"depth":12,"text":1994,"children":2177},[2178],{"id":2092,"depth":221,"text":2093},{"id":2117,"depth":12,"text":2118},"2026-07-16","The major AI crawlers honour robots.txt, so blocking them is one line in Bloggrify. Whether you should, and what it actually protects, is another question.",{},"13",{"text":1806,"minutes":2185,"time":2186,"words":2187},5.26,315600,1052,{"title":1819,"description":2181},{"loc":1031},"2026\u002Fai-crawlers-and-llms-txt",[1020,2192],"ai","GgefkS9BX4PROYvTOhxnGy1gV1Bc9ZyzXngjtVbW9xY",{"id":2195,"title":2196,"alternates":6,"author":33,"body":2197,"categories":6,"comments":14,"cover":6,"date":2275,"description":2276,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":2277,"navigation":19,"nocomments":14,"notoc":14,"pageid":2278,"path":2279,"readingTime":2280,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":2285,"sitemap":2286,"stem":2287,"table_of_contents":14,"tags":2288,"__hash__":2289},"page\u002F2025\u002Fversion-3-0.md","Announcing Bloggrify 3.0: CLI tools, Nuxt UI integration, and more",{"type":8,"value":2198,"toc":2266},[2199,2202,2206,2210,2213,2217,2220,2224,2227,2231,2234,2237,2251,2253,2258],[37,2200,2201],{},"We're excited to announce the release of Bloggrify 3.0.0! This major release brings a CLI to manage your blog, a Nuxt UI integration, and moves the whole stack to Nuxt 4 and Nuxt Content 3.",[60,2203,2205],{"id":2204},"whats-new-in-300","What's New in 3.0.0",[2090,2207,2209],{"id":2208},"_1-cli-tools","1. CLI tools",[37,2211,2212],{},"Bloggrify now comes with a CLI tool to help you manage your blog. You can use it to create new posts, update existing ones, and more.",[2090,2214,2216],{"id":2215},"_2-nuxt-ui-integration","2. Nuxt UI integration",[37,2218,2219],{},"Bloggrify now comes with a Nuxt UI integration to help you create beautiful and modern UI for your blog.",[2090,2221,2223],{"id":2222},"_3-nuxt-4-and-nuxt-content-3-support","3. Nuxt 4 and Nuxt-Content 3 support",[37,2225,2226],{},"Bloggrify now comes with a Nuxt 4 and Nuxt-Content 3 support.",[2090,2228,2230],{"id":2229},"_4-author-pages","4. Author pages",[37,2232,2233],{},"Author pages are automatically generated based on the authors you define in your app.config.ts file.",[37,2235,2236],{},"And more...",[983,2238,2239,2242,2245,2248],{},[986,2240,2241],{},"Automatic og:image generation when cover is not provided",[986,2243,2244],{},"Use Search feature from nuxt-ui and Nuxt-Content",[986,2246,2247],{},"Improve RSS discovery",[986,2249,2250],{},"Improve performance using nuxt seo zeroRuntime option",[60,2252,1185],{"id":1184},[37,2254,1188,2255,164],{},[41,2256,1192],{"href":933,"rel":2257},[45],[983,2259,2260,2262,2264],{},[986,2261,1197],{},[986,2263,1200],{},[986,2265,1203],{},{"title":11,"searchDepth":12,"depth":12,"links":2267},[2268,2274],{"id":2204,"depth":12,"text":2205,"children":2269},[2270,2271,2272,2273],{"id":2208,"depth":221,"text":2209},{"id":2215,"depth":221,"text":2216},{"id":2222,"depth":221,"text":2223},{"id":2229,"depth":221,"text":2230},{"id":1184,"depth":12,"text":1185},"2025-12-27","Major release with new CLI tools, nuxt ui integration, and more.",{},"12","\u002F2025\u002Fversion-3-0",{"text":2281,"minutes":2282,"time":2283,"words":2284},"1 min read",0.75,45000,150,{"title":2196,"description":2276},{"loc":2279},"2025\u002Fversion-3-0",[1229],"RaAzbC_ziNBVoKXYDgE_6zFJmcmtWCKDWEkngzq8J_8",{"id":2291,"title":2292,"alternates":6,"author":33,"body":2293,"categories":6,"comments":14,"cover":6,"date":2553,"description":2554,"draft":14,"extension":16,"hidden":14,"language":6,"layout":6,"links":6,"listed":14,"meta":2555,"navigation":19,"nocomments":14,"notoc":14,"pageid":2556,"path":2557,"readingTime":2558,"redirect_to_domain":6,"redirect_to_full_url":6,"robots":6,"schemaOrg":6,"seo":2562,"sitemap":2563,"stem":2564,"table_of_contents":14,"tags":2565,"__hash__":2566},"page\u002F2025\u002Fversion-2-1.md","Announcing Bloggrify 2.1: More Shortcodes and Analytics Integration",{"type":8,"value":2294,"toc":2545},[2295,2298,2302,2306,2309,2396,2416,2420,2423,2426,2502,2506,2509,2524,2527,2529,2534,2542],[37,2296,2297],{},"We're excited to announce the release of Bloggrify 2.1.0! This update improves existing features to enhance your blogging experience: shortcodes and built-in analytics integration.",[60,2299,2301],{"id":2300},"whats-new-in-210","What's New in 2.1.0",[2090,2303,2305],{"id":2304},"_1-shortcodes-simplify-complex-content","1. Shortcodes: Simplify Complex Content",[37,2307,2308],{},"Shortcodes provide an elegant way to insert complex elements into your Markdown content without writing lengthy HTML or Vue components.\nHere are the new shortcodes available in Bloggrify 2.1.0:",[983,2310,2311,2341,2367,2393],{},[986,2312,2313,2314],{},"youtube: Embed YouTube videos with a simple shortcode",[2315,2316,2317,2324],"mdd",{},[2318,2319,2320],"template",{"v-slot:preview":11},[2321,2322],"youtube",{"video-id":2323},"su2gNQJkteg",[2318,2325,2326,2334,2336],{"v-slot:markup":11},[74,2327,2330],{"className":2328,"code":11,"language":2329,"meta":11,"style":11},"language-markdown shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","markdown",[80,2331,2332],{"__ignoreMap":11},[83,2333],{"class":85,"line":86},[2321,2335],{"video-id":2323},[74,2337,2339],{"className":2338,"code":11,"language":132},[130],[80,2340,11],{"__ignoreMap":11},[986,2342,2343,2344],{},"X: Embed tweets directly in your content",[2315,2345,2346,2352],{},[2318,2347,2348],{"v-slot:preview":11},[2349,2350],"x",{"tweet-id":2351},"1750435525071159309",[2318,2353,2354,2360,2362],{"v-slot:markup":11},[74,2355,2356],{"className":2328,"code":11,"language":2329,"meta":11,"style":11},[80,2357,2358],{"__ignoreMap":11},[83,2359],{"class":85,"line":86},[2349,2361],{"tweet-id":2351},[74,2363,2365],{"className":2364,"code":11,"language":132},[130],[80,2366,11],{"__ignoreMap":11},[986,2368,2369,2370],{},"instagram: Display Instagram posts with ease",[2315,2371,2372,2378],{},[2318,2373,2374],{"v-slot:preview":11},[2375,2376],"instagram",{"post-id":2377},"CxOWiQNP2MO",[2318,2379,2380,2386,2388],{"v-slot:markup":11},[74,2381,2382],{"className":2328,"code":11,"language":2329,"meta":11,"style":11},[80,2383,2384],{"__ignoreMap":11},[83,2385],{"class":85,"line":86},[2375,2387],{"post-id":2377},[74,2389,2391],{"className":2390,"code":11,"language":132},[130],[80,2392,11],{"__ignoreMap":11},[986,2394,2395],{},"vimeo: Add Vimeo videos to your blog posts",[2315,2397,2398,2404],{},[2318,2399,2400],{"v-slot:preview":11},[2401,2402],"vimeo",{"video-id":2403},"55073825",[2318,2405,2406],{"v-slot:markup":11},[74,2407,2409],{"className":2328,"code":2408,"language":2329,"meta":11,"style":11},":Vimeo{videoId=\"55073825\"}\n\n",[80,2410,2411],{"__ignoreMap":11},[83,2412,2413],{"class":85,"line":86},[83,2414,2415],{"class":187},":Vimeo{videoId=\"55073825\"}\n",[2090,2417,2419],{"id":2418},"_2-analytics-integration","2. Analytics Integration",[37,2421,2422],{},"Umami has been added as a new analytics provider in Bloggrify 2.1.0.",[37,2424,2425],{},"You can now use it alongside Google Analytics, Hakanai Pulse, Fathom, Pirsch, and Plausible to track your blog's performance.",[74,2427,2431],{"className":2428,"code":2429,"language":2430,"meta":11,"style":11},"language-ts shiki shiki-themes material-theme-lighter material-theme-lighter github-dark","\u002F\u002F app.config.ts\nanalytics: {\n    providers: [ {\n        provider: 'umami',\n        code: 'YOUR_TRACKING_CODE'\n    }]\n }\n","ts",[80,2432,2433,2439,2448,2460,2476,2491,2498],{"__ignoreMap":11},[83,2434,2435],{"class":85,"line":86},[83,2436,2438],{"class":2437},"sxjbK","\u002F\u002F app.config.ts\n",[83,2440,2441,2444,2446],{"class":85,"line":12},[83,2442,2443],{"class":89},"analytics",[83,2445,164],{"class":191},[83,2447,229],{"class":191},[83,2449,2450,2453,2455,2458],{"class":85,"line":221},[83,2451,2452],{"class":89},"    providers",[83,2454,164],{"class":191},[83,2456,2457],{"class":197}," [ ",[83,2459,192],{"class":191},[83,2461,2462,2465,2467,2469,2472,2474],{"class":85,"line":232},[83,2463,2464],{"class":197},"        provider",[83,2466,164],{"class":191},[83,2468,240],{"class":206},[83,2470,2471],{"class":93},"umami",[83,2473,207],{"class":206},[83,2475,218],{"class":191},[83,2477,2478,2481,2483,2485,2488],{"class":85,"line":250},[83,2479,2480],{"class":197},"        code",[83,2482,164],{"class":191},[83,2484,240],{"class":206},[83,2486,2487],{"class":93},"YOUR_TRACKING_CODE",[83,2489,2490],{"class":206},"'\n",[83,2492,2493,2496],{"class":85,"line":256},[83,2494,2495],{"class":191},"    }",[83,2497,560],{"class":197},[83,2499,2500],{"class":85,"line":582},[83,2501,1348],{"class":191},[60,2503,2505],{"id":2504},"upgrading-to-210","Upgrading to 2.1.0",[37,2507,2508],{},"Upgrading to Bloggrify 2.1.0 is straightforward:",[74,2510,2512],{"className":76,"code":2511,"language":78,"meta":11,"style":11},"npm install bloggrify@latest\n",[80,2513,2514],{"__ignoreMap":11},[83,2515,2516,2518,2521],{"class":85,"line":86},[83,2517,90],{"class":89},[83,2519,2520],{"class":93}," install",[83,2522,2523],{"class":93}," bloggrify@latest\n",[37,2525,2526],{},"For existing projects, no configuration changes are required unless you want to use the new features.",[60,2528,1185],{"id":1184},[37,2530,1188,2531,164],{},[41,2532,1192],{"href":933,"rel":2533},[45],[983,2535,2536,2538,2540],{},[986,2537,1197],{},[986,2539,1200],{},[986,2541,1203],{},[938,2543,2544],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHr2s, html code.shiki .sHr2s{--shiki-light:#90A4AE;--shiki-default:#90A4AE;--shiki-dark:#E1E4E8}html pre.shiki code .sxjbK, html code.shiki .sxjbK{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#90A4AE;--shiki-default-font-style:italic;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sY6Vb, html code.shiki .sY6Vb{--shiki-light:#E2931D;--shiki-default:#E2931D;--shiki-dark:#B392F0}html pre.shiki code .s9dvb, html code.shiki .s9dvb{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#E1E4E8}html pre.shiki code .s3cY9, html code.shiki .s3cY9{--shiki-light:#E53935;--shiki-default:#E53935;--shiki-dark:#E1E4E8}html pre.shiki code .s03E9, html code.shiki .s03E9{--shiki-light:#39ADB5;--shiki-default:#39ADB5;--shiki-dark:#9ECBFF}html pre.shiki code .sk5mZ, html code.shiki .sk5mZ{--shiki-light:#91B859;--shiki-default:#91B859;--shiki-dark:#9ECBFF}",{"title":11,"searchDepth":12,"depth":12,"links":2546},[2547,2551,2552],{"id":2300,"depth":12,"text":2301,"children":2548},[2549,2550],{"id":2304,"depth":221,"text":2305},{"id":2418,"depth":221,"text":2419},{"id":2504,"depth":12,"text":2505},{"id":1184,"depth":12,"text":1185},"2025-01-25","Minor release with new shortcodes and Umami analytics integration. ",{},"5","\u002F2025\u002Fversion-2-1",{"text":2281,"minutes":2559,"time":2560,"words":2561},0.81,48600,162,{"title":2292,"description":2554},{"loc":2557},"2025\u002Fversion-2-1",[1229],"14k6JJb-nXVRJqIJ8qzWxSzPCc7hrOfBvyDXEcpKUMg",[2568,2588,2602,2609,2613,2614,2615],{"title":2569,"path":2570,"stem":2569,"children":2571,"page":14},"2024","\u002F2024",[2572,2576,2580,2584],{"title":2573,"path":2574,"stem":2575},"Analytics in Bloggrify: Measuring Your Audience Without Tracking Them","\u002F2024\u002Fanalytics","2024\u002Fanalytics",{"title":2577,"path":2578,"stem":2579},"Announcing KaTeX Support: Easily Embed Mathematical Formulas in Bloggrify","\u002F2024\u002Fkatex","2024\u002Fkatex",{"title":2581,"path":2582,"stem":2583},"Announcing Bloggrify 1.0: A New Static Blog Generator Built on Nuxt","\u002F2024\u002Flaunching","2024\u002Flaunching",{"title":2585,"path":2586,"stem":2587},"Writing in Bloggrify: Beyond Basic Markdown","\u002F2024\u002Fmarkdown","2024\u002Fmarkdown",{"title":2589,"path":2590,"stem":2589,"children":2591,"page":14},"2025","\u002F2025",[2592,2596,2600,2601],{"title":2593,"path":2594,"stem":2595},"Introducing a new custom component: listing page","\u002F2025\u002Fnew-custom-component","2025\u002Fnew-custom-component",{"title":2597,"path":2598,"stem":2599},"Announcing Bloggrify 2.0: Enhanced SEO, Analytics, and Developer Experience","\u002F2025\u002Fversion-2-0","2025\u002Fversion-2-0",{"title":2292,"path":2557,"stem":2564},{"title":2196,"path":2279,"stem":2287},{"title":2603,"path":2604,"stem":2603,"children":2605,"page":14},"2026","\u002F2026",[2606,2607,2608],{"title":1819,"path":1031,"stem":2190},{"title":1233,"path":1059,"stem":1812},{"title":969,"path":1219,"stem":1227},{"title":2610,"path":2611,"stem":2612},"About","\u002Fabout","about",{"title":32,"path":954,"stem":962},{"title":5,"path":21,"stem":27},{"title":2616,"path":2617,"stem":1020},"SEO in Bloggrify: How Your Blog Gets Found","\u002Fseo",1785155176430]