After spending nearly a decade navigating different areas of IT—from building complex e-commerce frontends to managing the underlying Linux servers and infrastructure—I decided it was time to completely rebuild my personal portfolio.
My old website was full of typical "frontend fireworks": heavy animations, flashy graphics, and complex layouts. But as my career naturally shifted towards IT Systems Administration and infrastructure management, that style no longer resonated with my daily work. I wanted a space that felt like home to a SysOps engineer: raw, minimalist, and terminal-inspired.
However, as an admin, I had a strict set of requirements for this new platform:
- Zero maintenance: I don't want to deal with database backups or traditional CMS panels.
- No local Markdown files: Pushing a commit to GitHub just to fix a typo in a blog post is an overkill that breaks the writing flow.
- Single Source of Truth: I want to write technical notes once and have them automatically distribute to my community profile and my personal website.
The solution? Using the Dev.to API as a free, headless CMS for a React Single Page Application.
Here is a technical breakdown of how I designed this architecture
1. Fetching the Data (The API Layer)
Dev.to provides a fantastic, mostly undocumented perk for developers: a public, read-only API that returns your articles in JSON format.
Instead of hardcoding data, my React app fetches the article list directly from my Dev.to profile upon initialization. To prevent unnecessary network requests and ensure a 0ms load time when navigating back and forth, I implemented a simple in-memory cache outside the component lifecycle.
// Global cache for instant UI rendering
let cachedArticles: DevToArticle[] | null = null;
export function MainAdmin() {
const [articles, setArticles] = useState<DevToArticle[]>(cachedArticles || []);
const [loading, setLoading] = useState(cachedArticles === null);
useEffect(() => {
if (!cachedArticles) {
fetch('[https://dev.to/api/articles?username=kkadzielawa](https://dev.to/api/articles?username=kkadzielawa)')
.then(response => response.json())
.then(data => {
setArticles(data);
cachedArticles = data; // Cache the response
setLoading(false);
})
.catch(console.error);
}
}, []);
// ... render logic
}
2. Dynamic Tag Filtering
Since the Dev.to API returns an array of tags (tag_list) for each article, I didn't need to build a custom taxonomy system. I simply extract all unique tags dynamically from the fetched payload and generate a terminal-like filter menu [ #linux ], [ #docker ], etc.
3. The "Optimistic UI" Trick
One downside of the /articles list endpoint is that it doesn't return the full Markdown body of the posts. To get the content, you have to make a second request to /articles/{id} when the user clicks on a specific note.
To prevent the annoying "layout shift" and loading spinners, I used React Router's state to pass the title of the article from the main list directly to the reader component.
<Link
to={`/note/${article.id}`}
state={{ title: article.title }} // Pass the title forward!
>
{article.title}
</Link>
Thanks to this, the note viewer immediately renders the title and structure, while the actual Markdown body stream loads discreetly in the background.
4. Rendering Markdown with Syntax Highlighting
Once the API returns the body_markdown, it needs to be safely parsed into HTML. Since this is a SysOps blog, code snippets must look flawless. I combined react-markdown with react-syntax-highlighter using the VS Code Dark Plus theme for a native IDE feel.
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
// Inside the render function:
<ReactMarkdown
components={{
code({ node, inline, className, children, ...props }: any) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={vscDarkPlus as any}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className="inline-code" {...props}>
{children}
</code>
);
}
}}
>
{content}
</ReactMarkdown>
Conclusion
By treating Dev.to as a completely touchless, headless CMS, I achieved a perfect Separation of Concerns. Dev.to handles the database, image hosting, SEO, and the writing experience. My React application acts purely as a customized, terminal-themed presentation layer.
My personal portfolio updates automatically. That's the exact kind of automation every IT Admin loves.
Now, whenever I want to drop some technical meat (like a new Linux configuration or securing a server environment), I just hit "Publish" on Dev.to, and it's instantly live.
Check out the live result at kadzielawa.dev.
Top comments (0)