Skip to content

feat: Added the blog site #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions app/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { blogSource } from '@/lib/source';
import { notFound } from 'next/navigation';
import Image from 'next/image';
import { authors } from '@/lib/authors';

// Page params type is defined inline in the component

export function generateStaticParams() {
return blogSource.getPages().map((page) => ({
slug: page.slugs,
}));
}

export default async function BlogPostPage(props: { params: Promise<{ slug: string[] }>}) {
const params = await props.params;
const page = blogSource.getPage(params.slug);

if (!page) {
notFound();
}

return (
<div className="max-w-4xl mx-auto px-4 py-12">
<article>
<h1 className="text-4xl font-extrabold mb-4 text-[#0e141b]">{page.data.title}</h1>
{page.data.description && (
<p className="text-xl mb-8 text-[#4e7097] font-medium leading-relaxed">
{page.data.description}
</p>
)}
{page.data.date && (
<div className="text-sm font-medium text-[#4e7097] mb-8">
Published on {new Date(page.data.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
)}
<div className="prose dark:prose-invert max-w-none mb-8 prose-headings:text-[#0e141b] prose-headings:font-bold prose-p:text-[#0e141b] prose-p:text-lg prose-p:leading-relaxed prose-a:text-blue-600 prose-a:font-medium prose-li:text-[#0e141b] prose-li:text-lg prose-code:text-blue-700 prose-code:bg-slate-100 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none prose-pre:bg-slate-800 prose-pre:text-slate-100 prose-pre:p-4 prose-pre:rounded-lg">
{(() => {
const MDXContent = page.data.body as React.ComponentType;
return <MDXContent />;
})()}
</div>
</article>
{'author' in page.data && page.data.author && (
<div className="mt-10 pt-6 border-t border-[#e7edf3]">
<h3 className="text-sm font-medium text-[#4e7097] mb-4">WRITTEN BY</h3>
<div className="flex items-center gap-4">
{(() => {
const info = authors[page.data.author ?? ''];
if (!info) return null;
return (
<>
{info.avatar ? (
<Image
src={info.avatar || "/avatars/generic.svg"}
alt={`${info.name}'s avatar`}
width={64}
height={64}
className="rounded-full object-cover shrink-0 bg-[#e7edf3] border-2 border-white shadow-sm"
/>
) : (
<div className="w-16 h-16 rounded-full bg-[#e7edf3] flex items-center justify-center shrink-0 border-2 border-white shadow-sm">
<svg viewBox="0 0 24 24" fill="currentColor" className="w-10 h-10 text-[#4e7097]">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
</svg>
</div>
)}
<div>
<p className="font-bold text-lg text-[#0e141b] m-0">{info.name}</p>
{info.title && <p className="text-sm text-[#4e7097] m-0">{info.title}</p>}
{info.bio && <p className="text-sm text-[#0e141b] mt-2 leading-relaxed">{info.bio}</p>}
</div>
</>
);
})()}
</div>
</div>
)}
</div>
);
}
79 changes: 79 additions & 0 deletions app/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Link from 'next/link';

export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="relative flex size-full min-h-screen flex-col bg-slate-50 group/design-root overflow-x-hidden" style={{fontFamily: 'system-ui, sans-serif'}}>
<div className="layout-container flex h-full grow flex-col">
<header className="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-[#e7edf3] px-10 py-3">
<div className="flex items-center gap-4 text-[#0e141b]">
<div className="size-4">
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 4H17.3334V17.3334H30.6666V30.6666H44V44H4V4Z" fill="currentColor"></path></svg>
</div>
<h2 className="text-[#0e141b] text-lg font-bold leading-tight tracking-[-0.015em]">
<Link href="/blog" className="hover:text-blue-600">Parseable Blog</Link>
</h2>
</div>
<div className="flex flex-1 justify-end gap-8">
<div className="flex items-center gap-9">
<a className="text-[#0e141b] text-sm font-medium leading-normal" href="https://www.parseable.com">Home</a>
<a className="text-[#0e141b] text-sm font-medium leading-normal" href="https://www.parseable.com/docs">Documentation</a>
<a className="text-[#0e141b] text-sm font-medium leading-normal" href="https://www.parseable.com/docs/api">API</a>
<a className="text-[#0e141b] text-sm font-medium leading-normal" href="https://www.parseable.com/docs/release-notes">Release Notes</a>
</div>
<div className="flex gap-2">
<button
className="flex max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 bg-[#e7edf3] text-[#0e141b] gap-2 text-sm font-bold leading-normal tracking-[0.015em] min-w-0 px-2.5"
>
<div className="text-[#0e141b]">
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill="currentColor" viewBox="0 0 256 256">
<path
d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"
></path>
</svg>
</div>
</button>
</div>
</div>
</header>
<main className="flex-grow px-4 md:px-10 lg:px-40 py-5">
{children}
</main>
<footer className="flex justify-center">
<div className="flex max-w-[960px] flex-1 flex-col">
<footer className="flex flex-col gap-6 px-5 py-10 text-center">
<div className="flex flex-wrap items-center justify-center gap-6 md:flex-row md:justify-around">
<a className="text-[#4e7097] text-base font-normal leading-normal min-w-40" href="https://www.parseable.com/docs">Documentation</a>
<a className="text-[#4e7097] text-base font-normal leading-normal min-w-40" href="https://github.com/parseablehq/parseable">GitHub</a>
<a className="text-[#4e7097] text-base font-normal leading-normal min-w-40" href="https://www.parseable.com/docs/api">API</a>
<a className="text-[#4e7097] text-base font-normal leading-normal min-w-40" href="https://www.parseable.com/docs/release-notes">Release Notes</a>
</div>
<div className="flex flex-wrap justify-center gap-4">
<a href="https://twitter.com/parseableio" target="_blank" rel="noopener noreferrer">
<div className="text-[#4e7097]">
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 256 256">
<path
d="M247.39,68.94A8,8,0,0,0,240,64H209.57A48.66,48.66,0,0,0,168.1,40a46.91,46.91,0,0,0-33.75,13.7A47.9,47.9,0,0,0,120,88v6.09C79.74,83.47,46.81,50.72,46.46,50.37a8,8,0,0,0-13.65,4.92c-4.31,47.79,9.57,79.77,22,98.18a110.93,110.93,0,0,0,21.88,24.2c-15.23,17.53-39.21,26.74-39.47,26.84a8,8,0,0,0-3.85,11.93c.75,1.12,3.75,5.05,11.08,8.72C53.51,229.7,65.48,232,80,232c70.67,0,129.72-54.42,135.75-124.44l29.91-29.9A8,8,0,0,0,247.39,68.94Zm-45,29.41a8,8,0,0,0-2.32,5.14C196,166.58,143.28,216,80,216c-10.56,0-18-1.4-23.22-3.08,11.51-6.25,27.56-17,37.88-32.48A8,8,0,0,0,92,169.08c-.47-.27-43.91-26.34-44-96,16,13,45.25,33.17,78.67,38.79A8,8,0,0,0,136,104V88a32,32,0,0,1,9.6-22.92A30.94,30.94,0,0,1,167.9,56c12.66.16,24.49,7.88,29.44,19.21A8,8,0,0,0,204.67,80h16Z"
></path>
</svg>
</div>
</a>
<a href="https://github.com/parseablehq/parseable" target="_blank" rel="noopener noreferrer">
<div className="text-[#4e7097]">
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
</div>
</a>
</div>
<p className="text-[#4e7097] text-base font-normal leading-normal">© {new Date().getFullYear()} Parseable. All rights reserved.</p>
</footer>
</div>
</footer>
</div>
</div>
);
}
131 changes: 131 additions & 0 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { blogSource } from '@/lib/source';
import { notFound } from 'next/navigation';
import Link from 'next/link';

export default function BlogPage() {
const pages = blogSource.getPages();

if (!pages || pages.length === 0) {
notFound();
}

// Sort pages by date (newest first)
const sortedPages = [...pages].sort((a, b) => {
const dateA = a.data.date ? new Date(a.data.date).getTime() : 0;
const dateB = b.data.date ? new Date(b.data.date).getTime() : 0;
return dateB - dateA;
});

// Featured articles (first 3)
const featuredArticles = sortedPages.slice(0, 3);

// Recent posts (next 4 or all available)
const recentPosts = sortedPages.slice(0, 4);

// We'll use the same posts for featured and recent sections

return (
<div className="layout-content-container flex flex-col max-w-[960px] flex-1 mx-auto">
{/* Hero Section */}
<div className="@container">
<div className="px-4 py-3">
<div
className="bg-cover bg-center flex flex-col justify-end overflow-hidden bg-slate-50 rounded-lg min-h-80"
style={{
backgroundImage: 'linear-gradient(0deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 25%), url("/blog-hero.jpg")'
}}
>
<div className="flex p-4">
<p className="text-white tracking-light text-[28px] font-bold leading-tight">
Exploring the Latest in Parseable Logging
</p>
</div>
</div>
</div>
</div>

{/* Featured Articles */}
<h2 className="text-[#0e141b] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">
Featured Articles
</h2>
<div className="flex overflow-y-auto [-ms-scrollbar-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<div className="flex items-stretch p-4 gap-3">
{featuredArticles.map((page) => (
<div key={page.slugs.join('/')} className="flex h-full flex-1 flex-col gap-4 rounded-lg min-w-60">
<div
className="w-full bg-center bg-no-repeat aspect-video bg-cover rounded-lg flex flex-col"
style={{ backgroundImage: `url("/blog-placeholder.jpg")` }}
></div>
<div>
<Link href={`/blog/${page.slugs.join('/')}`} className="text-[#0e141b] text-base font-medium leading-normal hover:text-blue-600">
{page.data.title}
</Link>
<p className="text-[#4e7097] text-sm font-normal leading-normal">{page.data.description}</p>
</div>
</div>
))}
</div>
</div>

{/* Recent Posts */}
<h2 className="text-[#0e141b] text-[22px] font-bold leading-tight tracking-[-0.015em] px-4 pb-3 pt-5">
Recent Posts
</h2>
<div className="grid grid-cols-[repeat(auto-fit,minmax(158px,1fr))] gap-3 p-4">
{recentPosts.map((page) => (
<div key={page.slugs.join('/')} className="flex flex-col gap-3 pb-3">
<div
className="w-full bg-center bg-no-repeat aspect-video bg-cover rounded-lg"
style={{ backgroundImage: `url("/blog-placeholder.jpg")` }}
></div>
<div>
<Link href={`/blog/${page.slugs.join('/')}`} className="text-[#0e141b] text-base font-medium leading-normal hover:text-blue-600">
{page.data.title}
</Link>
<p className="text-[#4e7097] text-sm font-normal leading-normal">{page.data.description}</p>
{page.data.date && (
<p className="text-xs text-gray-500 mt-1">
{new Date(page.data.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</p>
)}
</div>
</div>
))}
</div>

{/* Newsletter Section */}
<div className="@container">
<div className="flex flex-col justify-end gap-6 px-4 py-10 @[480px]:gap-8 @[480px]:px-10 @[480px]:py-20">
<div className="flex flex-col gap-2 text-center">
<h1 className="text-[#0e141b] tracking-light text-[32px] font-bold leading-tight @[480px]:text-4xl @[480px]:font-black @[480px]:leading-tight @[480px]:tracking-[-0.033em] max-w-[720px]">
Stay Updated
</h1>
<p className="text-[#0e141b] text-base font-normal leading-normal max-w-[720px]">
Get the latest Parseable updates delivered straight to your inbox.
</p>
</div>
<div className="flex flex-1 justify-center">
<label className="flex flex-col min-w-40 h-14 max-w-[480px] flex-1 @[480px]:h-16">
<div className="flex w-full flex-1 items-stretch rounded-lg h-full">
<input
placeholder="Your email address"
className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-lg text-[#0e141b] focus:outline-0 focus:ring-0 border-none bg-[#e7edf3] focus:border-none h-full placeholder:text-[#4e7097] px-4 rounded-r-none border-r-0 pr-2 text-sm font-normal leading-normal @[480px]:text-base @[480px]:font-normal @[480px]:leading-normal"
defaultValue=""
/>
<div className="flex items-center justify-center rounded-r-lg border-l-0 border-none bg-[#e7edf3] pr-2">
<button className="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 @[480px]:h-12 @[480px]:px-5 bg-[#176fd3] text-slate-50 text-sm font-bold leading-normal tracking-[0.015em] @[480px]:text-base @[480px]:font-bold @[480px]:leading-normal @[480px]:tracking-[0.015em]">
<span className="truncate">Subscribe</span>
</button>
</div>
</div>
</label>
</div>
</div>
</div>
</div>
);
}
8 changes: 7 additions & 1 deletion app/layout.config.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
import { IconMonkeybar, IconApi, IconMapShare } from "@tabler/icons-react";
import { IconMonkeybar, IconApi, IconMapShare, IconArticle } from "@tabler/icons-react";

/**
* Shared layout configurations
Expand All @@ -19,6 +19,12 @@ export const baseOptions: BaseLayoutProps = {
external: true,
icon: <IconMonkeybar />, // Icon for the link
},
{
text: "Blog",
url: "/blog",
external: false, // Internal link
icon: <IconArticle />, // Icon for the link
},
{
text: "Release Notes",
url: "/docs/release-notes",
Expand Down
4 changes: 4 additions & 0 deletions content/blog/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Blog",
"position": 1
}
47 changes: 47 additions & 0 deletions content/blog/introducing-parseable.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: Introducing Parseable
description: A modern, cloud-native logging solution built for scale
date: "2023-06-15"
author: nitish
---

# Introducing Parseable

Parseable is a modern, cloud-native logging solution designed to handle logs at scale. Built with performance and simplicity in mind, Parseable offers a powerful alternative to traditional logging systems.

## Why Parseable?

Traditional logging solutions often struggle with the volume and velocity of logs generated by modern applications. Parseable addresses these challenges by:

- **High Performance**: Built on Rust and Apache Arrow for blazing-fast log ingestion and querying
- **Cloud Native**: Designed to run seamlessly in Kubernetes and other cloud environments
- **Simple Setup**: Get started in minutes with minimal configuration
- **SQL Querying**: Query your logs using familiar SQL syntax
- **Schema Enforcement**: Ensure log quality with schema validation

## Getting Started

Getting started with Parseable is easy:

```bash
docker run -p 8000:8000 parseablehq/parseable:latest
```

Once running, you can send logs to Parseable using simple HTTP requests:

```bash
curl -X POST http://localhost:8000/api/v1/logstream/my-app \
-H "Content-Type: application/json" \
-d '[{"message": "Hello, Parseable!", "level": "info"}]'
```

## What's Next?

In the coming weeks, we'll be sharing more about Parseable's features, use cases, and best practices. Stay tuned for:

- Deep dives into Parseable's architecture
- Performance benchmarks
- Integration guides for popular frameworks
- Advanced querying techniques

We're excited to have you join us on this journey to better logging!
Loading