# fgilio.com > A blog about code and useful tips ### Going static, 2026 edition A little over a year ago I wrote about [going static](/going-static/), moving this site from a Cloudways server to Cloudflare Pages using WordPress + the Simply Static plugin. At the end of that post I said "I'm excited to work on simplifying it and maybe base it around Markdown... we'll see." Well, here we are. The old setup worked, but it was annoying. I had to run a full WordPress install locally via [Laravel Herd](https://herd.laravel.com/), write a post, export the entire site with Simply Static, and then manually deploy. It felt like driving a truck to get groceries. **So ~~I~~ Claude Code rebuilt the whole thing with [Hugo](https://gohugo.io/).** The source of truth is now a folder of Markdown files in a git repo. No database, no PHP, no plugins. Just text files that I can edit with whatever I want. Hugo builds the site in milliseconds and the output goes to Cloudflare Pages as static assets. **What I cared about:** - **Keep every old URL working**: Hugo generates the same `/:slug/` permalink structure WordPress used. No redirects needed, no broken links. This was non-negotiable. - **Markdown as the source of truth**: writing and versioning in the same place. `git log` is my changelog now. - **Fast hosting**: Cloudflare Pages for static assets is extremely fast and free. - **llms.txt support**: I added [llmstxt.org](https://llmstxt.org/) compliant output so AI tools can read my content properly. Every post gets a `/index.md` alongside the HTML, plus there's a site-wide `/llms.txt` and `/llms-full.txt`. The entire migration that included export script, custom Hugo theme, llms.txt templates and deployment config was done in a couple of Claude Code sessions. The theme is called `vivre`. It's plain CSS, no build tools, no JavaScript frameworks. Just [Petrona](https://fonts.google.com/specimen/Petrona) for body text and [Work Sans](https://fonts.google.com/specimen/Work+Sans) for headings. I wanted something that felt warm and emphasized the content. I'm very happy with this setup. Writing a new post is now: create a `.md` file, write, `hugo server -D` to preview, push, deploy. That's it. The simplicity I was looking for all along. PD: If you're curious about the llms.txt stuff, try hitting [/llms.txt](/llms.txt) or [/llms-full.txt](/llms-full.txt) on this site. Pretty cool, right? I no longer have comments enabled here, but you can reach me on [X](https://x.com/fgili0). ### Going static I've been using WordPress for more than 10 years, both for fun and serious projects - from million-visit websites to custom apps when the WP REST API was still a proof of concept... long before the Gutenberg editor was even a thing. While my writing cadence is extremely unpredictable, I want to share more about my journey. And I aim to do it with a simple setup, both in terms of writing and hosting. **Why a "Static Site"?** I've been hosting [fgilio.com](https://fgilio.com/) on a simple server managed by [Cloudways](https://cloudways.com/) for a very long time. Honestly, I hardly take advantage of any of the possibilities of "owning" the server. It felt like a waste of money and resources. I've been sporadically thinking about migrating to something else for a while. I didn't want to manage a server, and I'd prefer to host it for free. That, of course, limits the options... I toyed with the idea of using [Statamic](https://statamic.com/), but in the end, I decided to start with another solution and see how it feels. **Enter Cloudflare Pages** I decided to host it on [Cloudflare Pages](https://pages.cloudflare.com/) as a static site. Here's how I did it: **1. Export Everything from Cloudways** First, I exported the site's data from [Cloudways](https://cloudways.com/). **2. Install Laravel Herd** Next, I installed [Laravel Herd](https://herd.laravel.com/), which streamlined the local development environment setup as I don't really want to manage local PHP versions and such. I'd use Docker for more serious things, but this is not one of those things. **3. Set Up Site's Backup with SQLite** Following a guide from [Fractolog](https://www.fractolog.com/2024/06/installing-wordpress-on-sqlite/), I set up my site's backup using SQLite. This was a cool thing I wanted to experiment with, and helps a bit in keeping things simple. **4. Export to Cloudflare Pages Using Simply Static** Finally, I exported the site to Cloudflare Pages using the [Simply Static](https://simplystatic.com/) plugin, by following this [Cloudflare guide](https://developers.cloudflare.com/pages/how-to/deploy-a-wordpress-site/). **Current Setup and Next Steps** This setup isn't 100% what I want yet. I'm still running WordPress locally to author the site and have to manually export and deploy. It's far from ideal, but it's a step in the right direction. I'm excited to work on simplifying it and maybe base it around Markdown... we'll see. I guess the next step will be automating the deployment. How About You? Do you have a blog or personal site? I no longer have comments enabled here, but you can reach me on [X](https://x.com/fgili0). --- *CTO at [publica.la](http://publica.la/), leading an awesome team and changing the way digital publishing works. Full stack developer passionate about building the web at scale, constantly learning, and always looking for exciting challenges. I love to automate-all-the-things and consider myself a die-hard suprematist. All about code and Open Source, podcasts, blogs, and **lots** of coffee.* #HappyCoding ### How to set a column's position in a Laravel migration In a past post I wrote about [How to move a column in a Laravel migration](https://fgilio.com/how-to-move-a-column-in-a-laravel-migration/), but sometimes you're better prepared and want to define the position of the column while creating it. Fortunately Laravel makes this real simple and we can fluently decide if a column must be positioned first or after another. To put the column in the first place we use `->first()`, for example: ```php // Place the column first Schema::table('users', function (Blueprint $table) { $table->string('external_id')->first(); }); ``` To put the column after another we use `->after('column')`, for example: ```php // Place the column after another column Schema::table('users', function (Blueprint $table) { $table->string('external_id')->after('id'); }); ``` And that's all, I hope someone finds it useful. ### How to move a column in a Laravel migration This post could have also be titled "How to move a column in MySQL" or "How to move a column with SQL"... I guess you get it. Sometimes you just have to *ship it and ship it now*™ and, in the heat of the moment, you could oversee the order of some database columns. Or maybe you just have OCD and need to see things organized like they should be (don't judge me, I'm only human). Well, worry not! While this functionality is not built in Laravel it is actually quite simple to do with a raw DB query, here you have an example in which we're moving the `external_id` column after the `id` one: ```php use Illuminate\Support\Facades\DB; // This goes inside a migration file DB::statement('ALTER TABLE users MODIFY COLUMN external_id VARCHAR(255) AFTER id'); ``` Those of you paying attention would have picked up that we are redefining the column type (`VARCHAR(255)`), it's a little of an annoyance but it's also required. In case you don't know the syntax for a column you want to move, you could use a program like Table Plus or Sequel Pro to copy the table's insert statement and extract the portion corresponding of the column in question. Have you ever needed to do such a thing? **2019/07/16 EDIT**: [How to set a column's position in a Laravel migration](https://fgilio.com/how-to-set-a-columns-position-in-a-laravel-migration/) ### Learn how to import a function() in PHP TIL\*¹ you can **import just a function** in a .php file, like we do with classes. It works from [PHP 5.6+](http://php.net/manual/en/language.namespaces.importing.php), so there's no weird compatibility issue. And it's dead simple. Here's an example from [Safe PHP](https://github.com/thecodingmachine/safe), specifically let's say we want to use the safer\*² version of `json_decode`. Pay attention at how this allows us to override PHP's default `json_decode` global function: ```php // First we import it use function Safe\json_decode; // And then we just use it $foobar = json_decode($content); ``` Yes, it's not a jode. That's all. You tell PHP that what you're importing is a function and the just use the function's name instead of a class name. After that, you can use said function in you code as if it were a global or local function. How cool is that?! ------------------------------------------------------------------------ - \*1 Not really today, this post was in draft state since 2017/12/22 o.o - \*2 These are a set of core PHP functions rewritten to throw exceptions instead of returning `false` when an error is encountered. For more information about this project you can go to their repo ### How to mark something as binary in git Sometimes you may want to override git's decision of wheter a file contains text or binary data. For example, you may have to pull in some external library and want git to track it but not diff it inside. For this, git lets you define what to do with specific files or entire directories. Simply add a `.gitattributes` file in the root of the project, or in the parent directory of the one you want to affect, and use the following syntax: ``` # For files file.txt binary # For entire directories folder/* binary ``` ### How to use Laravel's Job chaining This is a personal one, I simply love how clean and simple this feature is. In the past, I've had to implement job chaining and TBH the end result was pretty gross. It was a system that needed to take a file and pass it through a series of steps, so nothing too complex. But, by the way the chain worked, every job was conscious about the next one in line (sans the last one, of course). But now, how Laravel does it allows us to completely decouple the jobs from each other. Yeah, I know, it's awesome. It looks like this: ```php FirstJobToRun::withChain([ new SecondJobToRun, new ThirdJobToRun, new ForthJobToRun ])->dispatch(); ``` So yes, it's that simple. But if you are like me, simple is never enough cause of course you have that one case in which you need to pass arguments to your jobs. So, how about that? Well, thankfully it's simple too. Here in this example I've taken the one from the [official docs](https://laravel.com/docs/5.5/queues#job-chaining) and added a simple parameter: ```php ProcessPodcast::withChain([ new OptimizePodcast($podcast), new ReleasePodcast($podcast) ])->dispatch($podcast); ``` And that's all, now go [read the official docs](https://laravel.com/docs/5.5/queues#job-chaining) and then refactor all those tangled job chains that you've written before. Thank you to all those who worked on this. PD: Wouldn't it be really cool if we could just define common arguments for all jobs in a chain? ### Working with discount coupons in Laravel Spark **EDIT 2018/04/04**: This is applicable to both Spark 6 and 5 **EDIT 2018/10/19**: This posts was made for Spark 5, but most likely it still perfectly applies to Spark. I've read the changelog and upgrade instructions, and those do not mention any changes regarding coupons. I'll update this post once we upgrade to Spark 7. A couple of days ago it was Black Friday and at [publica.la](https://publica.la) we decided to put out a beefy discount for new customers. Thankfully we use Laravel Spark to handle all the SaaS boilerplate needed to bill our clients, but while it does offer full support for discount coupons... IMO the [docs](https://spark.laravel.com/docs/5.0/billing#site-wide-promotions) are a bit lacking. I even [tweeted](https://twitter.com/fgili0/status/933759259250184192) asking for help but, ironically, all I got was a [response](https://twitter.com/dev_jagroop/status/933988225256787968) from a guy that though I was looking for a discount to actually buy Spark. \* I'm going to make the assumption that you're using Stripe as the gateway, but Braintree should be pretty similar (tell us in the comments if you know about it). Spark offers two different ways to go about coupons: **1.** Individually with every purchase, like you would expect from any kind of system. This is the one that's missing in the docs. Basically, you need to setup your coupons 100% on [Stripe](https://stripe.com/docs/subscriptions/discounts) side and then use it as a query string on your Spark powered site. So, for example: ``` https://your.site/?coupon=coupon_code_here ``` With that in place, Spark will then automatically validate it against Stripe and show an input box in case the user wants to change the code. **2.** And the other is called **site-wide promotions**, which is exactly what we needed! The way it works is by globally forcing a coupon code in the query string section of the URL (don't worry, it won't mess with any existing values), but still letting the users change it case they want to. Yep, perfect for a Black Friday kind of deal. All you need to do is use this code: ```php /* * This will probably go in your SparkServiceProvider.php */ Spark::promotion('coupon_code_here'); ``` ### So you wanna yarn add a dependency from a git repo? I'm posting this because, even if really basic stuff, I've bumped my head with this wall way too many times before successfully managing to accomplish the task: Use `yarn add` to pull a dependency while using a git repo as the source. It is, in fact, really simple (though I feel is way to verbose (? ): ``` bash yarn add git+ssh://git@gitlab.com:organization/project ``` You can even specify use a branch or particular commit by adding it at the end, like this: ``` bash # To point to a branch: yarn add git+ssh://git@gitlab.com:organization/project#dev # To point to a commit we just use the hash (short one is this case): yarn add git+ssh://git@gitlab.com:organization/project#c8023772 ``` It's also listed in the official docs: https://yarnpkg.com/lang/en/docs/cli/add/ So, have you ever had to do this? Do you find it useful? ### Easily transfer entire local directories to Amazon S3 using s3-parallel-put A couple of weeks ago I faced the need to upload a large number of files to Amazon S3, we're talking about lots of nested directories and \~100gb. So, after entering panic-mode for a couple of seconds I turned to our trusty Google and kindly filled its input with \"transfer from local to amazon s3\" (well, I don't really remember what I searched). I was not feeling really hopeful until I found **s3-parallel-put**, which seemed to do just what I needed. Here's the repo: https://github.com/mishudark/s3-parallel-put It's a smart little phyton script that does just that, transfer possibly huge amounts of files to Amazon S3. And, yes it can parallelize the workload making it blazing fast. It has a couple of dependencies: ``` bash # Make sure to have pip updated. # You may need to use sudo apt-get update && apt-get -y install python-pip pip install boto pip install python-magic ``` Then, to install it, you just have to download the thing and make it executable: ``` bash curl https://raw.githubusercontent.com/mishudark/s3-parallel-put/master/s3-parallel-put > s3-parallel-put chmod +x ./s3-parallel-put ``` It needs the AWS credentials as environment variables, which you can easily set: ``` bash export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= ``` And, finally, you fire it up like this: ``` bash # This is considering that the script is in the current directory ./s3-parallel-put --bucket= --bucket_region=us-west-2 --put=update --processes=30 --content-type=guess --log-filename=./s3pp.log /path/to/source/directory ``` You can do a dry run with `--dry-run`. You can speed up the upload using `--put=stupid`. It won't check if the object already exists, thus making fewer calls. Use with caution. You can grant public read access to objects with `--grant=public-read`. You may noticed that you can specify a log file, which is really handy because sometimes stuff happens. But, you may also end up with an enormous log file. So here is a quick grep to search for any errors `grep "ERROR" s3pp.log`. And that's all. It has a lot more options that might come handy depending on your needs,so I encourage you to go and check it out. Thanks for reading, and I hope you find this as useful as I did. Let me know in the comments if you have any tips. ### Cherry-picking your way out of trouble I find `cherry-pick` to be one of those great underutilized features of git. And maybe that's good, because it's mainly used to apply hot fixes. The way it works is very simple, it just let's you merge one or more commits from one branch onto another. Awesome, right? Imagine a situation in which you have two branches, master and payments-refactor. You're battling your way out of a thought refactor and suddenly a bug emerges in production, but you find out that you've already fixed it during the refactor and have an isolated commit containing the changes. You need to replicate those changes in the master branch and re deploy the app. But copy-pasting, or manually re doing the changes, is cumbersome and probably even error prone. Well, `cherry-pick` comes to the rescue. It let's us replicate that single commit onto the master branch, all while preventing duplicate work and keeping our git history clean. The only thing we need is the abbreviated commit hash (or all of it), we move to the branch where we want to incorporate the changes and use it like this: ``` bash git checkout master git cherry-pick 3f75a585 ``` That's all! I hope you have to use this as little as possible, but find it useful when the time comes. What do you think of `cherry-pick`, what do you use it for? ### How to keep an SSH session alive \* This is a *Has it ever happened to you\...* kind of post. Imagine you're logged in to a server doing some magical stuff. Then you go grab a coffee and when you come back\... you're logged out from the server. Yes, it sucks. You have to SSH in again and cd into the same dir you were before, etc. [Ain't nobody go time for that](https://fgilio.com/wp-content/uploads/2017/08/Aint-nobody-got-time-for-that.jpg). What if I told you that you can keep an SSH session alive? 🚀 All you have to do is edit your `~/.ssh/config` file and add the following: Host * ServerAliveInterval 60 You can define a specific host, and choose the interval. Most servers with which I have this issue have a rather low timeout, so I've chosen to send the keep alive signal every 60 seconds. And baam, you've freed yourself from this annoyance. [rockymonkey555](https://stackoverflow.com/questions/25084288/keep-ssh-session-alive) over [stackoverflow.com](stackoverflow.com) recommends to also `chmod 600 ~/.ssh/config`, \"because the config file must not be world-readable\". I hope this is as useful to you as it's been for me. ### The first one This is the first post on this brand new blog, and it has a very descriptive title. I wanted to start a blogging for some time, but always ended up postponing it. Mainly because of a generous dose of impostor syndrome, but also because I haven't been good at making the time to make the blog, in the first place. And, yes, as a developer I wanted my blog to be just perfect. So, here we are. This is me forcing myself to start sharing, on a sketchy and rushed blog. Also, I love WordPress and this gives me an excuse to tinker with it a little more ever since I stopped working with it daily. I honestly have no clue if this is going to be a weekly thing, monthly or whatever. Though it won't be daily for sure. Let's just see how it feels to share some thoughts, things I learn or anything else. Talk to you in the next one! *PD 1*: I think most of the next posts will be just some meticulously crafted micro tutorials/reminders of some of the code snippets I have stored like everywhere. *PD 2*: As you can see there's no comments available here, but there will be in the upcoming entries. *PD 3*: Saw that line above where I said that "I wanted to start a blogging for some time", well I've this post drafted out for 7 months. ### About CTO in [publica.la](http://publica.la), leading an awesome team and changing the way digital publishing works. I'm full stack developer passionate with building the web at scale, constantly learning and always looking for exciting challenges. I love to automate-all-the-things an consider myself a die hard suprematist. I'm all about code and Open Source, podcasts, blogs and **lots** of coffee. [twitter.com/fgili0](https://twitter.com/fgili0) [github.com/fgilio](https://github.com/fgilio) and [gitlab.com/fgilio](https://gitlab.com/fgilio) [linkedin.com/in/franco-gilio-a6762641/](https://www.linkedin.com/in/franco-gilio-a6762641/)