<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Takashi Masuda</title>
    <description>The latest articles on DEV Community by Takashi Masuda (@masutaka).</description>
    <link>https://dev.to/masutaka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F44998%2Fd9ecea54-0f9a-4fbe-95e6-252288b4fbb1.jpeg</url>
      <title>DEV Community: Takashi Masuda</title>
      <link>https://dev.to/masutaka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masutaka"/>
    <language>en</language>
    <item>
      <title>Read 'AWS Container Design and Build: A Practical Introduction, Revised and Expanded Edition' and Worked Through Every Hands-On Exercise</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 23 Aug 2026 11:11:28 +0000</pubDate>
      <link>https://dev.to/masutaka/read-aws-container-design-and-build-a-practical-introduction-revised-and-expanded-edition-and-4a22</link>
      <guid>https://dev.to/masutaka/read-aws-container-design-and-build-a-practical-introduction-revised-and-expanded-edition-and-4a22</guid>
      <description>&lt;p&gt;I bought it on Kindle on February 20, 2026, and finished reading it on July 19. It took me quite a while.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.amazon.co.jp/dp/B0FSH7HM2C" rel="noopener noreferrer"&gt;AWSコンテナ設計・構築［本格］入門 増補改訂版&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd used ECS before, but I'd never built anything from scratch with it at work. Since it looks like I'll be working with ECS soon, and my last hands-on experience was a long time ago, I picked this up to catch up on the current state of things. The fact that it comes with hands-on exercises was another deciding factor.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Book Is Organized
&lt;/h2&gt;

&lt;p&gt;It's 656 pages. Here's the table of contents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Chapter 1&lt;/code&gt; Overview of containers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Chapter 2&lt;/code&gt; AWS fundamentals needed for container design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Chapter 3&lt;/code&gt; Basic AWS architecture design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Chapter 4&lt;/code&gt; Container design centered on ECS/Fargate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Chapter 5&lt;/code&gt; Building an AWS container architecture (basics)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Chapter 6&lt;/code&gt; Building an AWS container architecture (practice)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chapters 1-4 are the design theory, and Chapters 5-6 are the hands-on part. You start actually typing at page 301, so roughly half the book by page count is hands-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hands-On Part
&lt;/h2&gt;

&lt;p&gt;The hands-on exercises use four repositories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/uma-arai/sbcntr-resources" rel="noopener noreferrer"&gt;https://github.com/uma-arai/sbcntr-resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/uma-arai/sbcntr-frontend" rel="noopener noreferrer"&gt;https://github.com/uma-arai/sbcntr-frontend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/uma-arai/sbcntr-backend" rel="noopener noreferrer"&gt;https://github.com/uma-arai/sbcntr-backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/uma-arai/sbcntr-batch" rel="noopener noreferrer"&gt;https://github.com/uma-arai/sbcntr-batch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You build a pet shop visit reservation app out of three containers: frontend, backend, and batch.&lt;/p&gt;

&lt;p&gt;Here's roughly what Chapter 5 (the basics) covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the VPC-related resources with a CloudFormation template&lt;/li&gt;
&lt;li&gt;Build the frontend and backend Docker images and push them to ECR&lt;/li&gt;
&lt;li&gt;Create two target groups for Blue/Green deployment and set up an ALB&lt;/li&gt;
&lt;li&gt;Create the ECS task definitions, cluster, and service&lt;/li&gt;
&lt;li&gt;Create an Aurora PostgreSQL instance and pass the credentials to the containers as environment variables via Secrets Manager&lt;/li&gt;
&lt;li&gt;Build a CI/CD pipeline with CodeConnections, CodeBuild, and CodePipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The book has you spin up an EC2 instance for development and work from there, but I built everything from my local macOS instead. That caused no real problems, though the load testing with &lt;a href="https://github.com/grafana/k6" rel="noopener noreferrer"&gt;k6&lt;/a&gt; looked like a hassle to run from my local machine, so I created an EC2 instance just for that.&lt;/p&gt;

&lt;p&gt;Chapter 6 (the practical part) is where you add the things you actually need in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a FireLens (Fluent Bit) sidecar container and ship logs to S3&lt;/li&gt;
&lt;li&gt;Add an ADOT Collector sidecar container and do distributed tracing with X-Ray&lt;/li&gt;
&lt;li&gt;Build batch processing with Step Functions and EventBridge Scheduler&lt;/li&gt;
&lt;li&gt;Run load tests with k6 and watch Auto Scaling in action&lt;/li&gt;
&lt;li&gt;Migrate service-to-service communication from Cloud Map service discovery to ECS Service Connect&lt;/li&gt;
&lt;li&gt;Inject SIGKILL into the backend process with FIS and confirm automatic recovery&lt;/li&gt;
&lt;li&gt;Enable GuardDuty's ECS Runtime Monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you've got CodePipeline in place, pushing to the &lt;code&gt;v2&lt;/code&gt; branch of your forked frontend repository is all it takes to trigger a deployment. Getting this far is genuinely satisfying — you've got something that actually looks like the real thing 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping Costs Down
&lt;/h3&gt;

&lt;p&gt;Cost was what I worried about most during these exercises. Here's what I was actually billed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Month&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;June 2026&lt;/td&gt;
&lt;td&gt;USD 16.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;July 2026&lt;/td&gt;
&lt;td&gt;USD 82.34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest cost was interface VPC endpoints. I left them running for a few days to see how much they'd add up to, and the bill climbed fast enough that I deleted them right away.&lt;/p&gt;

&lt;p&gt;Lesson learned. From then on, I turned the following into a set of commands to run whenever I paused the exercises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop the Aurora PostgreSQL cluster (note that it starts automatically after at most 7 days)&lt;/li&gt;
&lt;li&gt;Delete the VPC endpoints along with their entire CloudFormation stack&lt;/li&gt;
&lt;li&gt;Set the ECS service's desired count to 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resuming is the reverse. I also set up a budget alert.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Dedicated AWS Account for the Hands-On
&lt;/h3&gt;

&lt;p&gt;Backing up a bit: every resource I created would be unnecessary once the exercises were done. So I enabled AWS Organizations beforehand and created a dedicated AWS account for the hands-on.&lt;/p&gt;

&lt;p&gt;I've used AWS Organizations at work, but I'd never set it up as an administrator, so it was a good opportunity to understand it — IAM Identity Center included.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Few Places I Got Stuck
&lt;/h3&gt;

&lt;p&gt;There were three.&lt;/p&gt;

&lt;p&gt;The first was the frontend's log group. The book says to "remove &lt;code&gt;awslogs-create-group&lt;/code&gt;," but when I did, the log group was never created and deployments kept failing. I ended up creating the log group by hand. The backend's log group appears to be created by the CloudFormation template.&lt;/p&gt;

&lt;p&gt;The second was CodeBuild triggering twice. After making the ECR tags immutable, CodeBuild started failing. Builds were being triggered by both the GitHub webhook and CodeBuild itself, so it was trying to push the same tag twice. Disabling the webhook fixed it.&lt;/p&gt;

&lt;p&gt;The third was the EventBridge UI. The book has you create things from "Buses &amp;gt; Rules," but these days you need to use "Scheduler &amp;gt; Schedules."&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;It took me five months, but I now have a feel for building things end to end on ECS. Clicking through the settings in the AWS console should also pay off later when I write the Terraform.&lt;/p&gt;

&lt;p&gt;This revised and expanded edition came out on January 30, 2026, so it covers recent topics like ECS Managed Instances and ECS Service Connect. I think it's a good book for anyone about to start working with ECS.&lt;/p&gt;

&lt;p&gt;One thing to note: the Kindle edition is a fixed-layout book, so you can't highlight or take notes in it. I kept my reading notes in a private GitHub Discussion instead, and they piled up quite a bit. This post is a summary of them.&lt;/p&gt;

&lt;p&gt;I hope this helps someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Notes
&lt;/h2&gt;

&lt;p&gt;Below are my personal notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  P46. ECS Managed Instances
&lt;/h3&gt;

&lt;p&gt;The data plane now has one more option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Fargate&lt;/li&gt;
&lt;li&gt;ECS Managed Instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ECS Managed Instances was announced on September 30, 2025, and the official AWS documentation recommends it over Fargate&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. It's based on &lt;a href="https://github.com/bottlerocket-os/bottlerocket" rel="noopener noreferrer"&gt;Bottlerocket&lt;/a&gt;, and since a new instance launches and replaces the old one every 14 days, ECS tasks need to be cycled regularly too.&lt;/p&gt;

&lt;p&gt;Note: the book's hands-on exercises use Fargate.&lt;/p&gt;

&lt;h3&gt;
  
  
  P54. AWS Copilot Has Moved to Maintenance Mode
&lt;/h3&gt;

&lt;p&gt;AWS Copilot is introduced as a tool for simplifying the use of ECS and App Runner, but when I looked it up, &lt;a href="https://github.com/aws/copilot-cli/discussions/5925" rel="noopener noreferrer"&gt;end of support&lt;/a&gt; had already been announced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kayac/ecspresso" rel="noopener noreferrer"&gt;ecspresso&lt;/a&gt; is a similar tool. Where AWS Copilot lets you work without thinking about the infrastructure, ecspresso apparently assumes users who understand the infrastructure deeply.&lt;/p&gt;

&lt;p&gt;I've been curious about ecspresso for a while, so I plan to give it a try.&lt;/p&gt;

&lt;h3&gt;
  
  
  P111. Connectivity Options Between ECS Services
&lt;/h3&gt;

&lt;p&gt;Chapter 2 compares three approaches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service discovery with Cloud Map

&lt;ul&gt;
&lt;li&gt;The simplest option. Relatively easy to operate if you're doing rolling updates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Private ALB/NLB integration

&lt;ul&gt;
&lt;li&gt;Useful when you want authentication between services or a sorry page. Naturally, you pay for the ALB/NLB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ECS Service Connect

&lt;ul&gt;
&lt;li&gt;A proxy container is automatically injected as a sidecar, controlling traffic, logs, and metrics between services&lt;/li&gt;
&lt;li&gt;Connecting from ECS services running in a different VPC works out of the box&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud Map looks like the call if rolling updates are enough; the other two if you need Blue/Green deployment or metrics.&lt;/p&gt;

&lt;p&gt;Note: the hands-on starts with Cloud Map and migrates to ECS Service Connect on page 585.&lt;/p&gt;

&lt;h3&gt;
  
  
  P135. FireLens
&lt;/h3&gt;

&lt;p&gt;Besides CloudWatch Logs (awslogs), there's FireLens for collecting container logs. So &lt;em&gt;this&lt;/em&gt; is the thing that uses Fluent Bit — that finally clicked for me.&lt;/p&gt;

&lt;p&gt;You can forward to CloudWatch Logs and then send to S3, but that runs up the bill, so FireLens is apparently the better choice.&lt;/p&gt;

&lt;p&gt;On a side note, CloudWatch Logs announced intelligent tiering for storage on July 15. It automatically sorts logs into three tiers — Standard, Infrequent Access, and Archive Instant Access — based on access patterns. I should factor this into my logging strategy too.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://aws.amazon.com/jp/about-aws/whats-new/2026/07/amazon-cloudwatch-intelligent-tiering/" rel="noopener noreferrer"&gt;Amazon CloudWatch Logs announces intelligent tiering for storage - AWS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  P214. Secrets Manager vs. SSM Parameter Store SecureString
&lt;/h3&gt;

&lt;p&gt;I'd never really understood when to use which, so the explanation was a big help.&lt;/p&gt;

&lt;p&gt;The rule of thumb: use Secrets Manager if you need automatic rotation, and SSM Parameter Store SecureString if you don't.&lt;/p&gt;

&lt;h3&gt;
  
  
  P250. Fault Testing with AWS FIS
&lt;/h3&gt;

&lt;p&gt;FIS (Fault Injection Service), AWS's chaos engineering service, lets you deliberately inject faults to verify a system's resilience.&lt;/p&gt;

&lt;p&gt;In the hands-on on page 594, I sent SIGKILL to the backend to force-kill the process, then confirmed it recovered automatically. All you do is create an experiment template and hit "Start experiment," so it was easier than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  P623. GuardDuty's ECS Runtime Monitoring
&lt;/h3&gt;

&lt;p&gt;Just enabling Runtime Monitoring (ECS) from the GuardDuty console automatically launches an agent sidecar in each task.&lt;/p&gt;

&lt;p&gt;I tried it out by getting into the backend container with ECS Exec and running &lt;code&gt;nslookup&lt;/code&gt; against a cryptocurrency mining pool service — and it was properly flagged as a threat. If that's what you get from simply turning it on, there's little reason not to.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonECS/latest/developerguide/clusters.html&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>books</category>
    </item>
    <item>
      <title>Switching from the Mac port Build of Emacs to the Standard NS Build</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 21 Jun 2026 03:34:35 +0000</pubDate>
      <link>https://dev.to/masutaka/switching-from-the-mac-port-build-of-emacs-to-the-standard-ns-build-lh4</link>
      <guid>https://dev.to/masutaka/switching-from-the-mac-port-build-of-emacs-to-the-standard-ns-build-lh4</guid>
      <description>&lt;p&gt;For a long time I'd been using &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport" rel="noopener noreferrer"&gt;railwaycat/homebrew-emacsmacport&lt;/a&gt;, a build of Emacs with the &lt;a href="https://bitbucket.org/mituharu/emacs-mac" rel="noopener noreferrer"&gt;Mac port&lt;/a&gt; patches applied.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/railwaycat" rel="noopener noreferrer"&gt;
        railwaycat
      &lt;/a&gt; / &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport" rel="noopener noreferrer"&gt;
        homebrew-emacsmacport
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Emacs mac port formulae for the Homebrew package manager
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why I Switched to the NS Build
&lt;/h2&gt;

&lt;p&gt;Over the past few years, though, I'd been feeling the following pain points.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Mac port hasn't kept up with the latest Emacs

&lt;ul&gt;
&lt;li&gt;The current latest Emacs is 30.2, but the Mac port only supports 29.4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;With the latest railwaycat/homebrew-emacsmacport release, &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/releases/tag/emacs-29.4-mac-10.1" rel="noopener noreferrer"&gt;emacs-29.4-mac-10.1&lt;/a&gt;, I ran into the problem in &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/issues/389" rel="noopener noreferrer"&gt;issue #389&lt;/a&gt; on my setup, so I'd been sticking with the previous &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/releases/tag/emacs-29.1-mac-10.0" rel="noopener noreferrer"&gt;emacs-29.1-mac-10.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ideally, I'd like to use the latest Emacs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only reason I used the Mac port build in the first place was to automatically turn off Japanese input when I hit &lt;code&gt;M-x&lt;/code&gt; and the like, then restore it to its original state afterward. &lt;a href="https://github.com/masutaka/dotfiles-public/blob/13486f26b1b73474e5ed3658ee6d5912119cf677/.emacs.d/init.el#L1214-L1290" rel="noopener noreferrer"&gt;Here's the configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As long as I could reproduce this behavior, I'd be able to switch to the standard NS build. This time, that finally happened.&lt;/p&gt;

&lt;p&gt;By the way, NS stands for NeXTSTEP, which on macOS refers to the GUI build that uses Cocoa (AppKit).&lt;/p&gt;

&lt;h2&gt;
  
  
  I Found That sis Could Solve It
&lt;/h2&gt;

&lt;p&gt;After looking into it, I found that &lt;a href="https://github.com/laishulu/emacs-smart-input-source" rel="noopener noreferrer"&gt;emacs-smart-input-source&lt;/a&gt; (hereafter "sis") could reproduce what I'd been doing with the Mac port build.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/laishulu" rel="noopener noreferrer"&gt;
        laishulu
      &lt;/a&gt; / &lt;a href="https://github.com/laishulu/emacs-smart-input-source" rel="noopener noreferrer"&gt;
        emacs-smart-input-source
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Minimize manual input source (input method) switching.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;On macOS, sis uses a CLI called &lt;a href="https://github.com/laishulu/macism" rel="noopener noreferrer"&gt;macism&lt;/a&gt; as the backend for switching input sources. It's by &lt;a href="https://github.com/laishulu" rel="noopener noreferrer"&gt;@laishulu&lt;/a&gt;, the same author as sis.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/laishulu" rel="noopener noreferrer"&gt;
        laishulu
      &lt;/a&gt; / &lt;a href="https://github.com/laishulu/macism" rel="noopener noreferrer"&gt;
        macism
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Reliable CLI MacOS Input Source Manager [MacOS上可靠切换输入法的命令行工具]
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Installing the NS Build of Emacs
&lt;/h2&gt;

&lt;p&gt;First, I installed the build from &lt;a href="https://emacsformacosx.com/" rel="noopener noreferrer"&gt;emacsformacosx.com&lt;/a&gt; via Homebrew.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; emacs-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Configuring sis Solved It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Installing macism
&lt;/h3&gt;

&lt;p&gt;I installed macism via Homebrew.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;laishulu/homebrew/macism
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;macism can switch input sources from the command line on its own, like this.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Set the input &lt;span class="nb"&gt;source &lt;/span&gt;to &lt;span class="s2"&gt;"ABC"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;macism com.apple.keylayout.ABC
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Set the input &lt;span class="nb"&gt;source &lt;/span&gt;to Google Japanese Input&lt;span class="s1"&gt;'s "あ"
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;macism com.google.inputmethod.Japanese.base
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Configuring sis
&lt;/h3&gt;

&lt;p&gt;This is all I configured.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; When moving the cursor to the minibuffer, automatically switch to English mode,&lt;/span&gt;
&lt;span class="c1"&gt;;; and restore the Japanese input state after returning to the original buffer.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sis-ism-lazyman-config&lt;/span&gt;
 &lt;span class="s"&gt;"com.apple.keylayout.ABC"&lt;/span&gt;               &lt;span class="c1"&gt;;; ABC&lt;/span&gt;
 &lt;span class="s"&gt;"com.google.inputmethod.Japanese.base"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;;; Google Japanese Input&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sis-global-respect-mode&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Switch the cursor color to match the input mode.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sis-global-cursor-color-mode&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;sis-global-respect-mode&lt;/code&gt; switches to English input when you move to the minibuffer with &lt;code&gt;M-x&lt;/code&gt; and the like, and restores the Japanese input state when you return to the original buffer. This is exactly what I'd been doing with the Mac port build.&lt;/p&gt;

&lt;p&gt;With that, sis solved it without any issues, and I successfully switched to the standard NS build.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I was able to switch from the Mac port build I'd used for years to the standard NS build. I'm glad I could solve the input source switching on &lt;code&gt;M-x&lt;/code&gt;, which was my one and biggest pain point.&lt;/p&gt;

&lt;p&gt;Compared to the Mac port build, there's nothing in particular that feels inconvenient.&lt;/p&gt;

&lt;p&gt;I can now keep up with the latest Emacs. Emacs 30 feels faster when it comes to networking. You can really tell when using &lt;a href="https://repo.or.cz/navi2ch.git" rel="noopener noreferrer"&gt;navi2ch&lt;/a&gt;(!).&lt;/p&gt;

&lt;p&gt;I hope this helps someone.&lt;/p&gt;
&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/main/.emacs.d/init.el" rel="noopener noreferrer"&gt;My current &lt;code&gt;~/.emacs.d/init.el&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/laishulu/macism" rel="noopener noreferrer"&gt;macism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/laishulu/emacs-smart-input-source" rel="noopener noreferrer"&gt;emacs-smart-input-source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emacsformacosx.com/" rel="noopener noreferrer"&gt;Emacs For Mac OS X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Switching Input Sources by Context with sis-global-context-mode
&lt;/h3&gt;

&lt;p&gt;sis also has &lt;code&gt;sis-global-context-mode&lt;/code&gt;, which looks at the characters around the cursor (the context) and switches the input source automatically.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sis-global-context-mode&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example, when you "write Japanese → edit somewhere else → return to the original spot and keep writing," it switches to Japanese input if you land mid-Japanese, or to English if you land mid-English. It determines whether the characters before and after the cursor are Japanese or English, and only switches when it can make that determination, so there are few false triggers.&lt;/p&gt;

&lt;p&gt;That said, the default for &lt;code&gt;sis-context-hooks&lt;/code&gt;, which decides when it fires, is &lt;code&gt;'(evil-insert-state-entry-hook)&lt;/code&gt;, which assumes &lt;a href="https://github.com/emacs-evil/evil" rel="noopener noreferrer"&gt;evil&lt;/a&gt;. If you don't use evil, this hook never gets called and it won't work, so you'll need to add hooks that match your own workflow.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;sis-global-respect-mode&lt;/code&gt; (switching to English on &lt;code&gt;M-x&lt;/code&gt; and back) is enough for you, I don't think you need to force this one on.&lt;/p&gt;
&lt;h3&gt;
  
  
  Mixing English Words into Japanese with sis-global-inline-mode
&lt;/h3&gt;

&lt;p&gt;sis also has &lt;code&gt;sis-global-inline-mode&lt;/code&gt;, which is handy when you want to mix English words into the middle of Japanese text.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sis-global-inline-mode&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For example, when you want to write &lt;code&gt;日本語 some english text 日本語&lt;/code&gt;, you just type &lt;code&gt;日本語&amp;lt;space&amp;gt;some english text&amp;lt;space&amp;gt;&amp;lt;RET&amp;gt;日本語&lt;/code&gt;. The moment you put a space right after the Japanese, it automatically switches to English input just for that spot (highlighted with an overlay), and a trailing space, &lt;code&gt;RET&lt;/code&gt;, or moving the cursor outside the region returns you to Japanese input. There's no need to switch the IME manually.&lt;/p&gt;

&lt;p&gt;By default, only "English within Japanese" is handled (&lt;code&gt;sis-inline-with-english&lt;/code&gt;). If you also want "Japanese within English," add &lt;code&gt;(setq sis-inline-with-other t)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Whereas &lt;code&gt;sis-global-respect-mode&lt;/code&gt; / &lt;code&gt;sis-global-context-mode&lt;/code&gt; handle "switching" the input source, this one creates a temporary English region on the spot — that's the difference.&lt;/p&gt;

&lt;p&gt;I tried it for a day, but of course this behavior only applies inside Emacs, so character input behaves differently between Emacs and everything else. That subtle inconsistency was disorienting, so I stopped using it. Another reason is that even within Emacs, it didn't turn out to be all that intuitive.&lt;/p&gt;
&lt;h2&gt;
  
  
  Addendum (2026-06-25): There's a Mac port build of Emacs 30.2
&lt;/h2&gt;

&lt;p&gt;I was tipped off via Mastodon. I was already aware of &lt;a href="https://github.com/jdtsmith/emacs-mac" rel="noopener noreferrer"&gt;jdtsmith/emacs-mac&lt;/a&gt;, but since I prefer to keep customization to a minimum, since I prefer to keep customization to a minimum, it seemed like the right opportunity to make the move to the standard NS build.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://mastodon.social/@hmelman/116785840476414197" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmastodon.social%2Fpacks%2Fassets%2Flogo-DXQkHAe5.svg" height="65" class="m-0" width="65"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://mastodon.social/@hmelman/116785840476414197" rel="noopener noreferrer" class="c-link"&gt;
            hmelman: "@masutaka@mstdn.love Glad that's working for you.…" - Mastodon
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            &lt;a class="mentioned-user" href="https://dev.to/masutaka"&gt;@masutaka&lt;/a&gt;@mstdn.love Glad that's working for you.  FWIW I've been using this fork of emacs-mac and it's been working fine for me.  I'm now using the master branch (on Tahoe) so it's close to emacs 31. https://github.com/jdtsmith/emacs-mac
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmastodon.social%2Fpacks%2Fassets%2Ffavicon-16x16-74JBPGmr.png" width="16" height="16"&gt;
          mastodon.social
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;I also received — for the first time! — a comment through the &lt;code&gt;[ Send Message ]&lt;/code&gt; form at the bottom of the page, with the same content. Thank you! 😄&lt;/p&gt;

&lt;p&gt;It seems there are quite a few people using the Mac port build. I can understand folks in language regions that need an Input Method — but I'm curious what benefits the Mac port build offers to those who don't.&lt;/p&gt;

</description>
      <category>emacs</category>
    </item>
    <item>
      <title>Making Codex CLI and Codex.app Use mise-managed Ruby and Node.js</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sat, 30 May 2026 12:43:18 +0000</pubDate>
      <link>https://dev.to/masutaka/making-codex-cli-and-codexapp-use-mise-managed-ruby-and-nodejs-4fm6</link>
      <guid>https://dev.to/masutaka/making-codex-cli-and-codexapp-use-mise-managed-ruby-and-nodejs-4fm6</guid>
      <description>&lt;p&gt;I mostly use Claude Code, but lately I've been using Codex CLI and Codex.app (hereafter "Codex") more often too. My environment is macOS.&lt;/p&gt;

&lt;p&gt;However, after I started using &lt;a href="https://mise.jdx.dev/" rel="noopener noreferrer"&gt;mise&lt;/a&gt; in &lt;a href="https://dev.to/masutaka/migrating-from-asdf-and-direnv-to-mise-3nhi"&gt;[2026-03-29-1]&lt;/a&gt;, I ran into trouble because Codex wouldn't use the mise-managed Ruby, Node.js, and so on.&lt;/p&gt;

&lt;p&gt;Here's the state I was in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where ruby
&lt;span class="go"&gt;/usr/bin/ruby

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ruby &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;I solved it by adding the following to &lt;code&gt;~/.zshenv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# When Codex CLI and Codex.app run commands, .zshrc's mise activate zsh doesn't take effect,&lt;/span&gt;
&lt;span class="c"&gt;# so add mise shims to PATH.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CODEX_SANDBOX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;XDG_DATA_HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/mise/shims:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the state inside Codex after the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where ruby
&lt;span class="go"&gt;/Users/masutaka/.local/share/mise/shims/ruby
/usr/bin/ruby

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ruby &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +PRISM [arm64-darwin25]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Codex's Command Execution Environment Is a Sandbox
&lt;/h2&gt;

&lt;p&gt;I'd vaguely suspected this for a while, but it seems Codex's command execution environment runs inside a sandbox.&lt;/p&gt;

&lt;p&gt;You can spot the clues from within Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;CODEX
&lt;span class="go"&gt;CODEX_CI=1
CODEX_SANDBOX=seatbelt
CODEX_THREAD_ID=019e7806-6025-7c13-a3c6-a70d41c13905
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"seatbelt" refers to Apple Seatbelt, which appears to be macOS's sandboxing mechanism.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://zenn.dev/alesion/articles/37abbfb9f2fa4b" rel="noopener noreferrer"&gt;macOSで手軽にSandbox環境を構築できるApple Seatbeltの実践ガイド&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;しかしながら、Apple Seatbeltは公式にドキュメントを公開されておらず、非推奨とされています。一方で実際には多くのアプリケーションやツールで使用されています。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(English translation)&lt;/em&gt; However, Apple Seatbelt has no officially published documentation and is considered deprecated. Yet in practice, it's used by many applications and tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I see...&lt;/p&gt;

&lt;p&gt;According to this article, Claude Code also adopts Apple Seatbelt, and I confirmed that it can be enabled with &lt;code&gt;/sandbox&lt;/code&gt; (see the &lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Coming from a background of being used to Claude Code, Codex's sandbox is hard to wrap my head around, but the following article covers it in detail. Much appreciated.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://zenn.dev/4br4si0n/articles/8dfed37969e8cc" rel="noopener noreferrer"&gt;[Codex] sandbox実行の仕組みと設定方法を完全に理解する&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Codex also seems to restrict network access by default. Come to think of it, I remembered that I'd previously set the following in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sandbox_workspace_write]&lt;/span&gt;
&lt;span class="py"&gt;network_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Road to the Solution
&lt;/h2&gt;

&lt;p&gt;At first I tried to solve it with a setting like this in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, but it didn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[shell_environment_policy.set]&lt;/span&gt;
&lt;span class="py"&gt;PATH&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/Users/masutaka/.local/share/mise/shims/ruby:(the existing PATH settings hardcoded here)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I dug deeper, it occurred to me: "Doesn't &lt;code&gt;~/.zshenv&lt;/code&gt; get loaded every time, even inside the sandbox? If Codex-related environment variables are defined, I should be able to handle it in &lt;code&gt;~/.zshenv&lt;/code&gt;." That led me to the solution.&lt;/p&gt;

&lt;p&gt;The reason &lt;code&gt;eval "$(mise activate zsh)"&lt;/code&gt; in &lt;code&gt;~/.zshrc&lt;/code&gt; doesn't take effect is that &lt;code&gt;~/.zshrc&lt;/code&gt; is only loaded for interactive shells. Codex appears to run commands in non-interactive shells, so the settings in &lt;code&gt;~/.zshenv&lt;/code&gt;, which is loaded every time zsh starts, are what take effect.&lt;/p&gt;

&lt;p&gt;I also came up with the idea of moving &lt;code&gt;eval "$(mise activate zsh)"&lt;/code&gt; itself into &lt;code&gt;~/.zshenv&lt;/code&gt;, and it did actually work. However, I think this isn't a good approach. The &lt;a href="https://mise.jdx.dev/dev-tools/shims.html" rel="noopener noreferrer"&gt;activate PATH method&lt;/a&gt; is a mechanism that updates environment variables every time the prompt is displayed, so using it in a non-interactive shell with no prompt falls outside its intended purpose.&lt;/p&gt;

&lt;p&gt;mise also recommends shims for non-interactive environments (scripts, IDEs, CI), so I followed that here. It's the same approach of adding shims to &lt;code&gt;PATH&lt;/code&gt; that I adopted for Emacs in &lt;a href="https://dev.to/masutaka/migrating-from-asdf-and-direnv-to-mise-3nhi"&gt;[2026-03-29-1]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to avoid hardcoding &lt;code&gt;PATH&lt;/code&gt; into &lt;code&gt;~/.codex/config.toml&lt;/code&gt; since that would mean duplicate management with &lt;code&gt;~/.zshenv&lt;/code&gt;, so I'm glad it settled into a clean form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I summarized how to make Codex CLI and Codex.app use mise-managed Ruby and Node.js.&lt;/p&gt;

&lt;p&gt;I think mise is gradually gaining adoption, but I couldn't find an answer by Googling and wondered how everyone else handles this, so I wrote this short article. I hope it helps someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/f752b939d030779795b703001d3f65b45e7dd0d8/.zshenv" rel="noopener noreferrer"&gt;My current &lt;code&gt;~/.zshenv&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/f752b939d030779795b703001d3f65b45e7dd0d8/.zshrc" rel="noopener noreferrer"&gt;My current &lt;code&gt;~/.zshrc&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codex</category>
      <category>mise</category>
    </item>
    <item>
      <title>[2026-04] How I Currently Use Claude Code</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 12 Apr 2026 13:27:22 +0000</pubDate>
      <link>https://dev.to/masutaka/2026-04-how-i-currently-use-claude-code-2nck</link>
      <guid>https://dev.to/masutaka/2026-04-how-i-currently-use-claude-code-2nck</guid>
      <description>&lt;p&gt;Here's a summary of how I currently use &lt;a href="https://github.com/anthropics/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I previously introduced a Claude Code book in &lt;a href="https://masutaka.net/2026-01-16-1/" rel="noopener noreferrer"&gt;[2026-01-16-1]&lt;/a&gt;, but this time I'll write about my own workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Environment
&lt;/h2&gt;

&lt;p&gt;I work on my MacBook's built-in display. I'm not really someone who uses external monitors.&lt;/p&gt;

&lt;p&gt;I display the macOS built-in Terminal.app in full screen and use &lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;tmux&lt;/a&gt;. I don't split tmux windows—instead, I switch between tabs (windows). I haven't (yet?) switched to &lt;a href="https://github.com/ghostty-org/ghostty" rel="noopener noreferrer"&gt;Ghostty&lt;/a&gt; or &lt;a href="https://github.com/gnachman/iTerm2" rel="noopener noreferrer"&gt;iTerm2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My editor is &lt;a href="https://www.gnu.org/software/emacs/" rel="noopener noreferrer"&gt;Emacs&lt;/a&gt;. Before Claude Code came along, I had tried switching to VSCode several times and given up each time, but now I no longer need to think about it. What a relief.&lt;/p&gt;

&lt;p&gt;On a related note, Emacs's &lt;code&gt;global-auto-revert-mode&lt;/code&gt; is really handy. When Claude Code edits a file that's open in Emacs, the buffer automatically refreshes. Here's &lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.emacs.d/init.el#L1646-L1649" rel="noopener noreferrer"&gt;my configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the Claude Code model, I use Opus 4.6 (1M context) both personally and at work. I basically don't use Sonnet. My personal Claude plan alternates between Pro and Max, and on Max I get the 1M context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;I follow a 3-step workflow starting from GitHub Issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Creating a Work Plan
&lt;/h3&gt;

&lt;p&gt;The starting point is a custom skill (slash command) called &lt;a href="https://github.com/masutaka/dotfiles-public/tree/44515ec73117fa32f310358e003db22399d9f222/.claude/skills/create-plan-for-gh-issue/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;/create-plan-for-gh-issue&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It creates a work plan as a Markdown file based on the content of a GitHub Issue. The Issue can be for creating a PR, doing research, or anything else.&lt;/p&gt;

&lt;p&gt;The key is to write the necessary information in the Issue. It can go in the description or comments, and it's fine if the content is messy. It also reads attached images.&lt;/p&gt;

&lt;p&gt;After generating the plan, I go back and forth a few times to refine it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reviewing the Work Plan
&lt;/h3&gt;

&lt;p&gt;I have a separate Claude Code session review the plan. When I'm unsure, I also have Codex review it.&lt;/p&gt;

&lt;p&gt;I don't use Codex directly very often—instead, I use it through a custom skill called &lt;a href="https://github.com/masutaka/dotfiles-public/tree/44515ec73117fa32f310358e003db22399d9f222/.claude/skills/codex-discuss/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;/codex-discuss&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💡 &lt;code&gt;/codex-discuss&lt;/code&gt; will start a discussion on whatever you pass as an argument, and even without arguments, it figures out what to discuss on its own. When it's unclear what to discuss, it asks the user. I find it valuable for getting a third-party perspective, though the downside is that it's slow...&lt;/p&gt;

&lt;p&gt;Since the plan is a Markdown file, it's easy to review, and there's no problem even if the work carries over into the next week.&lt;/p&gt;

&lt;p&gt;Once the plan is finalized, I paste it as a comment on the GitHub Issue for record-keeping. I used to commit plans to &lt;code&gt;docs/plans/&lt;/code&gt; in the repository, but stopped because deleted filenames would show up in grep results as noise, and it just didn't feel right.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Executing and Reviewing the Work
&lt;/h3&gt;

&lt;p&gt;Plans are typically divided into Phases, with each Phase expected to result in a commit.&lt;/p&gt;

&lt;p&gt;For now, git operations are done manually by me. For suggesting commit messages, a custom skill called &lt;a href="https://github.com/masutaka/dotfiles-public/tree/44515ec73117fa32f310358e003db22399d9f222/.claude/skills/suggest-commit-message/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;/suggest-commit-message&lt;/code&gt;&lt;/a&gt; has been very useful.&lt;/p&gt;

&lt;p&gt;Before requesting a PR review, I run &lt;code&gt;/review&lt;/code&gt; in a separate Claude Code session, and if I'm still unsure, I discuss it with &lt;code&gt;/codex-discuss&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also tried &lt;a href="https://github.com/openai/codex-plugin-cc" rel="noopener noreferrer"&gt;openai/codex-plugin-cc&lt;/a&gt;, but it didn't quite fit my style.&lt;/p&gt;

&lt;p&gt;Additionally, for reviewing PRs from Dependabot, &lt;a href="https://github.com/masutaka/dotfiles-public/tree/44515ec73117fa32f310358e003db22399d9f222/.claude/skills/review-dependabot-pr/SKILL.md" rel="noopener noreferrer"&gt;&lt;code&gt;/review-dependabot-pr&lt;/code&gt;&lt;/a&gt; has been very handy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I've summarized how I use Claude Code as of April 2026.&lt;/p&gt;

&lt;p&gt;I try not to over-customize Claude Code. Looking at Twitter, it seems like everyone is doing advanced things, but I try not to be too influenced and instead trust my own intuition.&lt;/p&gt;

&lt;p&gt;The Claude Code configurations I introduced above are all gathered in the following dotfiles-public repository.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/dotfiles-public" rel="noopener noreferrer"&gt;
        dotfiles-public
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      masutaka public dot files
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I'll continue to refine my setup going forward, and I'm also planning to try out &lt;a href="https://openai.com/index/introducing-the-codex-app/" rel="noopener noreferrer"&gt;Codex.app&lt;/a&gt;, which has been getting good reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gleanings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Answering Yes/No Questions from Claude Code with y/n
&lt;/h3&gt;

&lt;p&gt;When Claude Code asks a Yes/No question, &lt;code&gt;y&lt;/code&gt; or &lt;code&gt;n&lt;/code&gt; is sufficient. It's a small thing, but it helps reduce token usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interview-Style Hearings from Claude Code
&lt;/h3&gt;

&lt;p&gt;When the task at hand is vague, having Claude Code conduct an "interview-style" hearing helps organize your thoughts.&lt;/p&gt;

&lt;p&gt;When there isn't enough information, Claude Code autonomously asks questions.&lt;/p&gt;

&lt;p&gt;I have the line "Use the AskUserQuestion tool when asking the user for choices or decisions" in my &lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/CLAUDE.md" rel="noopener noreferrer"&gt;~/.claude/CLAUDE.md&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep Claude Code Sessions Short
&lt;/h3&gt;

&lt;p&gt;I try not to extend Claude Code sessions too long, even when there's room left in the context window. My experience is that noise from intermediate steps creeps in and things start to go a bit off.&lt;/p&gt;

&lt;p&gt;By the way, I monitor the current context window (cw) usage via the Claude Code status line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9ekse0r5g1l60gdel9b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9ekse0r5g1l60gdel9b.png" alt="Claude Code status line" width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the configuration for the status line above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/settings.json#L49-L52" rel="noopener noreferrer"&gt;~/.claude/settings.json#L49-L52&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/scripts/statusline.js" rel="noopener noreferrer"&gt;~/.claude/scripts/statusline.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Notification Settings from Claude Code
&lt;/h3&gt;

&lt;p&gt;To avoid missing when Claude Code finishes processing, I have a setup that sends notifications to both the tmux status line and the macOS Notification Center.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flenz871l15zb6ursz6ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flenz871l15zb6ursz6ws.png" alt="tmux status line" width="698" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh8dmx3qv9t2igdocuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh8dmx3qv9t2igdocuw.png" alt="macOS Notification Center" width="500" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An article by Yoshida-san, a former colleague, was very helpful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/milmed/articles/845f47259eca07" rel="noopener noreferrer"&gt;Reliably Detecting "My Turn" in Claude Code - Zenn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yoshida-san's article explains in detail how spinner display and unread markers work. My setup is based on that, with the following adjustments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Common:

&lt;ul&gt;
&lt;li&gt;Added a &lt;code&gt;Notification&lt;/code&gt; hook so I can be alerted by tmux's unread marker and a notification sound even during idle states like waiting for tool approval&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;tmux:

&lt;ul&gt;
&lt;li&gt;Added a &lt;code&gt;SessionEnd&lt;/code&gt; hook to clean up the spinner, fixing the issue where the spinner kept spinning after exiting with &lt;code&gt;/exit&lt;/code&gt; or &lt;code&gt;C-c C-c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Introduced a heartbeat file that monitors mtime as a TTL, so the spinner also auto-stops when interrupted with Esc&lt;/li&gt;
&lt;li&gt;Changed the unread indicator dot color from red to cyan, as cyan looks more like an "unread" marker&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;macOS Notification Center:

&lt;ul&gt;
&lt;li&gt;Used different sounds for &lt;code&gt;Notification&lt;/code&gt; (Funk) and &lt;code&gt;Stop&lt;/code&gt; (Glass), so I can distinguish "attention needed" from "completed" by ear&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;(tmux:N)&lt;/code&gt; with the window number in the notification body, so I know which window to switch back to&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here's the actual code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/settings.json#L53-L122" rel="noopener noreferrer"&gt;~/.claude/settings.json#L53-L122&lt;/a&gt; (hooks configuration)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/scripts/tmux-spinner.sh" rel="noopener noreferrer"&gt;~/.claude/scripts/tmux-spinner.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.claude/scripts/macos-notify.sh" rel="noopener noreferrer"&gt;~/.claude/scripts/macos-notify.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/44515ec73117fa32f310358e003db22399d9f222/.config/tmux/tmux.conf#L22-L32" rel="noopener noreferrer"&gt;~/.config/tmux/tmux.conf#L22-L32&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>tmux</category>
      <category>macos</category>
      <category>emacs</category>
    </item>
    <item>
      <title>Migrating from asdf and direnv to mise</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 29 Mar 2026 09:27:47 +0000</pubDate>
      <link>https://dev.to/masutaka/migrating-from-asdf-and-direnv-to-mise-3nhi</link>
      <guid>https://dev.to/masutaka/migrating-from-asdf-and-direnv-to-mise-3nhi</guid>
      <description>&lt;p&gt;For managing versions of development tools like Ruby and Node.js, I had gone through &lt;code&gt;*env&lt;/code&gt; tools like &lt;a href="https://github.com/rbenv/rbenv" rel="noopener noreferrer"&gt;rbenv&lt;/a&gt; and &lt;a href="https://github.com/nodenv/nodenv" rel="noopener noreferrer"&gt;nodenv&lt;/a&gt;, then switched to &lt;a href="https://github.com/asdf-vm/asdf" rel="noopener noreferrer"&gt;asdf&lt;/a&gt; in 2019. For environment variable management, I had been using &lt;a href="https://github.com/direnv/direnv" rel="noopener noreferrer"&gt;direnv&lt;/a&gt; since even earlier—2014.&lt;/p&gt;

&lt;p&gt;Recently, a tool called &lt;a href="https://mise.jdx.dev/" rel="noopener noreferrer"&gt;mise&lt;/a&gt; has been gaining attention. I wasn't particularly having issues, but out of curiosity and the motivation to reduce the number of tools—since I heard mise also has direnv-like functionality—I decided to make the switch. My environment is macOS.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jdx" rel="noopener noreferrer"&gt;
        jdx
      &lt;/a&gt; / &lt;a href="https://github.com/jdx/mise" rel="noopener noreferrer"&gt;
        mise
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      dev tools, env vars, task runner
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What is mise?
&lt;/h2&gt;

&lt;p&gt;mise (pronounced "meez") is a tool that handles both development tool version management and environment variable management in one place. It provides asdf-compatible runtime management along with direnv-equivalent environment variable management.&lt;/p&gt;

&lt;p&gt;Here are the notable features I found after using it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can read &lt;code&gt;.tool-versions&lt;/code&gt; (&lt;code&gt;.ruby-version&lt;/code&gt; etc. requires configuration, described later)&lt;/li&gt;
&lt;li&gt;Can define environment variables and task runners in &lt;code&gt;mise.toml&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/jdx/mise/blob/v2026.3.17/mise.toml" rel="noopener noreferrer"&gt;official mise.toml&lt;/a&gt; may be a good reference&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Supports &lt;code&gt;mise.local.toml&lt;/code&gt; for local settings meant to be gitignored. This allows individual adoption even before a team officially adopts mise&lt;/li&gt;
&lt;li&gt;Well-organized tool management experience

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mise install&lt;/code&gt; installs all tools from &lt;code&gt;.tool-versions&lt;/code&gt; in one command&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mise list&lt;/code&gt; shows all tool versions and their source files at a glance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Did for the Migration
&lt;/h2&gt;

&lt;p&gt;The official documentation has a &lt;a href="https://mise.jdx.dev/faq.html#how-do-i-migrate-from-asdf" rel="noopener noreferrer"&gt;migration guide from asdf&lt;/a&gt;, so start there. Below are the specific steps for my environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Homebrew Package Changes
&lt;/h3&gt;

&lt;p&gt;I uninstalled asdf and direnv, and installed mise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew uninstall &lt;span class="nt"&gt;--force&lt;/span&gt; asdf
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew uninstall &lt;span class="nt"&gt;--force&lt;/span&gt; direnv
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;mise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. zsh Configuration Changes
&lt;/h3&gt;

&lt;p&gt;I removed all asdf settings from &lt;code&gt;~/.zshenv&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-MY_ASDF_CONFIG_HOME="${XDG_CONFIG_HOME}/asdf"
-export ASDF_CONFIG_FILE="${MY_ASDF_CONFIG_HOME}/asdfrc"
-export ASDF_DATA_DIR="${XDG_DATA_HOME}/asdf"
-export ASDF_GEM_DEFAULT_PACKAGES_FILE="${MY_ASDF_CONFIG_HOME}/default-gems"
-export ASDF_NPM_DEFAULT_PACKAGES_FILE="${MY_ASDF_CONFIG_HOME}/default-npm-packages"
-export ASDF_PERL_DEFAULT_PACKAGES_FILE="${MY_ASDF_CONFIG_HOME}/default-perl-modules"
-export ASDF_PYTHON_DEFAULT_PACKAGES_FILE="${MY_ASDF_CONFIG_HOME}/default-python-packages"
-export ASDF_RUBY_BUILD_VERSION=master
-PATH=$ASDF_DATA_DIR/shims:$PATH
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also removed the direnv settings from &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-eval "$(direnv hook zsh)"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, I added the mise configuration to &lt;code&gt;~/.zshrc&lt;/code&gt;. Since the activation mechanism enabled by this setting handles PATH management, no configuration in &lt;code&gt;~/.zshenv&lt;/code&gt; is needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mise activate zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the actual files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/dotfiles-public/blob/df434ff7b79eb4f61817501a3ca21c6c3e37668d/.zshenv" rel="noopener noreferrer"&gt;&lt;code&gt;~/.zshenv&lt;/code&gt;&lt;/a&gt; (no mise settings)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/df434ff7b79eb4f61817501a3ca21c6c3e37668d/.zshrc#L238-L252" rel="noopener noreferrer"&gt;&lt;code&gt;~/.zshrc&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the asdf-era environment variables gone, my zsh configuration became cleaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Installing Tools
&lt;/h3&gt;

&lt;p&gt;I reinstalled the tools that asdf had been managing using &lt;code&gt;mise install&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.tool-versions
&lt;span class="go"&gt;nodejs 24.14.0
ruby 4.0.2

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mise &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mise list
&lt;span class="go"&gt;Tool       Version   Source            Requested
node       24.14.0   ~/.tool-versions  24.14.0
ruby       4.0.2     ~/.tool-versions  4.0.2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Migrating default_packages_file
&lt;/h3&gt;

&lt;p&gt;With asdf, I managed default_packages_file via environment variables in &lt;code&gt;~/.zshenv&lt;/code&gt;. With mise, I consolidated them in &lt;code&gt;~/.config/mise/config.toml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[settings.node]&lt;/span&gt;
&lt;span class="py"&gt;default_packages_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"~/.config/mise/default-npm-packages"&lt;/span&gt;

&lt;span class="nn"&gt;[settings.ruby]&lt;/span&gt;
&lt;span class="py"&gt;default_packages_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"~/.config/mise/default-gems"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the migration, I discovered a bug where &lt;code&gt;~&lt;/code&gt; in Node.js's &lt;code&gt;default_packages_file&lt;/code&gt; wasn't being expanded, causing default packages not to be installed. I reported it in &lt;a href="https://github.com/jdx/mise/discussions/8606" rel="noopener noreferrer"&gt;Discussion#8606&lt;/a&gt;, and it was fixed in &lt;a href="https://github.com/jdx/mise/pull/8709" rel="noopener noreferrer"&gt;PR #8709&lt;/a&gt; 🙏 This is resolved in &lt;a href="https://github.com/jdx/mise/releases/tag/v2026.3.11" rel="noopener noreferrer"&gt;v2026.3.11&lt;/a&gt; and later.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Configuring .ruby-version and .node-version Support
&lt;/h3&gt;

&lt;p&gt;Neither asdf nor mise reads &lt;code&gt;.ruby-version&lt;/code&gt; or &lt;code&gt;.node-version&lt;/code&gt; by default. With asdf, you set &lt;code&gt;legacy_version_file = yes&lt;/code&gt; in &lt;code&gt;asdfrc&lt;/code&gt;, but with mise, the following configuration is needed. I added it to the repository's &lt;code&gt;mise.toml&lt;/code&gt; or &lt;code&gt;mise.local.toml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[settings]&lt;/span&gt;
&lt;span class="py"&gt;idiomatic_version_file_enable_tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Didn't Migrate
&lt;/h2&gt;

&lt;p&gt;Some settings were either unnecessary to migrate or I chose not to migrate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby pre-install hook

&lt;ul&gt;
&lt;li&gt;With asdf, I set &lt;code&gt;RUBY_CONFIGURE_OPTS&lt;/code&gt; via &lt;a href="https://github.com/masutaka/dotfiles-public/blob/9aa6ff2e058dc2c87aae77bc8c640399cac34596/.config/asdf/asdfrc#L2" rel="noopener noreferrer"&gt;&lt;code&gt;pre_asdf_install_ruby&lt;/code&gt;&lt;/a&gt;, but this turned out to be unnecessary with mise&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ASDF_RUBY_BUILD_VERSION=master&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;mise &lt;a href="https://github.com/jdx/mise/blob/v2026.3.17/src/plugins/core/ruby.rs#L87-L125" rel="noopener noreferrer"&gt;automatically fetches the latest version of ruby-build&lt;/a&gt; when installing Ruby, so this setting is unnecessary&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Perl default-perl-modules

&lt;ul&gt;
&lt;li&gt;mise doesn't support &lt;code&gt;default_packages_file&lt;/code&gt; for Perl (see the appendix below for a workaround)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;direnvrc PATH_add&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I had a configuration to automatically add &lt;code&gt;./bin&lt;/code&gt; to PATH for Ruby projects with a Gemfile.lock&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;mise doesn't have an equivalent global feature, but I decided to configure it per-project in &lt;code&gt;mise.toml&lt;/code&gt; when needed&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[env]&lt;/span&gt;
&lt;span class="py"&gt;_.path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./bin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I replaced two tools—asdf + direnv—with just mise. The zsh configuration became cleaner, and being able to consolidate tool versions and environment variables in &lt;code&gt;mise.toml&lt;/code&gt; is a nice benefit.&lt;/p&gt;

&lt;p&gt;There were some gotchas like the Emacs compatibility issue described in the appendix below, but overall I'm happy with the migration.&lt;/p&gt;

&lt;p&gt;Also, with direnv no longer needed, I was able to migrate from &lt;code&gt;.envrc&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt;, which later enabled me to set up &lt;code&gt;.env&lt;/code&gt; mounting with &lt;a href="https://developer.1password.com/docs/environments/" rel="noopener noreferrer"&gt;1Password Environments&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mise.jdx.dev/getting-started.html" rel="noopener noreferrer"&gt;mise Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techracho.bpsinc.jp/hachi8833/2025_09_29/153488" rel="noopener noreferrer"&gt;miseは便利: タスクランナー兼ツールバージョン管理&amp;amp;環境変数管理ツール - TechRacho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.1password.com/docs/environments/" rel="noopener noreferrer"&gt;1Password Environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.classmethod.jp/articles/1password-environments-env-management/" rel="noopener noreferrer"&gt;1Password Environmentsで.envファイルを管理できるようになったので試してみた - DevelopersIO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixing ruby-lsp Not Working in Emacs
&lt;/h3&gt;

&lt;p&gt;I encountered an issue where &lt;a href="https://github.com/Shopify/ruby-lsp" rel="noopener noreferrer"&gt;ruby-lsp&lt;/a&gt; wouldn't work in Emacs for repositories using a Ruby version different from the global one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mise activate zsh&lt;/code&gt; takes a hook-based approach, using zsh's &lt;code&gt;chpwd&lt;/code&gt; hook to switch tool versions when changing directories. However, Emacs doesn't execute this hook, so mise's version switching doesn't work.&lt;/p&gt;

&lt;p&gt;mise offers an alternative to hook-based activation called shims.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;activate&lt;/th&gt;
&lt;th&gt;shims&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Interactive shell&lt;/td&gt;
&lt;td&gt;Non-interactive (IDE, Emacs, scripts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hooks like &lt;code&gt;cd&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Triggered&lt;/td&gt;
&lt;td&gt;Not triggered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;which&lt;/code&gt; result&lt;/td&gt;
&lt;td&gt;Returns the actual tool path&lt;/td&gt;
&lt;td&gt;Returns the shim path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Initially, I added &lt;code&gt;eval "$(mise activate zsh --shims)"&lt;/code&gt; to &lt;code&gt;~/.zshrc&lt;/code&gt; to enable both, but the &lt;a href="https://mise.jdx.dev/dev-tools/shims.html" rel="noopener noreferrer"&gt;mise documentation&lt;/a&gt; assumes activate and shims are mutually exclusive.&lt;/p&gt;

&lt;p&gt;I ultimately settled on the following setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eval "$(mise activate zsh)"&lt;/code&gt; in &lt;code&gt;~/.zshrc&lt;/code&gt; (for interactive shell)&lt;/li&gt;
&lt;li&gt;Add the shims directory to PATH only when launching Emacs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;emacs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LC_COLLATE=C PATH='&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;XDG_DATA_HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mise/shims:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;' emacs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a separation of concerns: regular zsh sessions use hooks only, while Emacs uses shims.&lt;/p&gt;

&lt;p&gt;Compared to asdf's simpler shims-only design, this setup might look more complex at first glance. However, I understand this is a tradeoff stemming from mise being a replacement for both asdf and direnv. The real-time reflection of environment variable changes in the interactive shell via &lt;code&gt;mise activate&lt;/code&gt; is something shims alone cannot achieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workaround for Perl default-packages
&lt;/h3&gt;

&lt;p&gt;mise's default-packages mechanism is a language-specific feature hardcoded in core plugins (Go, Node.js, Python, Ruby). Perl is not a core plugin—it's installed via the Aqua backend—so it's not covered.&lt;/p&gt;

&lt;p&gt;As a workaround, you can run &lt;code&gt;cpanm&lt;/code&gt; in &lt;code&gt;mise.toml&lt;/code&gt;'s &lt;code&gt;[hooks]&lt;/code&gt; postinstall&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. This way, &lt;code&gt;mise install&lt;/code&gt; alone sets up both Perl and CPAN modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[settings]&lt;/span&gt;
&lt;span class="py"&gt;experimental&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[hooks]&lt;/span&gt;
&lt;span class="py"&gt;postinstall&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cpanm CGI HTML::Template"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that hooks is an experimental feature, so &lt;code&gt;experimental = true&lt;/code&gt; is required. Also, postinstall runs on every tool installation, not just Perl, so this hook will be triggered every time you run &lt;code&gt;mise install&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update: 2026-06-21
&lt;/h4&gt;

&lt;p&gt;Later, in mise &lt;a href="https://github.com/jdx/mise/releases/tag/v2026.6.6" rel="noopener noreferrer"&gt;v2026.6.6&lt;/a&gt;, hooks graduated from experimental, so &lt;code&gt;experimental = true&lt;/code&gt; is no longer required. You can omit the &lt;code&gt;[settings]&lt;/code&gt; section above.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Relationship Between mise and aqua
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/aquaproj/aqua" rel="noopener noreferrer"&gt;aqua&lt;/a&gt; is a tool specialized in version management for CLI tools (terraform, gh, etc.), with robust security through checksum verification. While mise and aqua cover different scopes, mise can use aqua-registry as a backend (via the &lt;code&gt;aqua:&lt;/code&gt; prefix), allowing you to install tools managed by aqua through mise.&lt;/p&gt;

&lt;p&gt;They're not identical, but for personal dotfiles management, I feel mise alone can sufficiently cover aqua-like use cases. If your team uses aqua or you need checksum verification for supply chain security in CI/CD, there's still value in using aqua separately.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://mise.jdx.dev/hooks.html" rel="noopener noreferrer"&gt;Hooks | mise-en-place&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>mise</category>
      <category>asdf</category>
      <category>direnv</category>
      <category>emacs</category>
    </item>
    <item>
      <title>Created a GitHub Actions Reusable Workflows Repository for Personal Use</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Mon, 23 Feb 2026 10:00:48 +0000</pubDate>
      <link>https://dev.to/masutaka/created-a-github-reusable-workflows-repository-for-personal-use-28mo</link>
      <guid>https://dev.to/masutaka/created-a-github-reusable-workflows-repository-for-personal-use-28mo</guid>
      <description>&lt;p&gt;I created a GitHub Actions Reusable Workflows repository for my personal use.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/actions" rel="noopener noreferrer"&gt;
        actions
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GitHub Actions reusable workflows
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  What Are GitHub Actions Reusable Workflows?
&lt;/h2&gt;

&lt;p&gt;GitHub Actions' &lt;a href="https://docs.github.com/actions/how-tos/reuse-automations/reuse-workflows" rel="noopener noreferrer"&gt;reusable workflows&lt;/a&gt; is a mechanism that allows workflow files to be called from other repositories.&lt;/p&gt;

&lt;p&gt;For example, you can call a workflow from another repository like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;masutaka/actions/.github/workflows/some-workflow.yml@main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since you can consolidate common processes in one place, it saves you the trouble of managing the same workflow across multiple repositories.&lt;/p&gt;

&lt;p&gt;There are some limitations and caveats to be aware of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reusable workflows in private repositories cannot be called from public repositories&lt;/li&gt;
&lt;li&gt;Reusable workflows in private repositories can only be called from other repositories within the same org/user (different orgs are not allowed, and the Access policy setting of the called repository must be configured)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;env&lt;/code&gt; context at the calling workflow level is not propagated to the called workflow&lt;/li&gt;
&lt;li&gt;Environment secrets cannot be passed (only regular secrets can be passed via &lt;code&gt;secrets: inherit&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reusable workflows always run at the job level (they cannot be used as steps). This means a separate runner starts each time they are called, and the filesystem cannot be shared between jobs. For private repositories, this also increases Actions minutes consumption. If you want to reuse at the step level, you need to use a &lt;a href="https://docs.github.com/actions/sharing-automations/creating-actions/creating-a-composite-action" rel="noopener noreferrer"&gt;composite action&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since &lt;code&gt;masutaka/actions&lt;/code&gt; is a public repository, limitations 1 and 2 do not apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Included Workflows
&lt;/h2&gt;

&lt;p&gt;Currently, I have included the following reusable workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/add_assignee_to_pr.yml" rel="noopener noreferrer"&gt;add_assignee_to_pr.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/add_assignee_to_pr.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Sets the PR creator as the assignee when a PR is created&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/codeql.yml" rel="noopener noreferrer"&gt;codeql.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/codeql.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Detects languages from changed files and runs CodeQL analysis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/codeql_core.yml" rel="noopener noreferrer"&gt;codeql_core.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/codeql_core.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Runs CodeQL analysis for specified languages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/create_gh_issue.yml" rel="noopener noreferrer"&gt;create_gh_issue.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/create_gh_issue.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Creates a GitHub Issue from a template&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/dependency_review.yml" rel="noopener noreferrer"&gt;dependency_review.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/dependency_review.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Reviews PR dependencies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/pushover.yml" rel="noopener noreferrer"&gt;pushover.yml&lt;/a&gt; (&lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/docs/pushover.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) - Sends &lt;a href="https://pushover.net/" rel="noopener noreferrer"&gt;Pushover&lt;/a&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; notifications for workflow failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I referenced &lt;a href="https://github.com/mdn/workflows" rel="noopener noreferrer"&gt;mdn/workflows&lt;/a&gt; for the documentation structure, documenting each workflow's usage in Markdown files under &lt;code&gt;docs/&lt;/code&gt; and linking to them from README.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Created This
&lt;/h2&gt;

&lt;p&gt;I had been using &lt;a href="https://github.com/route06/actions" rel="noopener noreferrer"&gt;route06/actions&lt;/a&gt;, a repository where I had been a maintainer at my previous job, even after leaving the company.&lt;/p&gt;

&lt;p&gt;However, there were a few things I wanted to customize for personal use, and creating a new &lt;code&gt;pushover.yml&lt;/code&gt; workflow prompted me to copy the necessary workflow files and create my own repository.&lt;/p&gt;

&lt;p&gt;Up until then, the same &lt;code&gt;pushover.yml&lt;/code&gt; file was duplicated across my personal repositories, but I took this opportunity to consolidate everything into &lt;code&gt;masutaka/actions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Licenses
&lt;/h2&gt;

&lt;p&gt;Both repositories are under the MIT License. For workflow files copied over, I added attribution to the original repository at the top of each file like this:&lt;/p&gt;

&lt;p&gt;Example: &lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/.github/workflows/codeql.yml#L1-L3" rel="noopener noreferrer"&gt;codeql.yml&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Derived from https://github.com/route06/actions/blob/main/.github/workflows/codeql.yml&lt;/span&gt;
&lt;span class="c1"&gt;# Copyright (c) 2024 ROUTE06, Inc.&lt;/span&gt;
&lt;span class="c1"&gt;# Licensed under the MIT License.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also included both copyrights in the &lt;a href="https://github.com/masutaka/actions/blob/e69f6ebf3a73c22a7d963ac0dea51d2cee01a5c2/LICENSE" rel="noopener noreferrer"&gt;LICENSE&lt;/a&gt; file. I believe this satisfies the requirements of the MIT License.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copyright (c) Takashi Masuda
Copyright (c) 2024 ROUTE06, Inc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Until now, similar workflow files were scattered across my personal repositories, and I had to update multiple repositories every time a change was needed. By consolidating them into &lt;code&gt;masutaka/actions&lt;/code&gt;, changes can now be made in one place.&lt;/p&gt;

&lt;p&gt;As my personal repositories continue to grow, I plan to keep consolidating shareable workflows here going forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/actions/how-tos/reuse-automations/reuse-workflows" rel="noopener noreferrer"&gt;Reuse workflows - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/actions/sharing-automations/creating-actions/creating-a-composite-action" rel="noopener noreferrer"&gt;Creating a composite action - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mdn/workflows" rel="noopener noreferrer"&gt;mdn/workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/route06/actions" rel="noopener noreferrer"&gt;route06/actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;A push notification service for iOS/Android&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>githubactions</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Created a Rails 8 Learning Repository and Started Recovering from a 6-Year Blank with Rails Guides and Claude Code</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Tue, 30 Dec 2025 05:17:38 +0000</pubDate>
      <link>https://dev.to/masutaka/created-a-rails-8-learning-repository-and-started-recovering-from-a-6-year-blank-with-rails-guides-jcc</link>
      <guid>https://dev.to/masutaka/created-a-rails-8-learning-repository-and-started-recovering-from-a-6-year-blank-with-rails-guides-jcc</guid>
      <description>&lt;p&gt;Last month I changed jobs and returned to Rails development after a long time.&lt;/p&gt;

&lt;p&gt;With a 6-year gap and only one month available for learning, I needed to efficiently grasp Rails 8's new features and refresh my memory of existing ones.&lt;/p&gt;

&lt;p&gt;So I decided to learn by building a working app together with Claude Code while referencing the Rails Guides. I temporarily subscribed to Claude Code's Max plan at &lt;code&gt;USD 100/mo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the repository I created:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/trial-rails8" rel="noopener noreferrer"&gt;
        trial-rails8
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Rails ガイドを元にした素振り
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  Learning Process
&lt;/h2&gt;

&lt;p&gt;I used the &lt;a href="https://railsguides.jp/v8.0/" rel="noopener noreferrer"&gt;Rails Guides&lt;/a&gt; as my main reference material.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Reviewing the Basics with Rails Guides
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://railsguides.jp/v8.0/getting_started.html" rel="noopener noreferrer"&gt;Getting Started with Rails&lt;/a&gt;, I created &lt;code&gt;/products&lt;/code&gt;. I mainly learned about Active Storage (image uploads) and Action Text (rich text).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Learning Hotwire and Solid Trifecta
&lt;/h3&gt;

&lt;p&gt;To learn Hotwire and Solid Trifecta, I created &lt;code&gt;/chat&lt;/code&gt; and &lt;code&gt;/posts&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/chat&lt;/code&gt;: Learned Action Cable, Solid Cable, and Stimulus&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/posts&lt;/code&gt;: Learned Turbo Streams/Frames, Active Job, and Solid Queue. Added comment, follow, and notification features to a typical blog functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned through AI pair programming with Claude Code. The specific workflow was as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a GitHub Issue (e.g., &lt;a href="https://github.com/masutaka/trial-rails8/issues/14" rel="noopener noreferrer"&gt;#14 Follow Feature&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Have Claude Code create a plan document (e.g., &lt;a href="https://github.com/masutaka/trial-rails8/blob/19201432e4806aeadf22b9f27d7febca32691b77/docs/plan-for-issue-14.md" rel="noopener noreferrer"&gt;docs/plan-for-issue-14.md&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Have it create a PR based on the plan while understanding the implementation (e.g., &lt;a href="https://github.com/masutaka/trial-rails8/pull/38" rel="noopener noreferrer"&gt;#38&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also utilized AI reviews from CodeRabbit. It pointed out N+1 queries and suggested adding tests, allowing me to maintain quality even in solo development. It's great that it's free for OSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Features I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hotwire
&lt;/h3&gt;

&lt;p&gt;Hotwire is a framework introduced in Rails 7 for achieving SPA-like UX. Hotwire consists of Turbo and Stimulus.&lt;/p&gt;

&lt;p&gt;Turbo is further composed of three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turbo Drive: Speeds up page transitions (fetches and replaces HTML on link clicks)&lt;/li&gt;
&lt;li&gt;Turbo Frames: Updates parts of a page (inline editing of comments, etc.)&lt;/li&gt;
&lt;li&gt;Turbo Streams: Updates multiple locations simultaneously (updates list and count when adding comments)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stimulus is a JavaScript framework, but it's a very thin layer for adding minimal JS with HTML as the starting point. I used it for UI control in the chat feature.&lt;/p&gt;

&lt;p&gt;I utilized Turbo Streams in the comment feature. When a comment is posted, model callbacks broadcast it to other users' browsers in real-time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/comment.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

  &lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Append to comment list&lt;/span&gt;
    &lt;span class="n"&gt;broadcast_append_to&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"comments/comment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;comment: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;allow_actions: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# Update comment count&lt;/span&gt;
    &lt;span class="n"&gt;broadcast_replace_to&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"comment_count_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"posts/comment_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solid Trifecta
&lt;/h3&gt;

&lt;p&gt;"Solid Trifecta" introduced in Rails 8 eliminates the need for external middleware like Redis or Memcached.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solid Queue: Database-based Active Job backend&lt;/li&gt;
&lt;li&gt;Solid Cable: Database-based Action Cable adapter&lt;/li&gt;
&lt;li&gt;Solid Cache: Database-based cache store (not used this time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used Active Job + Solid Queue for the scheduled post feature. When you set a future datetime for &lt;code&gt;published_at&lt;/code&gt;, a job executes at that time to publish the article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/publish_post_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublishPostJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;

    &lt;span class="c1"&gt;# Idempotency: Skip if already published&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;published&lt;/span&gt;

    &lt;span class="c1"&gt;# Skip if publish datetime was changed&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;published_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;scheduled_at&lt;/span&gt;

    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;published: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check job execution status with Mission Control Jobs. Access &lt;a href="http://localhost:3000/jobs" rel="noopener noreferrer"&gt;http://localhost:3000/jobs&lt;/a&gt; in development to view job history and queue status in a Web UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Eager Loading: &lt;code&gt;includes&lt;/code&gt;/&lt;code&gt;preload&lt;/code&gt;/&lt;code&gt;eager_load&lt;/code&gt; to prevent N+1 queries&lt;/li&gt;
&lt;li&gt;has_secure_password + rate_limit: User authentication and rate limiting&lt;/li&gt;
&lt;li&gt;Counter Cache: Efficient retrieval of comment counts (&lt;code&gt;belongs_to :post, counter_cache: true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Foreign key constraint &lt;code&gt;on_delete: :cascade&lt;/code&gt;: Automatically deletes child records when parent is deleted. Used alongside Rails' &lt;code&gt;dependent: :destroy&lt;/code&gt; to maintain DB-level integrity&lt;/li&gt;
&lt;li&gt;Shallow Routing: Specifying &lt;code&gt;shallow: true&lt;/code&gt; for nested resources shortens URLs for member actions (show/edit/update/destroy)&lt;/li&gt;
&lt;li&gt;Polymorphic Association: Used for notification feature. Manages follow and comment notifications uniformly&lt;/li&gt;
&lt;li&gt;Self-Referential Association: Used for follow feature. Expresses many-to-many relationships with a single &lt;code&gt;follows&lt;/code&gt; table&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Despite a 6-year gap, I was able to learn efficiently with Rails Guides and Claude Code.&lt;/p&gt;

&lt;p&gt;Note that I didn't write many tests this time. The goal was to learn Rails features, so I'll save test learning for another time.&lt;/p&gt;

&lt;p&gt;My mental model was stuck around the time of "temporarily disabling Turbolinks...", so I was quite impressed by Hotwire's ability to achieve SPA-like UX with almost no JavaScript. My memory of Rails was that it often served as an API server or adopted React for the frontend, but I now think Hotwire should be considered first.&lt;/p&gt;

&lt;p&gt;Solid Queue and Solid Cable are also nice because you can start with just RDS, reducing the number of components.&lt;/p&gt;

&lt;p&gt;I'll continue working through the open Issues while continuing to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire Official Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://turbo.hotwired.dev/handbook/introduction" rel="noopener noreferrer"&gt;Turbo Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stimulus.hotwired.dev/handbook/introduction" rel="noopener noreferrer"&gt;Stimulus Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/solid_queue" rel="noopener noreferrer"&gt;Solid Queue (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/solid_cable" rel="noopener noreferrer"&gt;Solid Cable (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/mission_control-jobs" rel="noopener noreferrer"&gt;Mission Control Jobs (GitHub)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>github</category>
      <category>claudecode</category>
      <category>coderabbit</category>
    </item>
    <item>
      <title>Finally Modernized My Emacs Setup with LSP + Tree-sitter</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Mon, 24 Nov 2025 10:13:19 +0000</pubDate>
      <link>https://dev.to/masutaka/finally-modernized-my-emacs-setup-with-lsp-tree-sitter-125</link>
      <guid>https://dev.to/masutaka/finally-modernized-my-emacs-setup-with-lsp-tree-sitter-125</guid>
      <description>&lt;p&gt;In recent years, I've been mainly writing LookML, SQL, HCL, and other languages, with fewer opportunities to write programming languages like Ruby. However, I've recently returned to Rails development this month.&lt;/p&gt;

&lt;p&gt;So, I finally got around to setting up LSP (&lt;a href="https://en.wikipedia.org/wiki/Language_Server_Protocol" rel="noopener noreferrer"&gt;Language Server Protocol&lt;/a&gt;) in Emacs.&lt;/p&gt;

&lt;p&gt;During this process, I also learned about &lt;a href="https://en.wikipedia.org/wiki/Tree-sitter%20%28parser%20generator%29" rel="noopener noreferrer"&gt;Tree-sitter&lt;/a&gt; and configured it as well.&lt;/p&gt;

&lt;p&gt;※ LSP is an advanced code assistance mechanism provided by Language Servers, while Tree-sitter is a parsing engine that quickly generates &lt;a href="https://en.wikipedia.org/wiki/Abstract%20syntax%20tree" rel="noopener noreferrer"&gt;AST&lt;/a&gt; from code to enable highlighting and structural editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emacs Version I'm Using
&lt;/h2&gt;

&lt;p&gt;I'm using Emacs 29.1 instead of the latest 30.2 for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport" rel="noopener noreferrer"&gt;railwaycat/homebrew-emacsmacport&lt;/a&gt; with &lt;a href="https://bitbucket.org/mituharu/emacs-mac/" rel="noopener noreferrer"&gt;Mac port patch&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L1134-L1210" rel="noopener noreferrer"&gt;I rely on some useful features provided by the Mac port patch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The latest Mac port patch is &lt;a href="https://bitbucket.org/mituharu/emacs-mac/src/emacs-29.4-mac-10.1/" rel="noopener noreferrer"&gt;emacs-29.4-mac-10.1&lt;/a&gt; and hasn't caught up to version 30&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/releases/tag/emacs-29.4-mac-10.1" rel="noopener noreferrer"&gt;railwaycat/&lt;/a&gt;&lt;a href="mailto:homebrew-emacsmacport@emacs-29.4-mac-10.1"&gt;homebrew-emacsmacport@emacs-29.4-mac-10.1&lt;/a&gt; with the patch has issue &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/issues/389" rel="noopener noreferrer"&gt;#389&lt;/a&gt; on my environment&lt;/li&gt;

&lt;li&gt;Therefore, I'm using &lt;a href="https://github.com/railwaycat/homebrew-emacsmacport/releases/tag/emacs-29.1-mac-10.0" rel="noopener noreferrer"&gt;railwaycat/&lt;/a&gt;&lt;a href="mailto:homebrew-emacsmacport@emacs-29.1-mac-10.0"&gt;homebrew-emacsmacport@emacs-29.1-mac-10.0&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducing LSP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adopted lsp-mode
&lt;/h3&gt;

&lt;p&gt;I compared &lt;a href="https://github.com/joaotavora/eglot" rel="noopener noreferrer"&gt;eglot&lt;/a&gt;, which is standard in Emacs 29, with the third-party &lt;a href="https://github.com/emacs-lsp/lsp-mode" rel="noopener noreferrer"&gt;lsp-mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would have preferred to use the standard eglot, but I decided to adopt lsp-mode because there's information suggesting that &lt;a href="https://github.com/castwide/solargraph" rel="noopener noreferrer"&gt;Solargraph&lt;/a&gt;, which eglot requires as the Ruby Language Server, doesn't have good performance. lsp-mode requires &lt;a href="https://github.com/Shopify/ruby-lsp" rel="noopener noreferrer"&gt;ruby-lsp&lt;/a&gt; as the Language Server.&lt;/p&gt;

&lt;p&gt;I haven't actually verified whether Solargraph really has poor performance 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  lsp-mode Configuration Example
&lt;/h3&gt;

&lt;p&gt;First, install the Language Server for each language in a PATH that Emacs recognizes (exec-path).&lt;/p&gt;

&lt;p&gt;The correspondence between each language and Language Server is documented in &lt;a href="https://emacs-lsp.github.io/lsp-mode/page/languages/" rel="noopener noreferrer"&gt;Languages&lt;/a&gt; in the lsp-mode documentation.&lt;/p&gt;

&lt;p&gt;Here are the Language Servers I installed this time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lang&lt;/th&gt;
&lt;th&gt;Language Server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dockerfile&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/dockerfile-language-server-nodejs" rel="noopener noreferrer"&gt;dockerfile-language-server-nodejs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/golang/tools/tree/master/gopls" rel="noopener noreferrer"&gt;gopls&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/vscode-json-languageserver" rel="noopener noreferrer"&gt;vscode-json-languageserver&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://rubygems.org/gems/ruby-lsp" rel="noopener noreferrer"&gt;ruby-lsp&lt;/a&gt;, &lt;a href="https://rubygems.org/gems/ruby-lsp-rails" rel="noopener noreferrer"&gt;ruby-lsp-rails&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/typescript-language-server" rel="noopener noreferrer"&gt;typescript-language-server&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Next, &lt;a href="https://melpa.org/#/getting-started" rel="noopener noreferrer"&gt;install lsp-mode from Melpa&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, just add the &lt;code&gt;lsp-deferred&lt;/code&gt; function to the hook of the major-mode you want to enable.&lt;/p&gt;

&lt;p&gt;Here's a configuration example for ruby-mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;'ruby-mode-hook&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;lsp-deferred&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, you can jump to definitions with &lt;code&gt;M-.&lt;/code&gt; and return with &lt;code&gt;M-,&lt;/code&gt;, and it also points out indentation issues. For other features, please see the &lt;a href="https://emacs-lsp.github.io/lsp-mode/" rel="noopener noreferrer"&gt;lsp-mode documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also configured the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;lsp-keymap-prefix&lt;/span&gt; &lt;span class="s"&gt;"C-c C-l"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; Disable rubocop-ls so that ruby-lsp is prioritized even when rubocop is installed.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;lsp-disabled-clients&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rubocop-ls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;;; Format files on save for all buffers where lsp-mode is active.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;setq-default&lt;/span&gt; &lt;span class="nv"&gt;lsp-format-buffer-on-save&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Discovered xxx-ts-mode Has Been Added to Emacs
&lt;/h2&gt;

&lt;p&gt;When trying to configure lsp-mode for TypeScript, I noticed that xxx-ts-mode exists, which wasn't in previous versions of Emacs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;find /Applications/Emacs.app &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*-ts-mode.elc'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;span class="go"&gt;/Applications/Emacs.app/Contents/Resources/lisp/progmodes/c-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/cmake-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/dockerfile-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/go-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/java-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/json-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/ruby-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/rust-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/progmodes/typescript-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/textmodes/toml-ts-mode.elc
/Applications/Emacs.app/Contents/Resources/lisp/textmodes/yaml-ts-mode.elc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike traditional xxx-mode which parses syntax using regular expressions, xxx-ts-mode parses syntax at the AST level using Tree-sitter. Therefore, syntax highlighting is more accurate, and indentation and structural editing are more stable.&lt;/p&gt;

&lt;p&gt;I was about to skip it, but when I looked at &lt;a href="https://github.com/emacs-typescript/typescript.el/tree/481df3ad2cdf569d8e6697679669ff6206fbd2f9#readme" rel="noopener noreferrer"&gt;typescript-mode's README.md&lt;/a&gt;, I learned that development has stopped:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Essentially all major development of &lt;code&gt;typescript-mode&lt;/code&gt; has come to a halt.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I decided to introduce xxx-ts-mode, starting with typescript-ts-mode. I also felt the benefit of being able to drop third-party xxx-mode packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing typescript-ts-mode
&lt;/h2&gt;

&lt;p&gt;You can install the Tree-sitter for TypeScript by adding the following to init.el:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;treesit-language-source-alist&lt;/span&gt;
      &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;tsx&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/tree-sitter/tree-sitter-typescript"&lt;/span&gt; &lt;span class="s"&gt;"v0.23.2"&lt;/span&gt; &lt;span class="s"&gt;"tsx/src"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;typescript&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/tree-sitter/tree-sitter-typescript"&lt;/span&gt; &lt;span class="s"&gt;"v0.23.2"&lt;/span&gt; &lt;span class="s"&gt;"typescript/src"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dolist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;element&lt;/span&gt; &lt;span class="nv"&gt;treesit-language-source-alist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;lang&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;car&lt;/span&gt; &lt;span class="nv"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;treesit-language-available-p&lt;/span&gt; &lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;treesit-install-language-grammar&lt;/span&gt; &lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is evaluated, the &lt;code&gt;*.dylib&lt;/code&gt; files built from source code fetched from GitHub will be installed directly under &lt;code&gt;~/.emacs.d/tree-sitter/&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;libtree-sitter-tsx.dylib&lt;/li&gt;
&lt;li&gt;libtree-sitter-typescript.dylib&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 If you don't specify a tag, the default branch will be referenced. Since commit hash couldn't be specified, I specified tags from the perspective of security and version pinning.&lt;/p&gt;

&lt;p&gt;Then, simply associate &lt;code&gt;*.ts&lt;/code&gt; and &lt;code&gt;*.tsx&lt;/code&gt; with typescript-ts-mode and tsx-ts-mode respectively. Also enable lsp-mode.&lt;/p&gt;

&lt;p&gt;※ tsx-ts-mode is also defined in typescript-ts-mode.el.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="c1"&gt;;; TypeScript&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-to-list&lt;/span&gt; &lt;span class="ss"&gt;'auto-mode-alist&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\.ts\\'"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;typescript-ts-mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;typescript-ts-mode-hook-func&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;lsp-deferred&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;'typescript-ts-mode-hook&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;typescript-ts-mode-hook-func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;;; TSX&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-to-list&lt;/span&gt; &lt;span class="ss"&gt;'auto-mode-alist&lt;/span&gt; &lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\\.tsx\\'"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;tsx-ts-mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;tsx-ts-mode-hook-func&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;lsp-deferred&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;'tsx-ts-mode-hook&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;tsx-ts-mode-hook-func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Supplement: Why I Didn't Adopt the tree-sitter-langs Package
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://melpa.org/#/tree-sitter-langs" rel="noopener noreferrer"&gt;tree-sitter-langs&lt;/a&gt; package can also install &lt;code&gt;*.dylib&lt;/code&gt;, but I decided not to adopt it for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It installs under &lt;code&gt;~/.emacs.d/elpa/tree-sitter-langs-20251019.1145/bin/&lt;/code&gt; instead of &lt;code&gt;~/.emacs.d/tree-sitter/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This directory name will change with version updates of the tree-sitter-langs package&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The library names are like &lt;code&gt;typescript.dylib&lt;/code&gt; instead of &lt;code&gt;libtree-sitter-typescript.dylib&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;It's good for checking the location of Tree-sitter parsers for each language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/emacs-tree-sitter/tree-sitter-langs/tree/master/repos" rel="noopener noreferrer"&gt;https://github.com/emacs-tree-sitter/tree-sitter-langs/tree/master/repos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducing Other xxx-ts-modes
&lt;/h2&gt;

&lt;p&gt;Among the Emacs built-in xxx-ts-modes mentioned earlier, I introduced those with high usage frequency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dockerfile-ts-mode&lt;/li&gt;
&lt;li&gt;go-ts-mode&lt;/li&gt;
&lt;li&gt;json-ts-mode&lt;/li&gt;
&lt;li&gt;ruby-ts-mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an exception, I didn't introduce yaml-ts-mode. When saving, it formats with indent 4, and I couldn't figure out how to change it to 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Configuration for LSP mode, Tree-sitter, and xxx-ts-mode
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;LSP mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L1044-L1063" rel="noopener noreferrer"&gt;init.el#L1044-L1063&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tree-sitter: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L1431-L1450" rel="noopener noreferrer"&gt;init.el#L1431-L1450&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;dockerfile-ts-mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L801-L810" rel="noopener noreferrer"&gt;init.el#L801-L810&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;go-ts-mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L905-L917" rel="noopener noreferrer"&gt;init.el#L905-L917&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;json-ts-mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L980-L989" rel="noopener noreferrer"&gt;init.el#L980-L989&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ruby-ts-mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L1121-L1132" rel="noopener noreferrer"&gt;init.el#L1121-L1132&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;typescript-ts-mode: &lt;a href="https://github.com/masutaka/dotfiles-public/blob/37ce0f656d6ca344e187de2e15dccac0536d7360/.emacs.d/init.el#L1014-L1028" rel="noopener noreferrer"&gt;init.el#L1014-L1028&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus: Migration from auto-complete to company-mode
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/emacs-lsp/lsp-mode/blob/5c4bf238cba48e9b45c6969b22dc6de53b3523bb/lsp-completion.el#L913-L931" rel="noopener noreferrer"&gt;There were usage points for company-mode in the lsp-mode code.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had only heard the name &lt;a href="https://github.com/company-mode/company-mode" rel="noopener noreferrer"&gt;company-mode&lt;/a&gt;, but apparently it's a completion framework that assists code input.&lt;/p&gt;

&lt;p&gt;I migrated from &lt;a href="https://github.com/auto-complete/auto-complete" rel="noopener noreferrer"&gt;auto-complete&lt;/a&gt;, which I've been using for years, to company-mode.&lt;/p&gt;

&lt;p&gt;I saw some information suggesting that corfu, which is lighter than company-mode, might be better now, but I decided to go with company-mode this time as it seemed easier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;company-minimum-prefix-length&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;company-show-quick-access&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;'after-init-hook&lt;/span&gt; &lt;span class="nf"&gt;#'&lt;/span&gt;&lt;span class="nv"&gt;global-company-mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Issues
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When lsp-format-buffer-on-save is enabled for all buffers where lsp-mode is active, code written by others gets heavily reformatted, which might be awkward. Especially YAML files&lt;/li&gt;
&lt;li&gt;global-company-mode is a bit noisy, so it might be better to configure it for individual major-modes. I might change this later&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Finally, I brought my Emacs setup closer to a modern configuration with LSP + Tree-sitter. However, since there are almost no Emacs users around me, I'm not entirely sure how close I got.&lt;/p&gt;

&lt;p&gt;I was also able to reduce some third-party packages. I want to minimize dependencies as much as possible, so that's good.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added:

&lt;ul&gt;
&lt;li&gt;company&lt;/li&gt;
&lt;li&gt;lsp-mode&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Removed:

&lt;ul&gt;
&lt;li&gt;auto-complete&lt;/li&gt;
&lt;li&gt;dockerfile-mode&lt;/li&gt;
&lt;li&gt;go-autocomplete&lt;/li&gt;
&lt;li&gt;go-eldoc&lt;/li&gt;
&lt;li&gt;go-mode&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://emacs-lsp.github.io/lsp-mode/" rel="noopener noreferrer"&gt;LSP Mode - LSP support for Emacs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/emacs-tree-sitter/tree-sitter-langs" rel="noopener noreferrer"&gt;tree-sitter-langs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/eggc/articles/55e8fbde5fab1b" rel="noopener noreferrer"&gt;ruby-lsp による Rubocop 違反の自動修正 in Emacs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenn.dev/glassonion1/articles/20752bb8d2cf98" rel="noopener noreferrer"&gt;EmacsでTree-Sitterによる新しいモードを利用する方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mugijiru.github.io/.emacs.d/programming/yaml-mode/" rel="noopener noreferrer"&gt;yaml-mode :: 麦汁's Emacs Config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://joppot.info/posts/c05e989a-e642-4c84-a5b8-a0e0c3178941" rel="noopener noreferrer"&gt;emacs29でビルドインされたtree-sitterとtsx-ts-modeでtypescriptの開発環境を構築する | joppot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>emacs</category>
      <category>lsp</category>
      <category>treesitter</category>
    </item>
    <item>
      <title>Making this Hugo-built blog multilingual</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Tue, 23 Sep 2025 12:17:05 +0000</pubDate>
      <link>https://dev.to/masutaka/making-this-hugo-built-blog-multilingual-l9b</link>
      <guid>https://dev.to/masutaka/making-this-hugo-built-blog-multilingual-l9b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note: This article reflects the setup as of Hugo 0.138.0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've made this blog built with Hugo multilingual. For now, it's only available in English.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://masutaka.net/" rel="noopener noreferrer"&gt;https://masutaka.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://masutaka.net/en/" rel="noopener noreferrer"&gt;https://masutaka.net/en/&lt;/a&gt; 👈&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I implemented multilingual support
&lt;/h2&gt;

&lt;p&gt;As part of my OSS activities at work, I often cross-post translated articles to platforms like dev.to and Medium. Recently, I've been casually translating and cross-posting articles on my own as well.&lt;/p&gt;

&lt;p&gt;For example, the English translation of &lt;a href="https://masutaka.net/2025-08-15-1/" rel="noopener noreferrer"&gt;2025-08-15-1&lt;/a&gt; was cross-posted to these three sites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/masutaka/migration-from-pocket-and-hatena-bookmark-to-raindropio-and-creating-helm-raindropel-5f76"&gt;dev.to&lt;/a&gt; - This served as the original English version&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://masutaka.hashnode.dev/migration-from-pocket-and-hatena-bookmark-to-raindropio-and-creating-helm-raindropel" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt; - Set dev.to's article as the canonical version&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@masutaka/migration-from-pocket-and-hatena-bookmark-to-raindrop-io-and-creating-helm-raindrop-el-6e67e22a7f17" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; - Set dev.to's article as the canonical version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, since I'm maintaining this blog masutaka.net, I thought it would be better to store the original English articles here and cross-post them to the above three sites. Setting the canonical URL to my own blog seems like it would also be beneficial for SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo's Multilingual Support
&lt;/h2&gt;

&lt;p&gt;Hugo includes built-in multilingual support with simple configuration. All you need to do is add the following settings in your &lt;code&gt;config.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;defaultContentLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ja"&lt;/span&gt;

&lt;span class="nn"&gt;[languages.ja]&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"日本語"&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"マスタカの ChangeLog メモ"&lt;/span&gt;

&lt;span class="nn"&gt;[languages.en]&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"English"&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Masutaka's ChangeLog Memo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Articles with &lt;code&gt;.en.md&lt;/code&gt; in their filenames will be the English versions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Japanese version: &lt;code&gt;content/posts/2025-08-15-1.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;English version: &lt;code&gt;content/posts/2025-08-15-1.en.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this configuration, articles can be accessed via the following URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Japanese version: &lt;code&gt;https://masutaka.net/2025-08-15-1/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;English version: &lt;code&gt;https://masutaka.net/en/2025-08-15-1/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Multilingual Implementation Strategy
&lt;/h2&gt;

&lt;p&gt;When implementing multilingual support, I followed this approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Maintain Existing URLs Unchanged
&lt;/h3&gt;

&lt;p&gt;The most important policy was to absolutely preserve all existing URLs. Given that this blog has been operational for over 20 years, any changes to URLs would cause numerous broken links.&lt;/p&gt;

&lt;p&gt;Therefore, I kept the Japanese version as is and added an &lt;code&gt;/en/&lt;/code&gt; prefix only for the English version.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/index.html&lt;/code&gt; - Almost unchanged, with &lt;code&gt;/en/index.html&lt;/code&gt; added&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/index.xml&lt;/code&gt; - Unchanged, with &lt;code&gt;/en/index.xml&lt;/code&gt; added&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/sitemap.xml&lt;/code&gt; - Changed to reference &lt;code&gt;/ja/sitemap.xml&lt;/code&gt; and &lt;code&gt;/en/sitemap.xml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/llms.txt&lt;/code&gt;, &lt;code&gt;/llms-full.txt&lt;/code&gt; - Unchanged, with &lt;code&gt;/en/llms.txt&lt;/code&gt; and &lt;code&gt;/en/llms-full.txt&lt;/code&gt; added

&lt;ul&gt;
&lt;li&gt;See reference: &lt;a href="https://masutaka.net/en/2025-05-18-1/" rel="noopener noreferrer"&gt;2025-05-18-1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;👉 The behavior described above is achieved through the configuration settings for &lt;code&gt;defaultContentLanguage&lt;/code&gt;, &lt;code&gt;[languages.ja]&lt;/code&gt;, and &lt;code&gt;[languages.en]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keep English Menu Options in the Top Right Minimal
&lt;/h3&gt;

&lt;p&gt;The Device, History, and About pages contain content that is primarily Japanese-specific with limited value in translation, so I decided to skip them this time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Japanese Version&lt;/strong&gt;: &lt;a href="https://masutaka.net/archives/" rel="noopener noreferrer"&gt;Archive&lt;/a&gt;, &lt;a href="https://masutaka.net/tags/" rel="noopener noreferrer"&gt;Tags&lt;/a&gt;, &lt;a href="https://masutaka.net/search/" rel="noopener noreferrer"&gt;Search&lt;/a&gt;, &lt;a href="https://masutaka.net/device/" rel="noopener noreferrer"&gt;Device&lt;/a&gt;, &lt;a href="https://masutaka.net/history/" rel="noopener noreferrer"&gt;History&lt;/a&gt;, &lt;a href="https://masutaka.net/about/" rel="noopener noreferrer"&gt;About&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;English Version&lt;/strong&gt;: Only includes &lt;a href="https://masutaka.net/en/archives/" rel="noopener noreferrer"&gt;Archive&lt;/a&gt;, &lt;a href="https://masutaka.net/en/tags/" rel="noopener noreferrer"&gt;Tags&lt;/a&gt;, &lt;a href="https://masutaka.net/en/search/" rel="noopener noreferrer"&gt;Search&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation Details
&lt;/h2&gt;

&lt;p&gt;Here's an overview of the actual changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Modifications to config.toml
&lt;/h3&gt;

&lt;p&gt;Added language configuration settings and corresponding menu settings for each language.&lt;/p&gt;

&lt;p&gt;👉 The diff and the updated config.toml are attached in the Appendix.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Creating English versions of static pages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content/archives.en.md&lt;/code&gt; - Archives page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content/search.en.md&lt;/code&gt; - Search page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content/privacy.en.md&lt;/code&gt; - Privacy Policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ Initially, I attempted to handle this by creating symbolic links from &lt;code&gt;*.en.md&lt;/code&gt; to &lt;code&gt;*.md&lt;/code&gt;, but Hugo did not recognize these links.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Modifying custom shortcodes
&lt;/h3&gt;

&lt;p&gt;Updated the custom &lt;code&gt;post&lt;/code&gt; shortcode to accept an &lt;code&gt;lang&lt;/code&gt; argument.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;layouts/shortcodes/post.html:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{{- $id := .Get "id" | default (.Get 0) -}}
{{- $lang := .Get "lang" | default .Page.Lang -}}
{{- $url := relref . (dict "path" $id "lang" $lang) -}}
{{- $title := .Get "title" -}}

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $url }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ with $title }}{{ $title }}{{ else }}[{{ $id }}]{{ end }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- If you specify an article ID as an argument, it generates a link in the current language --&amp;gt;&lt;/span&gt;
{{&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nt"&gt;post&lt;/span&gt; &lt;span class="err"&gt;"2025&lt;/span&gt;&lt;span class="na"&gt;-09-23-1&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;}}
&lt;span class="c"&gt;&amp;lt;!-- -&amp;gt; If the current language is English, this will generate a link to /en/2025-09-23-1/ --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Adding the 'lang' argument creates an explicit link to another language --&amp;gt;&lt;/span&gt;
{{&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nt"&gt;post&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"2025-09-23-1"&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ja"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;}}
&lt;span class="c"&gt;&amp;lt;!-- -&amp;gt; If the current language is English, this will generate a link to /2025-09-23-1/ --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Translation Support for Custom Partials
&lt;/h3&gt;

&lt;p&gt;Since some translations were needed in &lt;code&gt;layouts/partials/*.html&lt;/code&gt;, I created &lt;code&gt;i18n/ja.toml&lt;/code&gt; and &lt;code&gt;i18n/en.toml&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Usage example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ i18n "sendMessage" }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;i18n/ja.toml:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;sendMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"メッセージ送信"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;i18n/en.toml:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;sendMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Send Message"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I successfully added multilingual support to this Hugo-built blog. The implementation was cleaner than I expected 👍&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All existing URLs remained unchanged, limiting the impact to the &lt;code&gt;/en/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Achieved using only Hugo's standard features with minimal customization&lt;/li&gt;
&lt;li&gt;Reduced maintenance costs by keeping the English menu minimal&lt;/li&gt;
&lt;li&gt;sitemap.xml and llms.txt were automatically multilingualized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving forward, I'll translate new articles as needed and use &lt;a href="https://masutaka.net/en/" rel="noopener noreferrer"&gt;https://masutaka.net/en/&lt;/a&gt; as the original source while cross-posting to dev.to, Hashnode, and Medium.&lt;/p&gt;

&lt;p&gt;While I'm not sure if there's actually an English-speaking audience, I'm satisfied with the result. With recent AI translation being fairly accurate, it's not that difficult anyway 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/content-management/multilingual/" rel="noopener noreferrer"&gt;Multilingual mode - Hugo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Changes to config.toml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/config.toml b/config.toml
index 0830be8b..a6bae12c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/config.toml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/config.toml
&lt;/span&gt;&lt;span class="p"&gt;@@ -7,7 +7,6 @@&lt;/span&gt; googleAnalytics = "G-K28CQCC064"
 hasCJKLanguage = true
 languageCode = "ja"
 theme = "papermod"
&lt;span class="gd"&gt;-title = "マスタカの ChangeLog メモ"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; [permalinks]
   posts = "/:filename"
&lt;span class="p"&gt;@@ -27,14 +26,12 @@&lt;/span&gt; title = "マスタカの ChangeLog メモ"
   isPlainText = true
   mediaType = "text/plain"
   rel = "alternate"
&lt;span class="gd"&gt;-  root = true
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; [outputFormats.llmsfull]
   baseName = "llms-full"
   isPlainText = true
   mediaType = "text/plain"
   rel = "alternate"
&lt;span class="gd"&gt;-  root = true
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; #
 # papermod configuration
&lt;span class="p"&gt;@@ -51,14 +48,9 @@&lt;/span&gt; title = "マスタカの ChangeLog メモ"
   author = "masutaka"
   comments = true
   defaultTheme = "auto"
&lt;span class="gd"&gt;-  description = "マスタカの変更履歴が記録されていくブログです。"
&lt;/span&gt;   showtoc = true
   tocopen = true
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-[params.homeInfoParams]
-  Title = "マスタカネット"
-  Content = "マスタカの変更履歴が記録されていくブログです。"
-
&lt;/span&gt; [params.assets]
   theme_color = "#ffffff"
   msapplication_TileColor = "#da532c"
&lt;span class="p"&gt;@@ -79,46 +71,81 @@&lt;/span&gt; title = "マスタカの ChangeLog メモ"
 [[params.socialIcons]]
   name =  "GitHub"
   url = "https://github.com/masutaka"
&lt;span class="gd"&gt;-[[params.socialIcons]]
-  name =  "Dev"
-  url = "https://dev.to/masutaka"
-[[params.socialIcons]]
-  name =  "Hashnode"
-  url = "https://masutaka.hashnode.dev/"
-[[params.socialIcons]]
-  name =  "Medium"
-  url = "https://medium.com/@masutaka"
&lt;/span&gt; [[params.socialIcons]]
   name =  "RSS"
   url = "/index.xml"
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+#
+# Multilingual
+#
+
+[languages.ja]
+  weight = 1
+  languageName = "日本語"
+  title = "マスタカの ChangeLog メモ"
+
+[languages.ja.params]
+  description = "マスタカの変更履歴が記録されていくブログです。"
+
+[languages.ja.params.homeInfoParams]
+  Title = "マスタカネット"
+  Content = "マスタカの変更履歴が記録されていくブログです。"
+
+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "archives"
   name = "Archive"
   url = "/archives/"
   weight = 1
&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "tags"
   name = "Tags"
   url = "/tags/"
   weight = 2
&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "search"
   name = "Search"
   url = "/search/"
   weight = 3
&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "device"
   name = "Device"
   url = "/device/"
   weight = 4
&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "history"
   name = "History"
   url = "/history/"
   weight = 5
&lt;span class="gd"&gt;-[[menu.main]]
&lt;/span&gt;&lt;span class="gi"&gt;+[[languages.ja.menu.main]]
&lt;/span&gt;   identifier = "about"
   name = "About"
   url = "/about/"
   weight = 6
&lt;span class="gi"&gt;+
+[languages.en]
+  weight = 2
+  languageName = "English"
+  title = "Masutaka's ChangeLog Memo"
+
+[languages.en.params]
+  description = "This is a blog that records Masutaka's change history."
+
+[languages.en.params.homeInfoParams]
+  Title = "Masutaka Net"
+  Content = "This is a blog that records Masutaka's change history."
+
+[[languages.en.menu.main]]
+  identifier = "archives"
+  name = "Archive"
+  url = "/en/archives/"
+  weight = 1
+[[languages.en.menu.main]]
+  identifier = "tags"
+  name = "Tags"
+  url = "/en/tags/"
+  weight = 2
+[[languages.en.menu.main]]
+  identifier = "search"
+  name = "Search"
+  url = "/en/search/"
+  weight = 3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  config.toml after multilingualization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;baseURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://masutaka.net/"&lt;/span&gt;
&lt;span class="py"&gt;defaultContentLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ja"&lt;/span&gt;
&lt;span class="py"&gt;disablePathToLower&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;enableEmoji&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;enableRobotsTXT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;googleAnalytics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"G-K28CQCC064"&lt;/span&gt;
&lt;span class="py"&gt;hasCJKLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;languageCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ja"&lt;/span&gt;
&lt;span class="py"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"papermod"&lt;/span&gt;

&lt;span class="nn"&gt;[permalinks]&lt;/span&gt;
  &lt;span class="py"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/:filename"&lt;/span&gt;

&lt;span class="nn"&gt;[taxonomies]&lt;/span&gt;
  &lt;span class="py"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tags"&lt;/span&gt;

&lt;span class="nn"&gt;[markup.goldmark.renderer]&lt;/span&gt;
  &lt;span class="py"&gt;hardWraps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[outputs]&lt;/span&gt;
  &lt;span class="py"&gt;home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llmsfull"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[outputFormats.llms]&lt;/span&gt;
  &lt;span class="py"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llms"&lt;/span&gt;
  &lt;span class="py"&gt;isPlainText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;
  &lt;span class="py"&gt;rel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alternate"&lt;/span&gt;

&lt;span class="nn"&gt;[outputFormats.llmsfull]&lt;/span&gt;
  &lt;span class="py"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llms-full"&lt;/span&gt;
  &lt;span class="py"&gt;isPlainText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;
  &lt;span class="py"&gt;rel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alternate"&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# papermod configuration&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="nn"&gt;[params]&lt;/span&gt;
  &lt;span class="py"&gt;AmazonJpAffiliateID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"masutaka04-22"&lt;/span&gt;
  &lt;span class="py"&gt;DateFormat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2006-01-02 (Mon)"&lt;/span&gt;
  &lt;span class="py"&gt;ShowAllPagesInArchive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;ShowCodeCopyButtons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;ShowFullTextinRSS&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;ShowPageNums&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;ShowPostNavLinks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;author&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"masutaka"&lt;/span&gt;
  &lt;span class="py"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;defaultTheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"auto"&lt;/span&gt;
  &lt;span class="py"&gt;showtoc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;tocopen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[params.assets]&lt;/span&gt;
  &lt;span class="py"&gt;theme_color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#ffffff"&lt;/span&gt;
  &lt;span class="py"&gt;msapplication_TileColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#da532c"&lt;/span&gt;

&lt;span class="nn"&gt;[params.social]&lt;/span&gt;
  &lt;span class="py"&gt;fediverse_creator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@masutaka@mstdn.love"&lt;/span&gt;
  &lt;span class="py"&gt;twitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@masutaka"&lt;/span&gt;

&lt;span class="nn"&gt;[[params.socialIcons]]&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"Mastodon"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://mstdn.love/@masutaka"&lt;/span&gt;
&lt;span class="nn"&gt;[[params.socialIcons]]&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"Bluesky"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://bsky.app/profile/masutaka.net"&lt;/span&gt;
&lt;span class="nn"&gt;[[params.socialIcons]]&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"Twitter"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://twitter.com/masutaka"&lt;/span&gt;
&lt;span class="nn"&gt;[[params.socialIcons]]&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"GitHub"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/masutaka"&lt;/span&gt;
&lt;span class="nn"&gt;[[params.socialIcons]]&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s"&gt;"RSS"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/index.xml"&lt;/span&gt;

&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Multilingual&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="nn"&gt;[languages.ja]&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"日本語"&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"マスタカの ChangeLog メモ"&lt;/span&gt;

&lt;span class="nn"&gt;[languages.ja.params]&lt;/span&gt;
  &lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"マスタカの変更履歴が記録されていくブログです。"&lt;/span&gt;

&lt;span class="nn"&gt;[languages.ja.params.homeInfoParams]&lt;/span&gt;
  &lt;span class="py"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"マスタカネット"&lt;/span&gt;
  &lt;span class="py"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"マスタカの変更履歴が記録されていくブログです。"&lt;/span&gt;

&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"archives"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Archive"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/archives/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tags"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tags"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/tags/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"search"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Search"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/search/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"device"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Device"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/device/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"history"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"History"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/history/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.ja.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"about"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"About"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/about/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="nn"&gt;[languages.en]&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="py"&gt;languageName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"English"&lt;/span&gt;
  &lt;span class="py"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Masutaka's ChangeLog Memo"&lt;/span&gt;

&lt;span class="nn"&gt;[languages.en.params]&lt;/span&gt;
  &lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a blog that records Masutaka's change history."&lt;/span&gt;

&lt;span class="nn"&gt;[languages.en.params.homeInfoParams]&lt;/span&gt;
  &lt;span class="py"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Masutaka Net"&lt;/span&gt;
  &lt;span class="py"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a blog that records Masutaka's change history."&lt;/span&gt;

&lt;span class="nn"&gt;[[languages.en.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"archives"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Archive"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/en/archives/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.en.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tags"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tags"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/en/tags/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="nn"&gt;[[languages.en.menu.main]]&lt;/span&gt;
  &lt;span class="py"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"search"&lt;/span&gt;
  &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Search"&lt;/span&gt;
  &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/en/search/"&lt;/span&gt;
  &lt;span class="py"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>hugo</category>
    </item>
    <item>
      <title>Migration from Pocket and Hatena Bookmark to Raindrop.io (and Creating helm-raindrop.el)</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Fri, 15 Aug 2025 10:39:11 +0000</pubDate>
      <link>https://dev.to/masutaka/migration-from-pocket-and-hatena-bookmark-to-raindropio-and-creating-helm-raindropel-5f76</link>
      <guid>https://dev.to/masutaka/migration-from-pocket-and-hatena-bookmark-to-raindropio-and-creating-helm-raindropel-5f76</guid>
      <description>&lt;p&gt;I've migrated from &lt;a href="https://getpocket.com/" rel="noopener noreferrer"&gt;Pocket&lt;/a&gt; and &lt;a href="https://b.hatena.ne.jp/" rel="noopener noreferrer"&gt;Hatena Bookmark (Hatebu)&lt;/a&gt;, services I had been using for over a decade, to a new bookmarking service: Raindrop.io. I've also created helm-raindrop.el so I can comfortably search from Emacs as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Raindrop.io?
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;a href="https://raindrop.io/" rel="noopener noreferrer"&gt;https://raindrop.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Raindrop.io is a bookmarking service with a modern design and rich features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organize bookmarks with collections&lt;/li&gt;
&lt;li&gt;Tagging and smart filters&lt;/li&gt;
&lt;li&gt;Full-text search (premium plan only)&lt;/li&gt;
&lt;li&gt;Browser extensions, mobile apps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://raindrop.io/integrations" rel="noopener noreferrer"&gt;Integrations with external services&lt;/a&gt; like IFTTT and Zapier&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.raindrop.io/" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend is proprietary, but everything else is open-source (OSS) and maintained by a single person, &lt;a href="https://github.com/exentrich" rel="noopener noreferrer"&gt;Rustem Mussabekov&lt;/a&gt;, who lives in Kazakhstan.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raindropio" rel="noopener noreferrer"&gt;
        raindropio
      &lt;/a&gt; / &lt;a href="https://github.com/raindropio/app" rel="noopener noreferrer"&gt;
        app
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Source code of web app and browser extensions
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raindropio" rel="noopener noreferrer"&gt;
        raindropio
      &lt;/a&gt; / &lt;a href="https://github.com/raindropio/mobile" rel="noopener noreferrer"&gt;
        mobile
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Official Raindrop.io mobile app for Android
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/raindropio" rel="noopener noreferrer"&gt;
        raindropio
      &lt;/a&gt; / &lt;a href="https://github.com/raindropio/desktop" rel="noopener noreferrer"&gt;
        desktop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Official Raindrop.io Desktop App
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Migration Pocket and Hatebu to Raindrop.io
&lt;/h2&gt;

&lt;p&gt;On July 8, 2025, Pocket was discontinued. I had used it as a "read-it-later" service for many years, so I had to find an alternative.&lt;/p&gt;

&lt;p&gt;I had also been using Hatebu for a full 16 years since 2009, but since I've been distancing myself from social media in recent years, I decided to switch to a unified service.&lt;/p&gt;

&lt;p&gt;Here's why I chose Raindrop.io:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free plan is more than enough (unlimited bookmarks)&lt;/li&gt;
&lt;li&gt;I can use it as a "read-it-later" service, just like Pocket&lt;/li&gt;
&lt;li&gt;I can also use it as a persistent bookmarking service, like Hatebu&lt;/li&gt;
&lt;li&gt;The API is public, making it easy to create my own tools&lt;/li&gt;
&lt;li&gt;It has a data export feature, so there's less worry about vendor lock-in&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The good things about Raindrop.io
&lt;/h2&gt;

&lt;p&gt;After using it, I found several convenient features.&lt;/p&gt;

&lt;h3&gt;
  
  
  The free plan is practical
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URLs can be edited&lt;/strong&gt; - You can correct the URL later even if it changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collections feature&lt;/strong&gt; - You can organize bookmarks like folders and set them to be public or private individually&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nested collections&lt;/strong&gt; - You can set up a hierarchical structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting feature&lt;/strong&gt; - You can mark important parts of an article&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile app&lt;/strong&gt; - In addition to the web version, browser extensions, and desktop version, there are also apps for iOS and Android&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The paid plan's automated web archiving is convenient
&lt;/h3&gt;

&lt;p&gt;🔗 &lt;a href="https://help.raindrop.io/premium-features" rel="noopener noreferrer"&gt;https://help.raindrop.io/premium-features&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered suggestions&lt;/strong&gt; - It suggests appropriate collections and tags when saving and suggests merging or renaming similar tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-text search&lt;/strong&gt; - It performs a full-text search on the content of saved articles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated web archiving feature&lt;/strong&gt; - It permanently saves content even if the site disappears&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reminder feature&lt;/strong&gt; - It notifies you of a specific bookmark via the app or email at a specified time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotation feature&lt;/strong&gt; - You can add notes to highlights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection of duplicate and broken links&lt;/strong&gt; - Makes maintenance easier (?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated backup&lt;/strong&gt; - You can manage versions by linking with Google Drive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extended upload limit&lt;/strong&gt; - You can upload up to 10GB of images, videos, and PDFs per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried the paid plan and found the automated web archiving to be very convenient.&lt;/p&gt;

&lt;p&gt;I can't help but fix broken links when I find them. I know some might argue that if you can't find them, you don't have to maintain them...&lt;/p&gt;

&lt;p&gt;While fixing them, I realized that many of my past bookmarks were inaccessible. With Raindrop.io's automated web archiving, a new reason is added to "read-it-later" or "bookmark": "it'll be readable forever."&lt;/p&gt;

&lt;p&gt;The current paid plan is $33.04 per year (tax included), which is about $3.54 per month (tax included), so it's not a bad price.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bad things about Raindrop.io
&lt;/h2&gt;

&lt;p&gt;It's not a perfect service, so there are a few points that are difficult to use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The app icon is plain&lt;/strong&gt; - It's difficult to spot in my smartphone's share menu, and I often have to search for it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The saving process is a bit sluggish&lt;/strong&gt; - It feels like it takes an extra 1-2 seconds to save a bookmark compared to Pocket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No archiving feature&lt;/strong&gt; - If you're looking for a 'mark as read' feature like Pocket's, you'll need to create a dedicated collection and manually move bookmarks to it (which requires two taps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, even with these points, it's still a good and very usable service.&lt;/p&gt;

&lt;h2&gt;
  
  
  I created helm-raindrop.el
&lt;/h2&gt;

&lt;p&gt;For this migration, I created helm-raindrop.el, which allows you to search and browse Raindrop.io bookmarks from Emacs.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/emacs-helm-raindrop" rel="noopener noreferrer"&gt;
        emacs-helm-raindrop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Helm interface for Raindrop.io
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I'm not following what's next after Helm, but since the main function of helm-raindrop.el is to create a cache file &lt;code&gt;~/.emacs.d/helm-raindrop&lt;/code&gt; with the REST API, I don't think it will be difficult to migrate later.&lt;/p&gt;

&lt;p&gt;Actually, I had a similar tool for Hatebu, and its extreme convenience was one of the reasons I was hesitant to migrate.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/emacs-helm-hatena-bookmark" rel="noopener noreferrer"&gt;
        emacs-helm-hatena-bookmark
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Helm interface for Hatena::Bookmark
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Being able to search bookmarks quickly from Emacs is quite comfortable. For example, if I think, "where was that article?", I just press &lt;code&gt;⌘-b&lt;/code&gt; and enter a search word. That's what my &lt;a href="https://github.com/masutaka/dotfiles-public/blob/816650d2ea5412ddf9e72925661f65eaed6218f5/.emacs.d/init.el" rel="noopener noreferrer"&gt;init.el&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/te8OTzM4V3A"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I'm not sure if it's a good or bad thing that I can continue to search for bookmarks from Emacs, but for now, I've managed to maintain the status quo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Following the discontinuation of Pocket, I migrated to Raindrop.io, including Hatebu, which I had used for a full 16 years. At first, it was a reluctant migration, but after using it, I found it to be a surprisingly good service.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free plan is practical, and the paid plan is also reasonably priced at $33.04 per year (tax included)&lt;/li&gt;
&lt;li&gt;The API is public, making it easy to integrate with self-made tools&lt;/li&gt;
&lt;li&gt;It has modern features like AI and automated web archiving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also created helm-raindrop.el, so I've been able to maintain the ability to search from Emacs.&lt;/p&gt;

&lt;p&gt;I recommend it to anyone looking for a bookmarking service. The free plan is very usable, and migrating from Pocket or Hatebu is easy, so it's a good idea to give it a try.&lt;/p&gt;

&lt;p&gt;I'll continue to use it and will write another article if I discover anything new.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>opensource</category>
      <category>emacs</category>
      <category>emacslisp</category>
    </item>
    <item>
      <title>Self-implemented IFTTT Pro's RSS feed notification feature with AWS serverless architecture</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 20 Jul 2025 09:49:18 +0000</pubDate>
      <link>https://dev.to/masutaka/self-implemented-ifttt-pros-rss-feed-notification-feature-with-aws-serverless-architecture-2ope</link>
      <guid>https://dev.to/masutaka/self-implemented-ifttt-pros-rss-feed-notification-feature-with-aws-serverless-architecture-2ope</guid>
      <description>&lt;p&gt;For casual information gathering, I've been running a serverless application called masutaka-feed since 2020.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/masutaka" rel="noopener noreferrer"&gt;
        masutaka
      &lt;/a&gt; / &lt;a href="https://github.com/masutaka/masutaka-feed" rel="noopener noreferrer"&gt;
        masutaka-feed
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Notify my feeds of github and hatebu to mastodon and pushover
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Post GitHub private feeds&lt;sup id="fnref1"&gt;1&lt;/sup&gt; to Mastodon

&lt;ul&gt;
&lt;li&gt;Star and follow notifications are also sent to &lt;a href="https://pushover.net/" rel="noopener noreferrer"&gt;Pushover&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Post &lt;a href="https://b.hatena.ne.jp/help/entry/favorite" rel="noopener noreferrer"&gt;Hatena Bookmark favorites&lt;/a&gt; feeds&lt;sup id="fnref2"&gt;2&lt;/sup&gt; to Mastodon&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;These are pieces of information that aren't worth subscribing to seriously with a feed reader, but I want to keep them in my field of view.&lt;/p&gt;

&lt;p&gt;※ Mastodon posts are made to the private account &lt;a href="https://mstdn.love/@masutakafeed" rel="noopener noreferrer"&gt;@masutakafeed@mstdn.love&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TOC
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Previous Architecture Diagram&lt;/li&gt;
&lt;li&gt;Current Architecture Diagram&lt;/li&gt;
&lt;li&gt;
Current Architecture

&lt;ul&gt;
&lt;li&gt;Periodic execution with EventBridge Scheduler&lt;/li&gt;
&lt;li&gt;Lambda function responsibility separation&lt;/li&gt;
&lt;li&gt;Duplicate prevention with DynamoDB&lt;/li&gt;
&lt;li&gt;Configuration management with SAM continues&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;After the migration&lt;/li&gt;

&lt;li&gt;Implemented with Claude Code&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Previous Architecture Diagram
&lt;/h2&gt;

&lt;p&gt;Previously, I used IFTTT's RSS Feed Integration to detect new items and call Lambda functions via API Gateway.&lt;/p&gt;

&lt;p&gt;However, starting in 2024, RSS Feed Integration became available only with paid IFTTT Pro, so I was reluctantly paying the annual fee of &lt;code&gt;$34.99&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qna2xezjmrud2tabtyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qna2xezjmrud2tabtyh.png" alt="Previous GitHub private feed architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hatena Bookmark:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbn0zptt84c52lm9gowk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbn0zptt84c52lm9gowk.png" alt="Previous Hatena Bookmark favorites feed architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Architecture Diagram
&lt;/h2&gt;

&lt;p&gt;Since I wasn't using IFTTT anywhere else, I really didn't want to continue paying the annual &lt;code&gt;$34.99&lt;/code&gt;, so I completely migrated to an AWS serverless architecture this time.&lt;/p&gt;

&lt;p&gt;The architecture uses EventBridge Scheduler to periodically check feeds and DynamoDB for read state management. Dependencies on external services have been eliminated, and processing is now completed entirely within AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuyhddo2lwcleykmuvzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuyhddo2lwcleykmuvzi.png" alt="Current GitHub private feed architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hatena Bookmark:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg69u0dnv5ed9gt2bkfmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg69u0dnv5ed9gt2bkfmv.png" alt="Current Hatena Bookmark favorites feed architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Architecture
&lt;/h2&gt;

&lt;p&gt;In the new architecture, the following components work together:&lt;/p&gt;

&lt;h3&gt;
  
  
  Periodic execution with EventBridge Scheduler
&lt;/h3&gt;

&lt;p&gt;GitHub private feeds are checked every 5 minutes, and Hatena Bookmark favorites feeds every 15 minutes, executing the Lambda Subscriber functions shown below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda function responsibility separation
&lt;/h3&gt;

&lt;p&gt;Each feed is separated into 2 Lambda functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subscriber function: Feed retrieval and new item detection

&lt;ul&gt;
&lt;li&gt;Retrieve RSS/Atom feeds and parse with &lt;a href="https://www.npmjs.com/package/rss-parser" rel="noopener noreferrer"&gt;rss-parser&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Manage read state with DynamoDB&lt;/li&gt;
&lt;li&gt;Call Notifier function if there are new items&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Notifier function: Execute notification processing

&lt;ul&gt;
&lt;li&gt;Filtering processing (GitHub only notifies specific events)&lt;/li&gt;
&lt;li&gt;Post to Mastodon&lt;/li&gt;
&lt;li&gt;Send notifications to Pushover (GitHub only)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Duplicate prevention with DynamoDB
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create tables for each feed

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;code&gt;masutaka-feed-github-state&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hatena Bookmark: &lt;code&gt;masutaka-feed-hatebu-state&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Set partition keys

&lt;ul&gt;
&lt;li&gt;GitHub: Atom feed, each &lt;code&gt;entry&lt;/code&gt;'s &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hatena Bookmark: RSS 1.0 feed, each &lt;code&gt;item&lt;/code&gt;'s &lt;code&gt;rdf:about&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Set TTL to automatically delete old records after 30 days&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration management with SAM continues
&lt;/h3&gt;

&lt;p&gt;As before, I use AWS SAM to manage the configuration of added components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/masutaka-feed/blob/678a0b03028e85bee0b2396a9ad6059fc336faba/samconfig.toml" rel="noopener noreferrer"&gt;samconfig.toml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/masutaka-feed/blob/678a0b03028e85bee0b2396a9ad6059fc336faba/template.yaml" rel="noopener noreferrer"&gt;template.yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Environment variables continue to be managed with GitHub Actions Secrets/Variables, and automatic deployment occurs when commits are added to the main branch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/masutaka-feed/blob/678a0b03028e85bee0b2396a9ad6059fc336faba/.github/workflows/deploy.yml" rel="noopener noreferrer"&gt;.github/workflows/deploy.yml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  After the migration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Despite increased responsibilities, the code became unexpectedly cleaner&lt;/li&gt;
&lt;li&gt;rss-parser was more convenient than expected - by just defining feed types like &lt;a href="https://github.com/masutaka/masutaka-feed/blob/678a0b03028e85bee0b2396a9ad6059fc336faba/github/subscriber/index.ts#L6-L20" rel="noopener noreferrer"&gt;GitHubFeedItem&lt;/a&gt; and &lt;a href="https://github.com/masutaka/masutaka-feed/blob/678a0b03028e85bee0b2396a9ad6059fc336faba/hatebu/subscriber/index.ts#L6C11-L25" rel="noopener noreferrer"&gt;HatebuFeedItem&lt;/a&gt;, parsing became simple

&lt;ul&gt;
&lt;li&gt;As a result, I was able to abandon &lt;a href="https://github.com/masutaka/masutaka-feed/blob/da3954210815138d76e9bf413cc344f96c015f40/hatebu/index.js#L72-L87" rel="noopener noreferrer"&gt;HTML extraction using regular expressions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;No longer need to pay IFTTT Pro's annual &lt;code&gt;$34.99&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;AWS costs stay within the free tier&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Running error-free for about 2 weeks&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implemented with Claude Code
&lt;/h2&gt;

&lt;p&gt;After starting to pay for IFTTT Pro, I had the concept in my head, but since it was essentially refactoring, I couldn't get motivated and left it alone.&lt;/p&gt;

&lt;p&gt;Claude Code helped me overcome that lack of motivation, and I made great use of it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Migration from JavaScript to TypeScript

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/masutaka-feed/pull/74" rel="noopener noreferrer"&gt;https://github.com/masutaka/masutaka-feed/pull/74&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Migration from IFTTT Pro to AWS self-implementation

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/masutaka/masutaka-feed/pull/77" rel="noopener noreferrer"&gt;https://github.com/masutaka/masutaka-feed/pull/77&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 Each commit message in the pull requests contains Claude Code prompts.&lt;/p&gt;

&lt;p&gt;Resource definitions in template.yaml are tedious to research, but Claude Code wrote them in one shot. Of course, I verified them afterwards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EventBridge Schedulers definition&lt;/li&gt;
&lt;li&gt;DynamoDB Tables definition&lt;/li&gt;
&lt;li&gt;IAM Role settings for each resource&lt;/li&gt;
&lt;li&gt;CloudWatch Alarm monitoring definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By self-implementing IFTTT Pro's feed notification feature with AWS serverless architecture, I was able to gain the following benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost reduction: Annual fixed cost of &lt;code&gt;$34.99&lt;/code&gt; became essentially zero&lt;/li&gt;
&lt;li&gt;Transparency gained: Full control over what items are added to feeds and what items have been marked as read&lt;/li&gt;
&lt;li&gt;Enhanced monitoring: Can set up monitoring for feed subscriptions and detect failures immediately&lt;/li&gt;
&lt;li&gt;Code optimization: Optimized feed parsing using rss-parser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, by utilizing Claude Code, I was able to significantly lower the motivation barrier for tasks like migrating from JavaScript to TypeScript and creating SAM templates.&lt;/p&gt;

&lt;p&gt;I will continue to operate masutaka-feed at a low altitude going forward.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Example: &lt;a href="https://github.com/masutaka.private.atom?token=xxxx" rel="noopener noreferrer"&gt;https://github.com/masutaka.private.atom?token=xxxx&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Example: &lt;a href="https://b.hatena.ne.jp/masutaka26/favorite.rss?key=xxxx" rel="noopener noreferrer"&gt;https://b.hatena.ne.jp/masutaka26/favorite.rss?key=xxxx&lt;/a&gt;&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>rss</category>
      <category>aws</category>
      <category>github</category>
      <category>mastodon</category>
    </item>
    <item>
      <title>Added llms.txt and llms-full.txt to My Hugo-built Website</title>
      <dc:creator>Takashi Masuda</dc:creator>
      <pubDate>Sun, 18 May 2025 12:38:45 +0000</pubDate>
      <link>https://dev.to/masutaka/added-llmstxt-and-llms-fulltxt-to-my-hugo-built-website-343i</link>
      <guid>https://dev.to/masutaka/added-llmstxt-and-llms-fulltxt-to-my-hugo-built-website-343i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note: This article reflects the setup as of Hugo 0.138.0.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is llms.txt?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a Markdown-formatted text file proposed to address the limitation that LLMs have small context windows and cannot process entire websites.&lt;/p&gt;

&lt;p&gt;It was proposed on September 3, 2024, by Jeremy Howard of Answer.AI at &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;https://llmstxt.org/&lt;/a&gt;. It is not defined in an RFC like robots.txt.&lt;/p&gt;

&lt;p&gt;The format includes certain specifications such as the website name in an H1 section, a brief summary, and a list of links in H2 sections.&lt;br&gt;
Example: &lt;a href="https://llmstxt.org/llms.txt" rel="noopener noreferrer"&gt;https://llmstxt.org/llms.txt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's also &lt;code&gt;llms-full.txt&lt;/code&gt;, which contains all website information.&lt;br&gt;
Example: &lt;a href="https://developers.cloudflare.com/llms-full.txt" rel="noopener noreferrer"&gt;https://developers.cloudflare.com/llms-full.txt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are directory sites for llms.txt as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://llmstxt.site/" rel="noopener noreferrer"&gt;https://llmstxt.site/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://directory.llmstxt.cloud/" rel="noopener noreferrer"&gt;https://directory.llmstxt.cloud/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://llmstxthub.com/" rel="noopener noreferrer"&gt;https://llmstxthub.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The llms.txt and llms-full.txt I Added
&lt;/h2&gt;

&lt;p&gt;I've added both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://masutaka.net/llms.txt" rel="noopener noreferrer"&gt;https://masutaka.net/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://masutaka.net/llms-full.txt" rel="noopener noreferrer"&gt;https://masutaka.net/llms-full.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How to Configure in Hugo
&lt;/h2&gt;

&lt;p&gt;The necessary work involves modifying or creating just three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;config.toml (modify)&lt;/li&gt;
&lt;li&gt;layouts/index.llms.txt (create new)&lt;/li&gt;
&lt;li&gt;layouts/index.llmsfull.txt (create new)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  config.toml
&lt;/h3&gt;

&lt;p&gt;I added the following to config.toml:&lt;/p&gt;

&lt;p&gt;Defining &lt;a href="https://gohugo.io/configuration/output-formats/" rel="noopener noreferrer"&gt;outputFormats&lt;/a&gt; for &lt;code&gt;llms&lt;/code&gt; and &lt;code&gt;llmsfull&lt;/code&gt; while adding them to the default value of &lt;a href="https://gohugo.io/configuration/outputs/" rel="noopener noreferrer"&gt;outputs.home&lt;/a&gt;, which is &lt;code&gt;["html", "rss"]&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[outputs]&lt;/span&gt;
  &lt;span class="py"&gt;home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"llmsfull"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[outputFormats.llms]&lt;/span&gt;
  &lt;span class="py"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llms"&lt;/span&gt;
  &lt;span class="py"&gt;isPlainText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;
  &lt;span class="py"&gt;rel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alternate"&lt;/span&gt;
  &lt;span class="py"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[outputFormats.llmsfull]&lt;/span&gt;
  &lt;span class="py"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"llms-full"&lt;/span&gt;
  &lt;span class="py"&gt;isPlainText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="py"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;
  &lt;span class="py"&gt;rel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alternate"&lt;/span&gt;
  &lt;span class="py"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  layouts/index.llms.txt
&lt;/h3&gt;

&lt;p&gt;I created &lt;code&gt;layouts/index.llms.txt&lt;/code&gt; as a template for &lt;code&gt;llms.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# {{ .Site.Title }}

&amp;gt; {{ .Site.Params.Description }}


## Articles
{{ $yearMonthGroups := slice -}}
{{ range where (where (sort (.Site.GetPage "/posts/").Pages "Date" "desc") "Draft" "ne" true) "Sitemap.Disable" "ne" true -}}
{{ $yearMonth := .Date.Format "2006/01" -}}
{{ if not (in $yearMonthGroups $yearMonth) }}
* {{ $yearMonth -}}
{{ $yearMonthGroups = $yearMonthGroups | append $yearMonth -}}
{{ end }}
    * [{{ .Title }}]({{ .Permalink }})
{{- end }}


## Others

{{- $baseURL := .Site.BaseURL | strings.TrimSuffix "/" }}

* [Device]({{ $baseURL }}/device/): 所有デバイス
* [History]({{ $baseURL }}/history/): このサイトの歴史
* [About]({{ $baseURL }}/about/): 自己紹介
* [Privacy Policy]({{ $baseURL }}/privacy/): 当サイトの広告、アフィリエイト、プライバシーポリシー
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since Hugo site structures can vary widely, this is just one example. My blog has both blog posts and other articles, and since other articles don't change much, I hardcoded them in the Others section.&lt;/p&gt;

&lt;h3&gt;
  
  
  layouts/index.llmsfull.txt
&lt;/h3&gt;

&lt;p&gt;I created &lt;code&gt;layouts/index.llmsfull.txt&lt;/code&gt; as a template for &lt;code&gt;llms-full.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# {{ .Site.Title }}

&amp;gt; {{ .Site.Params.Description }}


{{/* Articles */}}
{{ range where (where (sort (.Site.GetPage "/posts/").Pages "Date" "desc") "Draft" "ne" true) "Sitemap.Disable" "ne" true }}
--------------------------------------------------------------------------------
title: "{{ .Title }}"
date: "{{ .Date.Format "2006-01-02" }}"
--------------------------------------------------------------------------------
{{ replaceRE "{{&amp;lt;\\s*comment\\s*&amp;gt;}}(.|\n)*?{{&amp;lt;\\s*/comment\\s*&amp;gt;}}" "" .RawContent -}}
{{ end -}}


{{/* Others */}}
{{ range slice "device.md" "history.md" "about.md" "privacy.md" -}}
{{ with site.GetPage . -}}
--------------------------------------------------------------------------------
title: "{{ .Title }}"
lastmod: "{{ .Date.Format "2006-01-02" }}"
--------------------------------------------------------------------------------
{{ replaceRE "{{&amp;lt;\\s*comment\\s*&amp;gt;}}(.|\n)*?{{&amp;lt;\\s*/comment\\s*&amp;gt;}}" "" .RawContent -}}
{{ end -}}
{{ end -}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made the delimiter longer because some articles contained &lt;code&gt;---&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, I made sure comments like the following wouldn't be included in llms-full.txt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;{{&lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;}}
This is a Hugo comment. It won't be output as an HTML comment either.
{{&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  nginx Configuration
&lt;/h2&gt;

&lt;p&gt;Since llms.txt and llms-full.txt were showing character encoding issues, I added the following to the server directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;"^/llms(-full)?\.txt"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;UTF-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I added llms.txt and llms-full.txt on my Hugo-built blog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://masutaka.net/llms.txt" rel="noopener noreferrer"&gt;https://masutaka.net/llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://masutaka.net/llms-full.txt" rel="noopener noreferrer"&gt;https://masutaka.net/llms-full.txt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardless of whether there's demand for it, I'm satisfied with the result.&lt;/p&gt;

&lt;p&gt;As a side note, I initially found a method on &lt;a href="https://discourse.gohugo.io/t/support-for-llms-txt-standard-for-ai-crawlers/53782/3" rel="noopener noreferrer"&gt;Hugo Discourse&lt;/a&gt; that used &lt;code&gt;resources.ExecuteAsTemplate&lt;/code&gt; in &lt;code&gt;layouts/robots.txt&lt;/code&gt; to generate llms.txt. However, I decided not to use this approach since including llms.txt in robots.txt is not yet common practice.&lt;/p&gt;

&lt;p&gt;Another side note: in the past, my blog was created by converting a single ChangeLog-formatted file into HTML using a tool called &lt;a href="https://chalow.org/" rel="noopener noreferrer"&gt;chalow&lt;/a&gt;. Creating llms-full.txt reminded me of those days. It feels like I've come full circle.&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>llm</category>
      <category>llmstxt</category>
    </item>
  </channel>
</rss>
