How To Migrate From Rails to Nextjs: The Simple Edition

How To Migrate From Rails to Nextjs: The Simple Edition - Webdesign Antwerpen

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

I've always liked spending time on my personal site.

My own special little place on the internet where I can do whatever I want.

But, the previous version of my site was nearing 8 years old 👵 and started to show its age and limitations.

Adding new content became a hassle, forced updates were frequent, and just the entire experience started to feel more like work than play.

So I decided it was time for an upgrade!


Downsides 👎

Downsides, there were plenty...

But the ones that really annoyed me were;

  • It wasn't very fast 🐌
  • It needed regular maintenance (database management, server upgrades, S3 asset handling, rails/ruby upgrades...)
  • The friction to write an article was HUGE. (Mainly since I used a custom-built CMS 😅)
  • Content was hard to migrate to other places or frameworks
  • Releasing new versions/updates would be slow

Criteria 👍

For the next version, I wanted something that:

  • Would be blazing fast 🔥. As in: the fastest that the internet has to offer. (Or at least close to that)
  • Would be simple in tech-stack and easy to manage: No server upgrades, no database management, no microservices, no docker, no S3 asset handling and no stinking CORS. Something that would make having a personal site feel less like work.
  • Enables me to EASILY write articles in Markdown directly in my favourite note-taking app (Obsidian). But also allows me to preserve the existing content.
  • Would make my data portable. So in the future I could just take my JSON/Markdown content and move/import them into the next shiny thing.
  • Would be customizable and un-opinionated since I want to be able to "escape" out of the template and do whatever I want! To be able to exert the little creative freedoms I have as a developer.

Beautiful.

Conclusion: I wanted just a plain and simple static site where the data is read from .json/.md files and renders everything at build time.

Pretty straight-forward right?

Wroong!

Paradox of choice

Currently, there are at least a million-bazillion frameworks that do exactly that and promise you the world.

The ones I looked at were;

  1. Gatsbyjs: https://www.gatsbyjs.com/
  2. Hugo: https://gohugo.io/
  3. Jekyll: https://jekyllrb.com/
  4. Next.js: https://nextjs.org/
  5. Ghost: https://ghost.org/

Since all those choices don't differ that much and all will give a great experience, I decided to just keep it simple and go for the most popular one.

Experience (mainly pain) has taught me that whatever is popular has great documentation, many people actively finding bugs and enough stack overflow snippets to fall back upon when the going gets rough (because inevitably it will).

So Next.js it was.

Let's go!

The journey 🧗‍♂️

To migrate my old clunky rails app, there were 5 main steps I had to take;

  1. Extract the data 🗄️
  2. Create the pages ⚒️
  3. Integrate Obsidian for writing ✍️
  4. Optimize performance 🔥
  5. Deploy to the cloud 🚀

Extracting the data 🗄️

I only had 3 resources I really cared about: articles, testimonials and projects that were living in a Postgres db.

To extract this, I added a simple JSON endpoint in my rails controllers to extract all the data;

def list
  @projects = Project.all.includes(:project_images_attachments).sort_by(&:created_at).reverse
  render json: @projects
end

This allowed me to just copy several JSON files with a flat data structure to work with.

// projects.json
{
  "id": 3,
  "title": "Video Course App",
  "description": "Dit was een persoonlijk project waarin ik een video-cursus heb uitgebouwd om mijn vaardigheden aan te scherpen. In deze cursus kan de gebruiker de lessen doorlopen, actiepunten implementeren, comments achterlaten en zijn vooruitgang bijhouden over de verschillende hoofdstukken. \r\n\u003cbr/\u003e\u003cbr/\u003e\r\nOver het algemeen een leuk project :) - misschien dat ik er in de toekomst nog iets mee doe! \u003cbr/\u003e\u003cbr/\u003e\r\n\r\n\u003cstrong\u003eDemo login: \u003c/strong\u003e demo@email.com \u0026 password",
  "features": {
    "responsive": "icon-resize-horizontal",
  },
  "link": "https://bg-video-course.herokuapp.com/",
  "service": "webapplicatie",
  ...
}

Creating the pages ⚒️

The next step would be to use that data in my application to loop over the different resources and show some markup.

I briefly contemplated to go for a shiny new theme/website, but I didn't really find something that's;

  • A one-pager
  • Minimal
  • Boxed (not full-width)
  • Could serve both as a portfolio and personal blog

So I stuck to my existing 10-year-old HTML templates full of floats and !important CSS rules that I bought for $10 in college 😅

But I still think it looks pretty nice after all this time!

The first steps were pretty straight forward;

  • Copy the rendered HTML for my homepage in the developer tools and convert it to JSX with https://transform.tools/html-to-jsx
  • Extract the styling with uncss (https://davidwalsh.name/uncss) to only get the used style rules per page
  • Replace some old jQuery libraries (sliders/lightboxes) with more modern variants and tweak those a bit. Most of the code is simple JSX with simple loops;
import { Swiper, SwiperSlide } from "swiper/react";
import { articles } from "./articles.json";

<Swiper>
  {articles
    .filter((article) => article.posted)
    .map(({ title, description, slug, headerImage }) => (
      <SwiperSlide key={slug} className="owl-item active">
        <div className="oc-item">
          <div className="ipost clearfix">
            <div className="entry-image">
              <Link href={slug}>
                <img
                  placeholder="empty"
                  alt={`${title} - Webdesign Antwerpen`}
                  className="image_fade"
                  src={headerImage}
                />
                )}
              </Link>
            </div>
            <div className="entry-title">
              <h3>
                <Link href={slug}>{title}</Link>
              </h3>
            </div>
            <div className="entry-content">
              <p>{description}</p>
            </div>
          </div>
        </div>
      </SwiperSlide>
    ))}
</Swiper>;

Integrate Obsidian with Next.js blog ✍️

Since I use Obsidian for pretty much all my note-taking, journaling and book summaries, I wanted to use that squeaky-clean markdown writing experience for my personal site as well!

I didn't want to use VSCode as content editor since it just isn't the right tool for the job.

Integrating these two, allows me to;

  1. Have an unmatched writing experience
  2. Keep all my notes/writing in one place (source of truth) and selectively publish (copy) whatever I want to the web. Whether these are code snippets, book summaries or full-fledged articles.
  3. Have great support for (open-source) writing tools (like support for ChatGPT, spelling/grammar autocorrection, illustration/image generation, URL validation and whatever else the community can come up with).
  4. Have image/video drag-and-drop
  5. Instant live preview mode

To do this, I added a simple sync command that would listen to file changes in my Obsidian vault and simply copy the entire directory (including video's and images) into my codebase.

// package.json
{
  "sync": "sh ./scripts/sync.sh",
  "write": "concurrently --kill-others \"yarn dev\" \"yarn sync\""
}
# sync.sh
fswatch -o '/Desktop/writing' | xargs -I{} cp -R "'/Desktop/writing/migrating-rails-to-nextjs" "/simonsomlai/public/articles/"

Combining this with the concurrently package and the Next.js dev server, enabled me to have an instant preview mode on my blog!

Behold:

Performance optimization 🔥

So most of the heavy lifting was done!

The pages were working, everything was rendering nicely, and the writing experience felt buttery-smooth. 🧈

And although Next.js comes with great performance out-of-the-box, it doesn't automatically optimize the images when you render as a static site.

Therefore, I added-in next-image-export-optimizer. This gives you an ExportedImage component that you have to wrap around your images, and it'll do all the heavy lifting for you!

<Link href={fullSlug}>
  <ExportedImage
    placeholder="empty"
    alt={`${title} - Webdesign Antwerpen`}
    src={headerImage}
    width={161}
    height={136}
  />
</Link>

Afterward, when comparing the final result with Google lighthouse, it did make a big difference compared to the older rails version!

BeforeAfter

Deploy 🚀

To deploy, there are also a million things you could pick, but previously I've had great experiences using netlify.com. So that's what I decided to go with.

This couldn't be easier, since Netlify supports Next.js out-of-the-box with (almost) 0 configuration. I just had to link my Github account, pick a branch and presto!

I had a new version of my website accessible to the internet within 1 minute;

After that, I did some more small tweaks;

  • Generating a sitemap using next-sitemap
  • Adding client-side redirects for my previous /en pages
  • Code highlighting using highlight.js
  • Support for .mdx (MDX) instead of just markdown
  • Added in Google Analytics

Conclusion

There's still quite a lot left to be desired, but all-in-all it's a nice upgrade that allows my site to last for at least another 10 years.

(unless AR takes over the world)

I'm not part of the 10kbclub , it's not the best motherfuckingwebsite on the internet and it doesn't include every latest shiny library, but it does strike a nice balance between simplicity and being feature rich.

And that's enough.

Most websites don't really need complex AWS setup, 10-step CI/CD pipelines, docker containers, Kubernetes clusters, sub-millisecond response times or a microservice clusterfuck.

Sometimes you can do just fine serving some static HTML, CSS and JS to the user.

Sometimes simpler is better.

References

Here's some more cool people/sites that inspired me during the writing of this article that I highly recommend you to check out;

If you want to take a look at the code, I've uploaded a bare-bones example here. Maybe you can use a thing or two from it!

GitHub: https://github.com/SimonSomlai/site-migration-example

Comments