diff --git a/components/Blog/BlogHeader.tsx b/components/Blog/BlogHeader.tsx
new file mode 100644
index 000000000..3bc49a68b
--- /dev/null
+++ b/components/Blog/BlogHeader.tsx
@@ -0,0 +1,38 @@
+import { Rss } from "react-feather";
+import Dropdown from "../Dropdown";
+import { BLOG_CATEGORIES } from "./index";
+
+export default function BlogHeader({ description }: { description: string }) {
+ const categoryLinks = Object.keys(BLOG_CATEGORIES).map((k) => ({
+ href: `/blog/category/${k}`,
+ text: BLOG_CATEGORIES[k],
+ }));
+ return (
+
+ );
+}
diff --git a/components/Blog/BlogPostList.tsx b/components/Blog/BlogPostList.tsx
new file mode 100644
index 000000000..23e830b69
--- /dev/null
+++ b/components/Blog/BlogPostList.tsx
@@ -0,0 +1,42 @@
+import Image from "next/image";
+import { RiCalendarLine } from "@remixicon/react";
+import Tags from "src/shared/Blog/Tags";
+import { type BlogPost } from "./index";
+
+export default function BlogPostList({ posts }: { posts: BlogPost[] }) {
+ return (
+
+ );
+}
diff --git a/components/Blog/index.ts b/components/Blog/index.ts
new file mode 100644
index 000000000..4b2be6fbe
--- /dev/null
+++ b/components/Blog/index.ts
@@ -0,0 +1,19 @@
+import { type MDXFileMetadata } from "src/utils/markdown";
+
+export type BlogPost = {
+ heading: string;
+ subtitle: string;
+ author?: string;
+ image: string;
+ date: string;
+ humanDate: string;
+ tags?: string[];
+ hide?: boolean;
+} & MDXFileMetadata;
+
+// Slug -> Formatted title
+export const BLOG_CATEGORIES = {
+ "product-updates": "Product updates",
+ "company-news": "Company news",
+ engineering: "Engineering",
+};
diff --git a/components/Dropdown.tsx b/components/Dropdown.tsx
new file mode 100644
index 000000000..2d2bc57ab
--- /dev/null
+++ b/components/Dropdown.tsx
@@ -0,0 +1,62 @@
+"use client";
+import { useState } from "react";
+import { RiArrowDownSLine } from "@remixicon/react";
+
+export default function Dropdown({
+ title,
+ items,
+ className = "",
+}: {
+ title: React.ReactNode;
+ items: {
+ href: string;
+ text: string;
+ target?: string;
+ }[];
+ className?: string;
+}) {
+ const [open, setOpen] = useState(false);
+ /**
+ * NOTE - This uses md: prefixes to make the button work on hover on desktop and click on mobile
+ */
+ return (
+
+
setOpen(!open)}
+ >
+ {title}
+
+
+
md)
+ className={`absolute right-0 w-full min-w-min md:hidden md:group-hover:block ${
+ open ? "block" : "hidden"
+ }`}
+ >
+
+ {/* transparent element to persist hover */}
+
+
+
+
+ );
+}
diff --git a/components/Nav.tsx b/components/Nav.tsx
index a56885ffc..8aa773770 100644
--- a/components/Nav.tsx
+++ b/components/Nav.tsx
@@ -14,6 +14,7 @@ import classNames from "src/utils/classNames";
import Container from "src/shared/layout/Container";
import Menu, { type MenuProps } from "./Nav/Menu";
import { productLinks, resourcesLinks } from "./Nav/links";
+import Dropdown from "./Dropdown";
// Manual count of stars on GitHub for now
// Run pnpm run github:stars to get the latest count
@@ -200,50 +201,22 @@ const repos = [
];
function OpenSourceButton({ className = "" }: { className?: string }) {
- const [open, setOpen] = useState(false);
- /**
- * NOTE - This uses md: prefixes to make the button work on hover on desktop and click on mobile
- */
+ const items = repos.map((repo) => ({
+ href: `https://github.com/${repo}`,
+ text: repo,
+ target: "_blank",
+ }));
return (
-
-
setOpen(!open)}
- >
- Open Source
-
- {(GITHUB_STARS / 1000).toFixed(1)}K
-
-
-
md)
- className={`absolute right-0 w-full min-w-min md:hidden md:group-hover:block ${
- open ? "block" : "hidden"
- }`}
- >
-
- {/* transparent element to persist hover */}
-
-
- {repos.map((repo, idx) => (
- -
-
- {repo}
-
-
- ))}
-
-
-
+
+ Open Source
+
+ {(GITHUB_STARS / 1000).toFixed(1)}K
+ >
+ }
+ items={items}
+ className={className}
+ />
);
}
diff --git a/pages/blog.tsx b/pages/blog.tsx
index 79ad25d62..5683496fd 100644
--- a/pages/blog.tsx
+++ b/pages/blog.tsx
@@ -14,9 +14,12 @@ import {
loadMarkdownFilesMetadata,
type MDXFileMetadata,
} from "../utils/markdown";
+import BlogHeader from "src/components/Blog/BlogHeader";
+import BlogPostList from "src/components/Blog/BlogPostList";
+import { type BlogPost } from "src/components/Blog";
// import { LaunchWeekBanner } from "./index";
-export default function BlogLayout(props) {
+export default function BlogIndex(props) {
const router = useRouter();
const { showHidden } = router.query;
@@ -37,7 +40,7 @@ export default function BlogLayout(props) {
return (
<>
- Inngest → Product & Engineering blog
+ Inngest - Product & Engineering blog
*/}
-
-
- Blog
-
-
{description}
-
-
-
-
+
+
@@ -150,21 +111,13 @@ export default function BlogLayout(props) {
);
}
-export type BlogPost = {
- heading: string;
- subtitle: string;
- author?: string;
- image: string;
- date: string;
- humanDate: string;
- tags?: string[];
- hide?: boolean;
-} & MDXFileMetadata;
-
// This function also gets called at build time to generate specific content.
export async function getStaticProps() {
const posts = await loadMarkdownFilesMetadata("blog/_posts");
- const content = posts.map((p) => JSON.stringify(p));
+ // If a post is set to featured=false, do not show on main blog feed
+ // This can be used for less important posts that may be directly linked to from other places
+ const filteredPosts = posts.filter((p) => p?.featured !== false);
+ const content = filteredPosts.map((p) => JSON.stringify(p));
return {
props: {
diff --git a/pages/blog/_posts/2023-11-29-metrics-with-timescale.md b/pages/blog/_posts/2023-11-29-metrics-with-timescale.md
index 9cae2f5ca..c0825b9bd 100644
--- a/pages/blog/_posts/2023-11-29-metrics-with-timescale.md
+++ b/pages/blog/_posts/2023-11-29-metrics-with-timescale.md
@@ -6,19 +6,20 @@ image: /assets/blog/metrics-with-timescaledb/feature.png
date: 2023-11-29
author: Darwin Wu
disableCTA: true
+category: engineering
---
Understanding how your system works is key to keeping one’s sanity.
As we have more users deploying their workloads on Inngest, a common set of questions start coming up more and more over time.
-* Have my functions started?
-* Are they delayed?
-* If not,
- + Why are they not starting?
- + Is something stuck?
- + Am I getting throttled?
- + Was there a misconfiguration?
+- Have my functions started?
+- Are they delayed?
+- If not,
+ - Why are they not starting?
+ - Is something stuck?
+ - Am I getting throttled?
+ - Was there a misconfiguration?
Not being able to tell what’s going on was a common complaint/feedback, and on the flip side, our team was spending more time diving into our data stores to see what’s going on in order to provide the answers our users are asking.
@@ -30,17 +31,17 @@ We completely understand the need to answer these basic questions in order for u
A lot of the questions falls into 2 buckets,
-* **Status** - What’s going on?
-* **Resolution** - What do I need to do to get out of this?
+- **Status** - What’s going on?
+- **Resolution** - What do I need to do to get out of this?
-It’s almost impossible to provide a solution if you don’t know what the current status is, and to properly assess an issue, we need to understand the *what* and the *why*.
+It’s almost impossible to provide a solution if you don’t know what the current status is, and to properly assess an issue, we need to understand the _what_ and the _why_.
-The metrics project was a focus to expose the ***What*** of the user’s workload as a starting point. The ***Why*** is usually trickier to address as there are always some kind of context behind it.
+The metrics project was a focus to expose the **_What_** of the user’s workload as a starting point. The **_Why_** is usually trickier to address as there are always some kind of context behind it.
For example, a function with a concurrency setting could be throttled because
-* There was a burst of events, and it exceed the limit
-* External API that it was calling was having an outage, and all function runs has been failing, causing a lot of retries, and resulted in using up the limit
+- There was a burst of events, and it exceed the limit
+- External API that it was calling was having an outage, and all function runs has been failing, causing a lot of retries, and resulted in using up the limit
We can go on and on, and it’s impossible for us as service providers to tell users why something went wrong. It’s up to the users to know, but we can still provide the indicators to help them form a theory and get to the root cause.
@@ -50,12 +51,12 @@ We can go on and on, and it’s impossible for us as service providers to tell u
Choosing the right tool for the job is always important. We’ve looked into a couple of existing tools,
-* [InfluxDB](https://www.influxdata.com/)
-* [Prometheus](https://prometheus.io/)
-* [TimescaleDB](https://www.timescale.com/)
-* [Clickhouse](https://clickhouse.com/)
-* [M3](https://m3db.io/)
-* [Grafana Mimir](https://grafana.com/oss/mimir/)
+- [InfluxDB](https://www.influxdata.com/)
+- [Prometheus](https://prometheus.io/)
+- [TimescaleDB](https://www.timescale.com/)
+- [Clickhouse](https://clickhouse.com/)
+- [M3](https://m3db.io/)
+- [Grafana Mimir](https://grafana.com/oss/mimir/)
We pretty much crossed off Prometheus related tools right off the bat. It’s generally a pain to maintain, and also scaling profile is questionable. You need a Thanos or some other tool like M3 or Mimir to use as a storage backend, or it’ll overflow very quickly.
@@ -67,9 +68,9 @@ It was now down to Timescale and Clickhouse. Due to prior jobs, I’d had a pret
While there was a managed solution with Clickhouse now, we ultimately decided to go with TimescaleDB for the following reasons:
-* It is Postgres, and we do not need additional SQL drivers
-* We already run Postgres so we have a good idea of the scaling profile and what pitfalls there are
-* We already have some existing feature using TimescaleDB, and it was easier to expand the usage, instead of introducing a new database
+- It is Postgres, and we do not need additional SQL drivers
+- We already run Postgres so we have a good idea of the scaling profile and what pitfalls there are
+- We already have some existing feature using TimescaleDB, and it was easier to expand the usage, instead of introducing a new database
---
@@ -77,9 +78,9 @@ While there was a managed solution with Clickhouse now, we ultimately decided to
It was pretty clear what we wanted to provide as the MVP release at this point.
-* Function throughput
-* SDK throughput
-* Throttle (Concurrent limit) indicators
+- Function throughput
+- SDK throughput
+- Throttle (Concurrent limit) indicators
And the chart will give the user enough information for them to dive in more on their own to the problem.
@@ -100,10 +101,10 @@ However, at this iteration we went with option #2 instead.
Mainly because,
-* Timescale is not a columnar database, querying massive amounts of data will incur penalties[^2]
-* A future possible feature to expose metrics endpoints for each account, and this format was easier
-* The Go tally library has a nice way of [extending it to work with custom storage](https://pkg.go.dev/github.com/uber-go/tally#StatsReporter), and saves us time to release
-* #3 requires more involved technical work, which unfortunately we do not have the capacity and time right now
+- Timescale is not a columnar database, querying massive amounts of data will incur penalties[^2]
+- A future possible feature to expose metrics endpoints for each account, and this format was easier
+- The Go tally library has a nice way of [extending it to work with custom storage](https://pkg.go.dev/github.com/uber-go/tally#StatsReporter), and saves us time to release
+- #3 requires more involved technical work, which unfortunately we do not have the capacity and time right now
To give an idea what I’m talking about, here’s what a typical tally metric recording look like
@@ -114,6 +115,7 @@ metrics.Tagged(
metrics.WithFunctionID(id.FunctionID.String()),
).Counter(timescale.CounterFunctionRunStartedTotal).Inc(1)
```
+
which will create a Counter for `FunctionRunStartedTotal` if it doesn't exist, and increment the Counter.
We can leverage this easily by providing a [custom reporter](https://pkg.go.dev/github.com/uber-go/tally#StatsReporter) like this.
@@ -169,8 +171,8 @@ func (r metricsReporter) ReportCounter(name string, tags map[string]string, valu
All we need to care about now is to make sure `recordCounter` can map the metrics correctly to the database tables when writing to it, instead of having to figure out all the details like,
-* handle mapping of metrics with tags
-* atomic operations for counters, gauages, etc
+- handle mapping of metrics with tags
+- atomic operations for counters, gauages, etc
### Architecture
@@ -285,7 +287,7 @@ The screenshot above shows an example when inspecting deltas with different meth
The goal here is `calculate the difference between the last value of the current bucket and the previous bucket`. In other words,
```js
-diff = prev ? current - prev : current
+diff = prev ? current - prev : current;
```
When attempting to calculating the difference using `delta` or `interpolated_delta` aggregate functions, both are off from the actual value by `+1` or `-1`.
@@ -362,11 +364,8 @@ The cloud offering also provides good indications of compression, and they have
While there were some challenges, those were more due to the technical choices we made. For what we’ve been trying to do, Timescale has been performing very well, and above all it has allowed us to get something out rather quickly.
-
---
[^1]: Uber has a good [blog post](https://www.uber.com/blog/logging/) about this for their logging if you're interested. This can also apply to metrics as they're also just logs in a different format.
-
[^2]: Although they do seem to have a [hybrid vectorization](https://www.timescale.com/blog/teaching-postgres-new-tricks-simd-vectorization-for-faster-analytical-queries/) as well if you're interested.
-
-[^3]: There are other ways to smooth the transition, including delete -> update to merge segments or create a new table and record to both at the same time, but truncate was the quickest and most bullet proof method for our need at the time.
\ No newline at end of file
+[^3]: There are other ways to smooth the transition, including delete -> update to merge segments or create a new table and record to both at the same time, but truncate was the quickest and most bullet proof method for our need at the time.
diff --git a/pages/blog/_posts/2023-12-22-2023-wrapped.mdx b/pages/blog/_posts/2023-12-22-2023-wrapped.mdx
index 410c6f86d..613fe4d15 100644
--- a/pages/blog/_posts/2023-12-22-2023-wrapped.mdx
+++ b/pages/blog/_posts/2023-12-22-2023-wrapped.mdx
@@ -5,7 +5,7 @@ subtitle: Over the past twelve months, we've shipped a lot and improved the DX a
image: /assets/blog/2023-wrapped-inngest/feature.png
date: 2023-12-22
author: Sylwia Vargas
-disableCTA: true
+category: product-updates
---
It’s been a busy year at Inngest.
diff --git a/pages/blog/_posts/2024-08-16-incident-report.mdx b/pages/blog/_posts/2024-08-16-incident-report.mdx
index 43ab4133f..fae7e11ab 100644
--- a/pages/blog/_posts/2024-08-16-incident-report.mdx
+++ b/pages/blog/_posts/2024-08-16-incident-report.mdx
@@ -5,7 +5,8 @@ subtitle: A full report on the incident that caused function execution to fail o
image: /assets/blog/2024-08-16-incident-report/featured-image.png
date: 2024-08-16
author: Dan Farrelly
-disableCTA: true
+featured: false
+category: engineering
---
On August 16, 2024 (UTC), Inngest experienced an outage that prevent functions execution from successfully running. Specifically, our forward proxy for all function invocation requests, internally known as the “SDK Gateway,” stopped handling requests properly.
diff --git a/pages/blog/_posts/5-lessons-learned-from-taking-next-js-app-router-to-production.md b/pages/blog/_posts/5-lessons-learned-from-taking-next-js-app-router-to-production.md
index c033434c0..0b29bc06b 100644
--- a/pages/blog/_posts/5-lessons-learned-from-taking-next-js-app-router-to-production.md
+++ b/pages/blog/_posts/5-lessons-learned-from-taking-next-js-app-router-to-production.md
@@ -4,6 +4,7 @@ subtitle: "What did we learn from building and shipping our new app with the Nex
image: "/assets/blog/5-lessons-learned-from-taking-next-js-app-router-to-production/featured-image.png"
date: 2023-05-05
author: Igor Gassmann
+category: engineering
---
Next.js 13 introduced the new [App Router](https://nextjs.org/docs/app) that offers several new features, including [Nested Layouts](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#nesting-layouts), [Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components), and [Streaming](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#what-is-streaming). It’s the first open-source implementation that allows developers to fully leverage the primitives brought by [React 18](https://react.dev/blog/2022/03/29/react-v18).
diff --git a/pages/blog/_posts/accidentally-quadratic-evaluating-trillions-of-event-matches-in-real-time.mdx b/pages/blog/_posts/accidentally-quadratic-evaluating-trillions-of-event-matches-in-real-time.mdx
index b43bf2759..2e69893ee 100644
--- a/pages/blog/_posts/accidentally-quadratic-evaluating-trillions-of-event-matches-in-real-time.mdx
+++ b/pages/blog/_posts/accidentally-quadratic-evaluating-trillions-of-event-matches-in-real-time.mdx
@@ -6,6 +6,7 @@ image: /assets/blog/accidentally-quadratic/featured-image.png
imageCredits: Image by Rick Rothenberg on Unsplash
date: 2024-05-15
author: Tony Holdstock-Brown
+category: engineering
---
Event-driven systems depend on accurately matching and filtering events. This process ensures data is directed to the right part of the code that needs specific event information. Systems like Kafka offer topics and filters to allow consumers to achieve this, but they need to be determined up front either at the infrastructure level or directly in the code. This is great, but what about in a multi-tenant system where you don't know what event types or filters you might need up front?
@@ -18,10 +19,10 @@ This is the core of the challenge that we have with Inngest. Inngest allows deve
As an example, this code allows the developer to pause a function in the middle of execution to wait for an `app/onboarding.completed` event with a payload that matches the value of the `data.userId` exactly:
-```js
+```js
const onboardingCompleted = await step.waitForEvent(
"wait-for-onboarding-completion",
- {
+ {
event: "app/onboarding.completed",
timeout: "3d",
if: `data.userId == '${user.id}'`
diff --git a/pages/blog/_posts/announcing-batch-keys.mdx b/pages/blog/_posts/announcing-batch-keys.mdx
index c4d4b9bea..0aa918b80 100644
--- a/pages/blog/_posts/announcing-batch-keys.mdx
+++ b/pages/blog/_posts/announcing-batch-keys.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
subtitle: Batch keys allows developers to group work units by leveraging Inngest's efficient event-matching engine.
date: 2024-07-25
author: Charly Poly
-disableCTA: false
+category: product-updates
---
We are thrilled to release Batch Keys, a powerful addition to _Event Batching_. This new feature allows developers to group work units by leveraging [Inngest's efficient event-matching engine](https://www.inngest.com/blog/accidentally-quadratic-evaluating-trillions-of-event-matches-in-real-time), unlocking new advanced use cases such as Batch User Notifications or more efficient processing of large volumes of events.
diff --git a/pages/blog/_posts/announcing-funding-from-a16z.mdx b/pages/blog/_posts/announcing-funding-from-a16z.mdx
index b26442287..23e749e1e 100644
--- a/pages/blog/_posts/announcing-funding-from-a16z.mdx
+++ b/pages/blog/_posts/announcing-funding-from-a16z.mdx
@@ -5,6 +5,7 @@ showSubtitle: true
image: /assets/blog/announcing-funding-from-a16z/featured-image.png
date: 2024-01-30
author: Dan Farrelly, Tony Holdstock-Brown
+category: company-news
---
**Today, we're very excited to announce that Inngest has raised $6.1M in new funding led by Martin Casado and Yoko Li from a16z (Andreessen Horowitz), with follow-on investment from existing investors: GGV, Afore Capital and Guillermo Rauch.**
diff --git a/pages/blog/_posts/announcing-inngest-seed-financing.mdx b/pages/blog/_posts/announcing-inngest-seed-financing.mdx
index 408a85054..b77e506fb 100644
--- a/pages/blog/_posts/announcing-inngest-seed-financing.mdx
+++ b/pages/blog/_posts/announcing-inngest-seed-financing.mdx
@@ -4,6 +4,7 @@ subtitle: New round led by Glenn Solomon of GGV Capital, including Guillermo Rau
image: /assets/blog/seed-financing/featured-image.png?v1
date: 2023-07-12
author: Dan Farrelly, Tony Holdstock-Brown
+category: company-news
---
import Blockquote from "src/shared/Blog/Blockquote";
diff --git a/pages/blog/_posts/announcing-replay-the-death-of-the-dead-letter-queue.mdx b/pages/blog/_posts/announcing-replay-the-death-of-the-dead-letter-queue.mdx
index 500954b74..b6abc619b 100644
--- a/pages/blog/_posts/announcing-replay-the-death-of-the-dead-letter-queue.mdx
+++ b/pages/blog/_posts/announcing-replay-the-death-of-the-dead-letter-queue.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/announcing-replay/featured-image.png
date: 2024-01-22
author: Dan Farrelly
-disableCTA: true
+category: product-updates
---
Production systems will fail. Incidents are inevitable.
diff --git a/pages/blog/_posts/branch-environments.mdx b/pages/blog/_posts/branch-environments.mdx
index fbb502533..63e093ec7 100644
--- a/pages/blog/_posts/branch-environments.mdx
+++ b/pages/blog/_posts/branch-environments.mdx
@@ -4,7 +4,7 @@ subtitle: Modern workflow for your business-critical code
image: /assets/blog/branch-environments/featured-image.png
date: 2023-05-17
author: Dan Farrelly
-disableCTA: true
+category: product-updates
---
The modern developer workflow is centered around branching. Git is used to create branches, CI platforms run automated tests on each branch, and platforms like Vercel and Netlify have brought CD to branches with their deploy preview features. However, while deploy previews can help with front-end development, it can result in an awkward workflow when testing changes end-to-end using shared backing APIs, databases, caches, queues, and more.
diff --git a/pages/blog/_posts/building-the-inngest-queue-pt-i-fairness-multi-tenancy.mdx b/pages/blog/_posts/building-the-inngest-queue-pt-i-fairness-multi-tenancy.mdx
index 9f39a2254..dc2250d55 100644
--- a/pages/blog/_posts/building-the-inngest-queue-pt-i-fairness-multi-tenancy.mdx
+++ b/pages/blog/_posts/building-the-inngest-queue-pt-i-fairness-multi-tenancy.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/inngest-queue-pt-i/featured-image-v2.png
date: 2024-01-22
author: Tony Holdstock-Brown
-disableCTA: true
+category: engineering
---
In software development, queueing systems are important for reliability, and they're hard to build. There's lots of debate around *how* to build a queueing system — with [Postgres](https://news.ycombinator.com/item?id=35526846) and [SKIP LOCKED](https://news.ycombinator.com/item?id=37636841) *always* cropping up.
diff --git a/pages/blog/_posts/bulk-cancellation-api.mdx b/pages/blog/_posts/bulk-cancellation-api.mdx
index 6f70ee48b..8df2728e4 100644
--- a/pages/blog/_posts/bulk-cancellation-api.mdx
+++ b/pages/blog/_posts/bulk-cancellation-api.mdx
@@ -4,7 +4,7 @@ subtitle: Cancel a time range of functions using the REST API.
image: /assets/blog/bulk-cancellation-api/featured-image.png
date: 2024-01-22
author: Tony Holdstock-Brown
-disableCTA: true
+category: product-updates
---
Queues are vital for smoothing demand in production systems. This works when throughput is stable, but sometimes you have too much demand, and your backlog grows without shrinking. It's also possible that a batch of faulty work is added to your backlog by an uncaught bug. In both cases it's important that you can easily manage your backlog to keep your systems operating smoothly.
diff --git a/pages/blog/_posts/bulk-cancellation.mdx b/pages/blog/_posts/bulk-cancellation.mdx
index 2fe8f2d04..32dec8846 100644
--- a/pages/blog/_posts/bulk-cancellation.mdx
+++ b/pages/blog/_posts/bulk-cancellation.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/bulk-cancellation/featured-image.png
date: 2024-09-27
author: Cheryl Manalo
-disableCTA: true
+category: product-updates
---
The Bulk Cancellation UI feature is the latest addition to Inngest's powerful recovery tool suite, designed to help users manage and recover from incidents with ease. Alongside tools like [**Pause**](/docs/guides/pause-functions?ref=blog-bulk-cancellation#when-a-function-is-paused) and [**Replay**](/docs/platform/replay?ref=blog-bulk-cancellation), this feature provides a higher-level declarative approach to managing your workflows.
diff --git a/pages/blog/_posts/cross-language-support-with-new-sdks.mdx b/pages/blog/_posts/cross-language-support-with-new-sdks.mdx
index 6bdec551c..56f9b1c39 100644
--- a/pages/blog/_posts/cross-language-support-with-new-sdks.mdx
+++ b/pages/blog/_posts/cross-language-support-with-new-sdks.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/cross-language-support-with-new-sdks/featured-image.png
date: 2024-01-23
author: Sylwia Vargas
-disableCTA: true
+category: product-updates
---
In late 2022, we launched our [JavaScript/TypeScript SDK](/docs/reference/typescript) to improve the Developer Experience of our tools. This was just the first step towards becoming a truly language-agnostic reliability layer for everyone.
diff --git a/pages/blog/_posts/edge-event-api-beta.mdx b/pages/blog/_posts/edge-event-api-beta.mdx
index db23fc42b..8ef579ad2 100644
--- a/pages/blog/_posts/edge-event-api-beta.mdx
+++ b/pages/blog/_posts/edge-event-api-beta.mdx
@@ -5,7 +5,6 @@ showSubtitle: true
image: /assets/blog/edge-event-api-beta/featured-image.png
date: 2024-01-26
author: Dan Farelly
-disableCTA: true
---
At the core of Inngest are events. Events are used to trigger, cancel, and continue functions.
diff --git a/pages/blog/_posts/enhanced-observability-traces-and-metrics.mdx b/pages/blog/_posts/enhanced-observability-traces-and-metrics.mdx
index 56cb0d72d..9068eef35 100644
--- a/pages/blog/_posts/enhanced-observability-traces-and-metrics.mdx
+++ b/pages/blog/_posts/enhanced-observability-traces-and-metrics.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/enhanced-observability-traces-and-metrics/featured-image-2.png
date: 2024-09-26
author: Cheryl Manalo
-disableCTA: true
+category: product-updates
---
At Inngest, we are constantly evolving to meet the needs of developers building applications with durable execution. Today, we're excited to unveil two significant updates designed to enhance how you monitor, debug, and gain insights into your durable workflows: **our new trace experience for runs and a holistic top-down monitoring dashboard**. Together, these updates redefine how you interact with your function executions and provide deeper insights into the overall health of your Inngest applications.
diff --git a/pages/blog/_posts/improved-error-handling.mdx b/pages/blog/_posts/improved-error-handling.mdx
index 61b89908f..050b4a461 100644
--- a/pages/blog/_posts/improved-error-handling.mdx
+++ b/pages/blog/_posts/improved-error-handling.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/improved-error-handling/featured-image.png
date: 2024-01-25
author: Dan Farrelly
-disableCTA: true
+category: product-updates
---
We built Inngest to help you build reliable products. Every Inngest function has automatic retries. Functions can be broken down into individual steps which are all individually executed and [retried on error](/docs/functions/retries). This makes your code durable. Inngest acts like a nice reliability layer on top of your code.
diff --git a/pages/blog/_posts/inngest-1-0-announcing-self-hosting-support.mdx b/pages/blog/_posts/inngest-1-0-announcing-self-hosting-support.mdx
index 2fea1fa31..bf72097c9 100644
--- a/pages/blog/_posts/inngest-1-0-announcing-self-hosting-support.mdx
+++ b/pages/blog/_posts/inngest-1-0-announcing-self-hosting-support.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/self-hosting/featured-image.png
date: 2024-09-23
author: Dan Farrelly
-disableCTA: true
+category: product-updates
---
Today, alongside our 1.0 release of [the Inngest open source project](https://github.com/inngest/inngest), we're excited to announce the ability to self-host Inngest on your own infrastructure. We're excited about this milestone for a number of reasons. First, self-hosting support is one of the most highly requested features from our developer community. Second, it gives developers more flexibility with how they use Inngest. Lastly, it is the easiest way to self-host durable execution, period.
diff --git a/pages/blog/_posts/introducing-cli-replays.md b/pages/blog/_posts/introducing-cli-replays.md
index ea63e2a61..6434a6439 100644
--- a/pages/blog/_posts/introducing-cli-replays.md
+++ b/pages/blog/_posts/introducing-cli-replays.md
@@ -5,7 +5,6 @@ subtitle: Battle-test your local code with real production events.
image: "/assets/blog/introducing-cli-replays/header.jpg"
date: 2022-08-03
author: Jack Williams
-tags: new-feature
---
Building an event-driven system can be challenging: how do you know your code will run as expected once it's deployed? With the release of [v0.5.0](/blog/release-v0-5-0?ref=blog-introducing-cli-replays), we're excited to launch `inngest run --replay`.
diff --git a/pages/blog/_posts/introducing-workflow-kit.mdx b/pages/blog/_posts/introducing-workflow-kit.mdx
index bfc67edfb..ee8011e8b 100644
--- a/pages/blog/_posts/introducing-workflow-kit.mdx
+++ b/pages/blog/_posts/introducing-workflow-kit.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/introducing-workflow-kit/featured-image-2.png
date: 2024-09-25
author: Charly Poly
-disableCTA: true
+category: product-updates
---
Today, we're excited to unveil our new open-source SDK: the **Inngest Workflow Kit**.
diff --git a/pages/blog/_posts/neon-postgres-database-triggers-for-durable-functions.mdx b/pages/blog/_posts/neon-postgres-database-triggers-for-durable-functions.mdx
index 3f49976dc..18b0f89df 100644
--- a/pages/blog/_posts/neon-postgres-database-triggers-for-durable-functions.mdx
+++ b/pages/blog/_posts/neon-postgres-database-triggers-for-durable-functions.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
image: /assets/blog/neon-integration/featured-image.png
date: 2024-09-24
author: Dan Farrelly
-disableCTA: true
+category: product-updates
---
We're excited to announce our integration with [Neon](https://neon.tech/), enabling you to trigger durable functions directly from changes in your Neon Postgres database.
diff --git a/pages/blog/_posts/python-errors-as-values.mdx b/pages/blog/_posts/python-errors-as-values.mdx
index d21758b92..5dbda15fd 100644
--- a/pages/blog/_posts/python-errors-as-values.mdx
+++ b/pages/blog/_posts/python-errors-as-values.mdx
@@ -4,7 +4,7 @@ subtitle: Safer error handling, inspired by Go and Rust
image: /assets/blog/python-errors-as-values/featured-image.png
date: 2023-11-08
author: Aaron Harper
-disableCTA: true
+category: engineering
---
Errors happen in programs -- they're unavoidable! It's important to know both *where* errors can happen and *how* to effectively handle them. As we developed our [Python SDK](https://github.com/inngest/inngest-py), safe and effective error handling was paramount.
diff --git a/pages/blog/_posts/sharding-at-inngest.mdx b/pages/blog/_posts/sharding-at-inngest.mdx
index 2fda41b13..250efe596 100644
--- a/pages/blog/_posts/sharding-at-inngest.mdx
+++ b/pages/blog/_posts/sharding-at-inngest.mdx
@@ -5,7 +5,7 @@ showSubtitle: true
subtitle: Read about how we rolled our new sharded infrastructure out to production without a millisecond of downtime and how it improved Inngest's overall performance.
date: 2024-07-23
author: Bruno Scheufler
-disableCTA: true
+category: engineering
---
**Every day, more and more software teams rely on Inngest for their core infrastructure which results in increased load on our core systems. When building distributed systems, vertical scaling can only get you so far. Because of that, we took on a project to horizontally scale by sharding our most crucial database, which serves more than 50,000 requests per second at low latency. As Inngest is critical infrastructure for many customers, we aim to avoid maintenance windows at all costs. In this post we’ll walk through how we sharded our function state database with zero downtime.**
diff --git a/pages/blog/_posts/soc2-compliant.mdx b/pages/blog/_posts/soc2-compliant.mdx
index c2513502a..a2321f593 100644
--- a/pages/blog/_posts/soc2-compliant.mdx
+++ b/pages/blog/_posts/soc2-compliant.mdx
@@ -5,6 +5,7 @@ image: /assets/blog/soc2-compliant/featured-image.png
date: 2024-06-20
author: Dan Farrelly
disableCTA: true
+category: product-updates
---
Inngest is proud to announce that we have achieved SOC 2 Type II compliance. This certification is a testament to our commitment to security and privacy, and our dedication to protecting our customers' data.
diff --git a/pages/blog/category/[category].tsx b/pages/blog/category/[category].tsx
new file mode 100644
index 000000000..a677a77db
--- /dev/null
+++ b/pages/blog/category/[category].tsx
@@ -0,0 +1,70 @@
+import Head from "next/head";
+
+import { loadMarkdownFilesMetadata } from "src/utils/markdown";
+import BlogPostList from "src/components/Blog/BlogPostList";
+import BlogHeader from "src/components/Blog/BlogHeader";
+import Container from "src/shared/layout/Container";
+import Nav from "src/components/Nav";
+import { type BlogPost, BLOG_CATEGORIES } from "src/components/Blog";
+
+type StaticProps = {
+ serializedPosts: string[];
+ categoryTitle: string;
+ meta: {
+ title: string;
+ description: string;
+ };
+};
+
+export default function BlogCategory(props: StaticProps) {
+ const posts: BlogPost[] = props.serializedPosts.map((p) => JSON.parse(p));
+
+ return (
+ <>
+
+ Inngest - {props.meta.title}
+
+
+
+
+
+
+ >
+ );
+}
+
+export async function getStaticProps({
+ params,
+}: {
+ params: { category: string };
+}): Promise<{ props: StaticProps }> {
+ const posts = await loadMarkdownFilesMetadata("blog/_posts");
+ const filteredPosts = posts.filter((p) => p.category === params.category);
+ const serializedPosts = filteredPosts.map((p) => JSON.stringify(p));
+ const title = BLOG_CATEGORIES[params.category];
+
+ return {
+ props: {
+ serializedPosts,
+ categoryTitle: title,
+ meta: {
+ title: `Product & Engineering Blog - ${title}`,
+ description: `Blog posts covering the topic of ${title}`,
+ },
+ },
+ };
+}
+
+export async function getStaticPaths() {
+ const categories = Object.keys(BLOG_CATEGORIES);
+ const paths = categories.map((c) => `/blog/category/${c}`);
+ return { paths, fallback: false };
+}