DEV Community

Konrad Kądzielawa
Konrad Kądzielawa

Posted on

Building a Zero-Maintenance SysOps Knowledge Base with React and the Dev.to API

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:

  1. Zero maintenance: I don't want to deal with database backups or traditional CMS panels.
  2. 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.
  3. 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
}

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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)