diff --git a/app/blog/[...slug]/page.tsx b/app/blog/[...slug]/page.tsx
new file mode 100644
index 0000000..9376d30
--- /dev/null
+++ b/app/blog/[...slug]/page.tsx
@@ -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 (
+
+
+ {page.data.title}
+ {page.data.description && (
+
+ {page.data.description}
+
+ )}
+ {page.data.date && (
+
+ Published on {new Date(page.data.date).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })}
+
+ )}
+
+ {(() => {
+ const MDXContent = page.data.body as React.ComponentType;
+ return ;
+ })()}
+
+
+ {'author' in page.data && page.data.author && (
+
+
WRITTEN BY
+
+ {(() => {
+ const info = authors[page.data.author ?? ''];
+ if (!info) return null;
+ return (
+ <>
+ {info.avatar ? (
+
+ ) : (
+
+ )}
+
+
{info.name}
+ {info.title &&
{info.title}
}
+ {info.bio &&
{info.bio}
}
+
+ >
+ );
+ })()}
+
+
+ )}
+
+ );
+}
diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx
new file mode 100644
index 0000000..174f713
--- /dev/null
+++ b/app/blog/layout.tsx
@@ -0,0 +1,79 @@
+import Link from 'next/link';
+
+export default function BlogLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/app/blog/page.tsx b/app/blog/page.tsx
new file mode 100644
index 0000000..fc03bf4
--- /dev/null
+++ b/app/blog/page.tsx
@@ -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 (
+
+ {/* Hero Section */}
+
+
+
+
+
+ Exploring the Latest in Parseable Logging
+
+
+
+
+
+
+ {/* Featured Articles */}
+
+ Featured Articles
+
+
+
+ {featuredArticles.map((page) => (
+
+
+
+
+ {page.data.title}
+
+
{page.data.description}
+
+
+ ))}
+
+
+
+ {/* Recent Posts */}
+
+ Recent Posts
+
+
+ {recentPosts.map((page) => (
+
+
+
+
+ {page.data.title}
+
+
{page.data.description}
+ {page.data.date && (
+
+ {new Date(page.data.date).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })}
+
+ )}
+
+
+ ))}
+
+
+ {/* Newsletter Section */}
+
+
+
+
+ Stay Updated
+
+
+ Get the latest Parseable updates delivered straight to your inbox.
+
+
+
+
+
+
+ );
+}
diff --git a/app/layout.config.tsx b/app/layout.config.tsx
index d141b2d..c4686e1 100644
--- a/app/layout.config.tsx
+++ b/app/layout.config.tsx
@@ -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
@@ -19,6 +19,12 @@ export const baseOptions: BaseLayoutProps = {
external: true,
icon: , // Icon for the link
},
+ {
+ text: "Blog",
+ url: "/blog",
+ external: false, // Internal link
+ icon: , // Icon for the link
+ },
{
text: "Release Notes",
url: "/docs/release-notes",
diff --git a/content/blog/_category_.json b/content/blog/_category_.json
new file mode 100644
index 0000000..21c6b00
--- /dev/null
+++ b/content/blog/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "Blog",
+ "position": 1
+}
diff --git a/content/blog/introducing-parseable.mdx b/content/blog/introducing-parseable.mdx
new file mode 100644
index 0000000..ce8564c
--- /dev/null
+++ b/content/blog/introducing-parseable.mdx
@@ -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!
diff --git a/content/blog/optimizing-log-queries.mdx b/content/blog/optimizing-log-queries.mdx
new file mode 100644
index 0000000..d5c9157
--- /dev/null
+++ b/content/blog/optimizing-log-queries.mdx
@@ -0,0 +1,81 @@
+---
+title: Optimizing Log Queries in Parseable
+description: Tips and techniques for writing efficient log queries
+date: "2023-07-20"
+author: deb
+---
+
+# Optimizing Log Queries in Parseable
+
+Effective log analysis depends on your ability to write efficient queries. In this post, we'll explore techniques to optimize your Parseable log queries for better performance and insights.
+
+## Understanding Parseable's Query Engine
+
+Parseable uses a powerful SQL-based query engine built on Apache Arrow. This architecture provides several advantages:
+
+- Column-oriented storage for faster analytical queries
+- Vectorized query execution
+- Predicate pushdown for efficient filtering
+- Parallel query processing
+
+## Query Optimization Techniques
+
+### 1. Use Time-Based Filtering
+
+Always include time constraints in your queries to limit the data scanned:
+
+```sql
+SELECT * FROM logs
+WHERE timestamp >= '2023-07-01T00:00:00Z'
+AND timestamp < '2023-07-02T00:00:00Z'
+```
+
+### 2. Select Only Needed Columns
+
+Instead of using `SELECT *`, specify only the columns you need:
+
+```sql
+SELECT timestamp, level, message, service_name
+FROM logs
+WHERE level = 'error'
+```
+
+### 3. Leverage Indexed Fields
+
+Parseable automatically indexes certain fields. Use these for faster filtering:
+
+```sql
+SELECT * FROM logs
+WHERE service_name = 'api-gateway'
+AND level = 'error'
+```
+
+### 4. Use Aggregations Wisely
+
+Aggregations can help summarize large volumes of data:
+
+```sql
+SELECT
+ service_name,
+ COUNT(*) as error_count
+FROM logs
+WHERE level = 'error'
+GROUP BY service_name
+ORDER BY error_count DESC
+```
+
+## Monitoring Query Performance
+
+Parseable provides query execution statistics to help you understand and optimize performance:
+
+- Query execution time
+- Bytes scanned
+- Rows processed
+
+Use these metrics to identify and refine slow-performing queries.
+
+## Conclusion
+
+By following these optimization techniques, you can significantly improve the performance of your log queries in Parseable, enabling faster troubleshooting and more efficient log analysis.
+
+Stay tuned for more advanced query techniques in our upcoming posts!
diff --git a/lib/authors.ts b/lib/authors.ts
new file mode 100644
index 0000000..c82c2a5
--- /dev/null
+++ b/lib/authors.ts
@@ -0,0 +1,19 @@
+export interface AuthorProfile {
+ name: string;
+ title?: string;
+ avatar?: string; // path in public
+ bio?: string;
+ twitter?: string;
+ github?: string;
+}
+
+export const authors: Record = {
+ nitish: {
+ name: 'Nitish Tiwari',
+ title: 'Founder, Parseable',
+ avatar: '/avatars/generic.svg',
+ twitter: 'https://twitter.com/nitish_tiwari',
+ github: 'https://github.com/nitish-tiwari',
+ bio: 'Logs, Rust and open-source enthusiast.'
+ },
+};
diff --git a/lib/source.ts b/lib/source.ts
index dba7790..0f34490 100644
--- a/lib/source.ts
+++ b/lib/source.ts
@@ -1,4 +1,4 @@
-import { docs, releaseNotes } from '@/.source';
+import { docs, releaseNotes, blog } from '@/.source';
import { loader } from 'fumadocs-core/source';
import { createOpenAPI, attachFile } from 'fumadocs-openapi/server';
@@ -22,6 +22,15 @@ export const releaseNotesSource = loader({
},
});
+// Create a separate loader for blog posts
+export const blogSource = loader({
+ baseUrl: '/blog',
+ source: blog.toFumadocsSource(),
+ pageTree: {
+ attachFile,
+ },
+});
+
// Create OpenAPI instance for the Parseable API
export const openapi = createOpenAPI({
// Point to the correct schema file
diff --git a/package.json b/package.json
index 1a16292..01c4f79 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,8 @@
"next": "15.3.1",
"next-mdx-remote": "^5.0.0",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "zod": "^3.25.64"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c51d90..dac90eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,6 +65,9 @@ importers:
react-dom:
specifier: ^19.1.0
version: 19.1.0(react@19.1.0)
+ zod:
+ specifier: ^3.25.64
+ version: 3.25.64
devDependencies:
'@tailwindcss/postcss':
specifier: ^4.1.5
@@ -3708,8 +3711,8 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- zod@3.24.4:
- resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==}
+ zod@3.25.64:
+ resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==}
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@@ -6037,7 +6040,7 @@ snapshots:
next: 15.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
picocolors: 1.1.1
unist-util-visit: 5.0.0
- zod: 3.24.4
+ zod: 3.25.64
transitivePeerDependencies:
- acorn
- supports-color
@@ -8067,6 +8070,6 @@ snapshots:
yocto-queue@0.1.0: {}
- zod@3.24.4: {}
+ zod@3.25.64: {}
zwitch@2.0.4: {}
diff --git a/public/avatars/generic.svg b/public/avatars/generic.svg
new file mode 100644
index 0000000..3d85600
--- /dev/null
+++ b/public/avatars/generic.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/blog-hero.jpg b/public/blog-hero.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/public/blog-placeholder.jpg b/public/blog-placeholder.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/source.config.ts b/source.config.ts
index 7798e36..aa4d28f 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -5,6 +5,7 @@ import {
metaSchema,
} from 'fumadocs-mdx/config';
import { remarkAdmonition } from 'fumadocs-core/mdx-plugins';
+import { z } from 'zod';
// Define the documentation collection
export const docs = defineDocs({
@@ -30,6 +31,24 @@ export const releaseNotes = defineDocs({
},
});
+// Define a custom schema for blog posts that includes the date field
+const blogFrontmatterSchema = frontmatterSchema.extend({
+ date: z.string().optional(),
+ author: z.string().optional(),
+});
+
+// Define the blog collection
+export const blog = defineDocs({
+ // The root directory for blog posts
+ dir: 'content/blog',
+ docs: {
+ schema: blogFrontmatterSchema,
+ },
+ meta: {
+ schema: metaSchema,
+ },
+});
+
// Configure MDX options
export default defineConfig({
mdxOptions: {