Sep 25 2023 | Iwá Duarte | 15 min read
That it is not me, but indeed is chilling on this paradisaical island (DALLE-2).
When I started creating my site, I had in mind two things:
These ideas were old (early 2018). Way older than the influx of new developers, COVID-19, the IT crisis, and layoffs. I wanted to enter, at the time, a very competitive market: working remotely. For that, I needed a stunning website. I did not do much at first, my first website was kind of ugly, it had no articles but a dummy bot that caught the attention of just one nice recruiter.
Forward to 2022, I needed a better UX/UI and my sister helped me create a design for me. One thing came to mind, where would I put my articles? I wanted to write things, and make jokes, and make my mistakes.
One great platform was Medium. Simple, I thought. I will just hyperlink everything to Medium and get some data from their API to display only the initial information on my site. The problem is. Medium is not that great, they make integrations look bad. They discontinued their API and made it hard to scrape as well. People created tutorials on how to circumvent that. It is a pain to do it.
I barely had any articles, and one solution worked if you had less than 10 posts. So I did that. Serverless in mind, I would create a lambda function that would grab the page, parse the data, and return a formatted object to the requester.
//articles.js
const { XMLParser } = require("fast-xml-parser");
const axios = require("axios");
const parser = new XMLParser();
const response = {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
isBase64Encoded: false
};
exports.handler = async () => {
const xmlData = await axios
.get(`https://medium.com/feed/@iwaduarte`)
.then(({ data }) => data);
const javascriptObject = parser.parse(xmlData);
const items = javascriptObject?.rss?.channel?.item;
const newItems = items.map((item) => {
const {
category: categories,
guid,
title,
"dc:creator": author,
"content:encoded": content,
} = item;
const description = content.substring(0, 300);
const partialContent = content.substring(
content.indexOf(`<figure>`),
content.indexOf(`<figcaption>`)
);
const thumbnail = partialContent.replace(/.*src="(.+)".*/, "$1");
return { categories, guid, title, author, description, thumbnail };
});
response.body = JSON.stringify(newItems);
return response;
};
That would work for months and I would be okay if Medium did not limit the number of posts to 10 and if I did not have errors when grabbing content because my regex was not properly set (no matter how I changed it).
As you can see, the images are broken or incorrect, and only 7 articles are shown
To make things worse other people’s articles started to become blocked and not even clearing the cache would work anymore. They started limiting people. They started a massive enshitification through paywalls.
If you have not seen it I have also talked about that here. In short, enshitification is the act of making the services on the web worse over time.
And that started bothering me. I mean. I write on your platform, and you can not even give me the right to read articles? You do not even allow me to easily grab a few articles and post metadata to use on my site? You need therapy for your controlling issues mate. And I need to stay away from you (or at least partially).
My site is mostly static. I was looking for a solution where I could start writing that involved the minimum changes possible. After lots of investigation it came down to Next.js and Argo. With Argo being the recommended choice for my use case. No need for server-side mambo jambo, and thousands of tools, just the simplicity out of the box. The plug-and-play approach.
If you are already using CRA I recommend following these steps:
npm uninstall @vitejs/plugin-react
npm install astro @astrojs/react
vite.config.js
to astro.config.mjs
astro.config.js
rename import react from '@vitejs/plugin-react'
to import react from '@astrojs/mdx'
package.json
rename the "scripts"
properties commands from vite
to astro
pages
inside the root directoryindex.astro
inside pages
index.astro
and import your App.jsx
---
// index.astro
import App from '../App.jsx';
import '../style.css';
---
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="iwaduarte portfolio dev fullstack" />
<title>@iwaduarte</title>
</head>
<App client:only='react'/>
npm run dev
That should be sufficient to migrate or start your application. You are now using Astro as your framework. Astro uses the concept of micro-frontend and islands which means that you can use React, Svelte, and Astro combined without having to migrate anything.
Concerning blogs, Astro uses markdown by default as an option to create your blog posts. It is an easy markup language for creating formatted text that is adopted across several websites, it is well-supported and can be converted to HTML, PDF, etc. Websites like GitHub (README.md) and Reddit support user-generated markdown content which makes its use more appealing.
Astro gives you a powerful way of setting up content. Which is the content
folder. It is that simple!
The context folder will be important because will give you Astro APIs that allow you to grab info on your static files. Inside this content
folder you will have to put either valid .md (markdown) or .mdx files.
These files will contain all your text. Let`s set a very simple example so you can use it inside your platform:
content
folder inside the root folder.blog
folder inside content
my-first-post.mdx
---
title: My first post
subtitle: Hey, hey, hey.
description: ""
tags:
- introduction
author: Yo
date: "Some date"
slug: /blog/my-first-post
---
Every programmer would be like:
It doesn't work... Why?
It works...? Why
With markdown files, you can write things like ”###” and ”[]()” and that will respectively stylize a header text and create a link. It is so intuitive that once you get the gist of it and set things up will be writing like Stephen King!! Maybe, not like him. But at least fast.
Markdown does not support JavaScript by default. To solve that, and to add integration with jsx
mdx was created. You can use jsx combined with markdown. Which is great for framework components. You can extend mdx functionality with plugins. You can create content faster if use javascript only. I will show a great example later. For more info about mdx please check their amazing website: https://mdxjs.com/docs/what-is-mdx/
Now things start to get more serious. You have set up everything. Your migration to Astro works, and you need to import all your posts from Medium. This is a very delicate topic. I am sorry, but currently, it is not an easy task. It is a long process but with this guide, you will wish you could find me a buy me a coffee (I hate coffee, so I will pass).
If you do not want to port your old posts because you are averse to long processes, you can start writing brand new from your new Astro application and keep the old posts as hyperlinks. I do recommend however that you grab a cup of coffee, tea, or chocolate and embark with me on this journey.
We are going to use two packages: miry/medup and medium-to-markdown
If you have a linux or macOs system the installation will be a breeze.
brew tap miry/medup
brew install medup
For Windows is mandatory to use WSL. This package runs with the Crystal Language which is not fully supported in Windows.
wsl --install
sudo apt update
sudo apt upgrade
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
brew tap miry/medup
brew install medup
You have medup installed now.
medup -u USERNAME -d posts/USERNAME --assets-images
Note: If you are on Windows and want to access the folder path you can run:
explorer.exe .
You can grab all files and posts and move them to your content/blog/
folder previously set. This library is great at downloading formatted files but some errors could occur:
Apart from that the files are almost properly formatted in Markdown, which gives you 90% of the job done.
For the remaining articles (if for some reason they have not been downloaded) you will use the second library. It is way poorer implementation than the @medup one, but it will give you a markdown-ish file to work with. Hopefully, you will have a few files for using this library.
git clone https://github.com/dtesler/medium-to-markdown.git
medium-to-markdown
folder npm install
npm run convert https://medium.com/@USERNAME/article > article.md
content/blog/
folder previously set.06-08-2023-a-very-big-title-name.md
=> small-title.md
)In possession of your markdown files, you will have to fix the errors discussed above. That should take you a good chunk of your time if you have many files
however if you have an IDE and know some regex like this one (!\[(.+[\)\.]).+\.\w+\))
things can go way easier. Just use that regex to match tags
and use the power of capturing groups to replace things that you would need. And voilà, you are now a mass editor.
Like I keep saying in most of my posts, I got your back mate.
Once everything is properly formatted we can start displaying the posts on your platform.
You will now dynamically set a router file to be able to display your posts from the content without having to manually create a file every time you create content. For that, follow these steps:
blog
folder inside pages[...slug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogPosts = await getCollection('blog');
return blogPosts.map(post => ({
params: { slug: post.slug.replace('/blog','') }, props: { post }}
));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Content/>
content/blog
files to have the following slug in the frontmatter
---
slug: /blog/small-title
---
You may have noticed that some of the YouTube videos and Codepen links that were on your site are appearing as links or looking broken.
Unfortunately, markdown does not support embedded links. The good news is that mdx with a little bit of configuration can make your site beautiful
again without using complicated JSX
or any other configuration than a simple markdown.
From now on all your files are going to be renamed to .mdx, or at least the ones that need videos on the page and other advanced integrations.
mdx
integration and pluginsnpm install @astrojs/mdx
npm install @remark-embedder/core @remark-embedder/transformer-oembed
astro.config.mjs
import { defineConfig } from 'astro/config';
import react from "@astrojs/react";
import mdx from '@astrojs/mdx';
import fauxRemarkEmbedder from '@remark-embedder/core'
import fauxOembedTransformer from '@remark-embedder/transformer-oembed'
const remarkEmbedder = fauxRemarkEmbedder.default
const oembedTransformer = fauxOembedTransformer.default
// <https://astro.build/config>
export default defineConfig({
integrations: [mdx(
{
remarkPlugins: [[remarkEmbedder, {transformers: [oembedTransformer]}]]
}
), react()]
});
Tip: You need to style the iframe afterward for things like width, height, margin, etc.
Now you have migrated and set up a powerful static website. It is time for celebration. Go for a walk. Go kiss your wife and children. Play 15 minutes of chess or Dota2. Come back when you are fully recharged. We need a few more steps to be able to claim the full benefits of this solution.
See I am not Kent C. Dodds or Dan Abramov. I am not popular. Medium however is a gigantic platform. They invest millions of dollars every year. They have engineers, marketers, salespeople, and a huge influx of website access. People like you and I can not compete with them, we also can not be too much dependent on their “misalignment” (thus the migration). We have to find a middle ground to get the most out of this relationship.
With that idea in mind comes the term POSSE - Publish (on your) Own Site, Syndicate Elsewhere. It is a cool idea that I have found in several good articles:
We will use their reach and they will use our text for a win-win situation.
For posting things in an easy way I literally had to stop writing this article to help with a tool that does most of the job but it is kind of broken. Yeah yeah. I know. We have been there before. But not this time mate. This time I have taken the time and made some pull requests that I expect to be integrated into their package.
Actually, forget about the PR request. I have created a way better library for that. It is called crossposting it solves the problem of POSSE for you, and it is better than the others (seriously, I have spent one week just writing that). It allows you to publish to 3 main sites: dev.to, hashnode, and medium.
For installation execute the following:
npm install crossposting -g
Now you are going to configure the platforms by setting their individual keys:
# [platform]: dev | hashnode | medium
cpt setk [platform]
You have to find the tokens/API keys of each platform you intend to cross-posting. The repository has a nice tutorial here.
Once everything is configured is time to use the tool to crosspost and profit. Navigate to the blog folder that we have set and execute the command below:
cpt <url|path> -p dev
Where path
is the name of your blog article if you are publishing from a local file and url
if your article is already posted on some website.
In my case, I have a local file with frontmatter metadata, which would be something like this::
cpt ./08-creating-a-blog.mdx -p dev
Nice :P
If everything goes well. You should see your post on dev.to with images set and ready to go. The post is set for draft but when you start using the tool you can confidently publish directly to the platform, you can even add to your integration deployment (ci/cd) tool. Again, check the documentation it is easy to follow.
So that is it. Once more, I hope I have brought you a little bit of happiness. This is a very thorough article, I believe. If you have any doubts or suggestions, please let me know so I can help you with it. If you think that this is amazing please leave a like, a comment, or just ignore it with love.
Cheers mate.