mirror of
https://github.com/AderKonstantin/aderktech-chronark.com-.git
synced 2025-06-08 05:38:41 +03:00
fix: og text
This commit is contained in:
parent
8f14ec4fbe
commit
68228c44fc
@ -1,2 +1,2 @@
|
||||
UPSTASH_REDIS_REST_URL=
|
||||
UPSTASH_REDIS_REST_TOKEN=
|
||||
UPSTASH_REDIS_REST_TOKEN=
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<a href="https://chronark.com"><h1 align="center">chronark.com</h1></a>
|
||||
|
||||
|
||||
My personal website, built with [Next.js](https://nextjs.org/), [Tailwind CSS](https://tailwindcss.com/), [Upstash](https://upstash.com?ref=chronark.com), [Contentlayer](https://www.contentlayer.dev/) and deployed to [Vercel](https://vercel.com/).
|
||||
|
||||
</div>
|
||||
@ -30,4 +30,4 @@ pnpm dev
|
||||
|
||||
## Cloning / Forking
|
||||
|
||||
Please remove all of my personal information (projects, images, etc.) before deploying your own version of this site.
|
||||
Please remove all of my personal information (projects, images, etc.) before deploying your own version of this site.
|
||||
|
117
app/layout.tsx
117
app/layout.tsx
@ -5,73 +5,72 @@ import { Metadata } from "next";
|
||||
import { Analytics } from "./components/analytics";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "chronark.com",
|
||||
template: "%s | chronark.com",
|
||||
},
|
||||
description: "Software engineer at upstash.com and founder of planetfall.io",
|
||||
openGraph: {
|
||||
title: "chronark.com",
|
||||
description:
|
||||
"Software engineer at upstash.com and founder of planetfall.io",
|
||||
url: "https://chronark.com",
|
||||
siteName: "chronark.com",
|
||||
images: [
|
||||
{
|
||||
url: "https://chronark.com/og.png",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
],
|
||||
locale: "en-US",
|
||||
type: "website",
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
twitter: {
|
||||
title: "Chronark",
|
||||
card: "summary_large_image",
|
||||
},
|
||||
icons: {
|
||||
shortcut: "/favicon.png",
|
||||
},
|
||||
title: {
|
||||
default: "chronark.com",
|
||||
template: "%s | chronark.com",
|
||||
},
|
||||
description: "Co-founder of unkey.dev and founder of planetfall.io",
|
||||
openGraph: {
|
||||
title: "chronark.com",
|
||||
description:
|
||||
"Co-founder of unkey.dev and founder of planetfall.io",
|
||||
url: "https://chronark.com",
|
||||
siteName: "chronark.com",
|
||||
images: [
|
||||
{
|
||||
url: "https://chronark.com/og.png",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
],
|
||||
locale: "en-US",
|
||||
type: "website",
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
"max-video-preview": -1,
|
||||
"max-image-preview": "large",
|
||||
"max-snippet": -1,
|
||||
},
|
||||
},
|
||||
twitter: {
|
||||
title: "Chronark",
|
||||
card: "summary_large_image",
|
||||
},
|
||||
icons: {
|
||||
shortcut: "/favicon.png",
|
||||
},
|
||||
};
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
});
|
||||
|
||||
const calSans = LocalFont({
|
||||
src: "../public/fonts/CalSans-SemiBold.ttf",
|
||||
variable: "--font-calsans",
|
||||
src: "../public/fonts/CalSans-SemiBold.ttf",
|
||||
variable: "--font-calsans",
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={[inter.variable, calSans.variable].join(" ")}>
|
||||
<head>
|
||||
<Analytics />
|
||||
</head>
|
||||
<body
|
||||
className={`bg-black ${
|
||||
process.env.NODE_ENV === "development" ? "debug-screens" : undefined
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
return (
|
||||
<html lang="en" className={[inter.variable, calSans.variable].join(" ")}>
|
||||
<head>
|
||||
<Analytics />
|
||||
</head>
|
||||
<body
|
||||
className={`bg-black ${process.env.NODE_ENV === "development" ? "debug-screens" : undefined
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
@ -9,40 +9,40 @@ import { Redis } from "@upstash/redis";
|
||||
export const revalidate = 60;
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
|
||||
const redis = Redis.fromEnv();
|
||||
|
||||
export async function generateStaticParams(): Promise<Props["params"][]> {
|
||||
return allProjects
|
||||
.filter((p) => p.published)
|
||||
.map((p) => ({
|
||||
slug: p.slug,
|
||||
}));
|
||||
return allProjects
|
||||
.filter((p) => p.published)
|
||||
.map((p) => ({
|
||||
slug: p.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function PostPage({ params }: Props) {
|
||||
const slug = params?.slug;
|
||||
const project = allProjects.find((project) => project.slug === slug);
|
||||
const slug = params?.slug;
|
||||
const project = allProjects.find((project) => project.slug === slug);
|
||||
|
||||
if (!project) {
|
||||
notFound();
|
||||
}
|
||||
if (!project) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const views =
|
||||
(await redis.get<number>(["pageviews", "projects", slug].join(":"))) ?? 0;
|
||||
const views =
|
||||
(await redis.get<number>(["pageviews", "projects", slug].join(":"))) ?? 0;
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-50 min-h-screen">
|
||||
<Header project={project} views={views} />
|
||||
<ReportView slug={project.slug} />
|
||||
return (
|
||||
<div className="bg-zinc-50 min-h-screen">
|
||||
<Header project={project} views={views} />
|
||||
<ReportView slug={project.slug} />
|
||||
|
||||
<article className="px-4 py-12 mx-auto prose prose-zinc prose-quoteless">
|
||||
<Mdx code={project.body.code} />
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
<article className="px-4 py-12 mx-auto prose prose-zinc prose-quoteless">
|
||||
<Mdx code={project.body.code} />
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -11,128 +11,128 @@ const redis = Redis.fromEnv();
|
||||
|
||||
export const revalidate = 60;
|
||||
export default async function ProjectsPage() {
|
||||
const views = (
|
||||
await redis.mget<number[]>(
|
||||
...allProjects.map((p) => ["pageviews", "projects", p.slug].join(":")),
|
||||
)
|
||||
).reduce((acc, v, i) => {
|
||||
acc[allProjects[i].slug] = v ?? 0;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
const views = (
|
||||
await redis.mget<number[]>(
|
||||
...allProjects.map((p) => ["pageviews", "projects", p.slug].join(":")),
|
||||
)
|
||||
).reduce((acc, v, i) => {
|
||||
acc[allProjects[i].slug] = v ?? 0;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
const featured = allProjects.find((project) => project.slug === "unkey")!;
|
||||
const top2 = allProjects.find((project) => project.slug === "planetfall")!;
|
||||
const top3 = allProjects.find((project) => project.slug === "highstorm")!;
|
||||
const sorted = allProjects
|
||||
.filter((p) => p.published)
|
||||
.filter(
|
||||
(project) =>
|
||||
project.slug !== featured.slug &&
|
||||
project.slug !== top2.slug &&
|
||||
project.slug !== top3.slug,
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.date ?? Number.POSITIVE_INFINITY).getTime() -
|
||||
new Date(a.date ?? Number.POSITIVE_INFINITY).getTime(),
|
||||
);
|
||||
const featured = allProjects.find((project) => project.slug === "unkey")!;
|
||||
const top2 = allProjects.find((project) => project.slug === "planetfall")!;
|
||||
const top3 = allProjects.find((project) => project.slug === "highstorm")!;
|
||||
const sorted = allProjects
|
||||
.filter((p) => p.published)
|
||||
.filter(
|
||||
(project) =>
|
||||
project.slug !== featured.slug &&
|
||||
project.slug !== top2.slug &&
|
||||
project.slug !== top3.slug,
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.date ?? Number.POSITIVE_INFINITY).getTime() -
|
||||
new Date(a.date ?? Number.POSITIVE_INFINITY).getTime(),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative pb-16">
|
||||
<Navigation />
|
||||
<div className="px-6 pt-20 mx-auto space-y-8 max-w-7xl lg:px-8 md:space-y-16 md:pt-24 lg:pt-32">
|
||||
<div className="max-w-2xl mx-auto lg:mx-0">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-zinc-100 sm:text-4xl">
|
||||
Projects
|
||||
</h2>
|
||||
<p className="mt-4 text-zinc-400">
|
||||
Some of the projects are from work and some are on my own time.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full h-px bg-zinc-800" />
|
||||
return (
|
||||
<div className="relative pb-16">
|
||||
<Navigation />
|
||||
<div className="px-6 pt-20 mx-auto space-y-8 max-w-7xl lg:px-8 md:space-y-16 md:pt-24 lg:pt-32">
|
||||
<div className="max-w-2xl mx-auto lg:mx-0">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-zinc-100 sm:text-4xl">
|
||||
Projects
|
||||
</h2>
|
||||
<p className="mt-4 text-zinc-400">
|
||||
Some of the projects are from work and some are on my own time.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full h-px bg-zinc-800" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 mx-auto lg:grid-cols-2 ">
|
||||
<Card>
|
||||
<Link href={`/projects/${featured.slug}`}>
|
||||
<article className="relative w-full h-full p-4 md:p-8">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="text-xs text-zinc-100">
|
||||
{featured.date ? (
|
||||
<time dateTime={new Date(featured.date).toISOString()}>
|
||||
{Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
}).format(new Date(featured.date))}
|
||||
</time>
|
||||
) : (
|
||||
<span>SOON</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="flex items-center gap-1 text-xs text-zinc-500">
|
||||
<Eye className="w-4 h-4" />{" "}
|
||||
{Intl.NumberFormat("en-US", { notation: "compact" }).format(
|
||||
views[featured.slug] ?? 0,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-8 mx-auto lg:grid-cols-2 ">
|
||||
<Card>
|
||||
<Link href={`/projects/${featured.slug}`}>
|
||||
<article className="relative w-full h-full p-4 md:p-8">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="text-xs text-zinc-100">
|
||||
{featured.date ? (
|
||||
<time dateTime={new Date(featured.date).toISOString()}>
|
||||
{Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
}).format(new Date(featured.date))}
|
||||
</time>
|
||||
) : (
|
||||
<span>SOON</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="flex items-center gap-1 text-xs text-zinc-500">
|
||||
<Eye className="w-4 h-4" />{" "}
|
||||
{Intl.NumberFormat("en-US", { notation: "compact" }).format(
|
||||
views[featured.slug] ?? 0,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
id="featured-post"
|
||||
className="mt-4 text-3xl font-bold text-zinc-100 group-hover:text-white sm:text-4xl font-display"
|
||||
>
|
||||
{featured.title}
|
||||
</h2>
|
||||
<p className="mt-4 leading-8 duration-150 text-zinc-400 group-hover:text-zinc-300">
|
||||
{featured.description}
|
||||
</p>
|
||||
<div className="absolute bottom-4 md:bottom-8">
|
||||
<p className="hidden text-zinc-200 hover:text-zinc-50 lg:block">
|
||||
Read more <span aria-hidden="true">→</span>
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</Link>
|
||||
</Card>
|
||||
<h2
|
||||
id="featured-post"
|
||||
className="mt-4 text-3xl font-bold text-zinc-100 group-hover:text-white sm:text-4xl font-display"
|
||||
>
|
||||
{featured.title}
|
||||
</h2>
|
||||
<p className="mt-4 leading-8 duration-150 text-zinc-400 group-hover:text-zinc-300">
|
||||
{featured.description}
|
||||
</p>
|
||||
<div className="absolute bottom-4 md:bottom-8">
|
||||
<p className="hidden text-zinc-200 hover:text-zinc-50 lg:block">
|
||||
Read more <span aria-hidden="true">→</span>
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</Link>
|
||||
</Card>
|
||||
|
||||
<div className="flex flex-col w-full gap-8 mx-auto border-t border-gray-900/10 lg:mx-0 lg:border-t-0 ">
|
||||
{[top2, top3].map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden w-full h-px md:block bg-zinc-800" />
|
||||
<div className="flex flex-col w-full gap-8 mx-auto border-t border-gray-900/10 lg:mx-0 lg:border-t-0 ">
|
||||
{[top2, top3].map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden w-full h-px md:block bg-zinc-800" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 mx-auto lg:mx-0 md:grid-cols-3">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 0)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 1)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 2)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="grid grid-cols-1 gap-4 mx-auto lg:mx-0 md:grid-cols-3">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 0)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 1)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{sorted
|
||||
.filter((_, i) => i % 3 === 2)
|
||||
.map((project) => (
|
||||
<Card key={project.slug}>
|
||||
<Article project={project} views={views[project.slug] ?? 0} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -113,4 +113,4 @@ $ curl -s https://envshare.dev/api/v1/secret/HdPbXgpvUvNk43oxSdK97u
|
||||
"remainingReads": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -4,7 +4,7 @@ description: QStash is a fully managed serverless queue and messaging service de
|
||||
date: "2022-07-18"
|
||||
url: https://upstash.com/qstash
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
QStash is an HTTP based messaging and scheduling solution for the serverless and edge runtimes.
|
||||
@ -33,4 +33,4 @@ If you use QStash in the above example, you simply send a request to QStash from
|
||||
With QStash, you can add delays to the requests. Send an email 3 days after the shipment to remind the customer to add a review. You can also schedule tasks. You can send the requests with a CRON expression, so it will be run repetitively.
|
||||
|
||||
|
||||
To learn more about QStash, visit [upstash.com/qstash](upstash.com/qstash).
|
||||
To learn more about QStash, visit [upstash.com/qstash](upstash.com/qstash).
|
||||
|
@ -5,4 +5,4 @@ url: https://console.upstash.com/ratelimit
|
||||
repository: upstash/auth-analytics
|
||||
---
|
||||
|
||||
TODO:
|
||||
TODO:
|
||||
|
@ -4,7 +4,7 @@ description: A CLI to provision and manage Upstash resources, including Redis an
|
||||
date: "2022-05-16"
|
||||
repository: upstash/cli
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ for windows, linux and mac (both intel and m1).
|
||||
```bash
|
||||
> upstash
|
||||
|
||||
Usage: upstash
|
||||
Usage: upstash
|
||||
Version: development
|
||||
|
||||
Description:
|
||||
@ -47,15 +47,15 @@ for windows, linux and mac (both intel and m1).
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
-V, --version - Show the version number for this program.
|
||||
-h, --help - Show this help.
|
||||
-V, --version - Show the version number for this program.
|
||||
-c, --config <string> - Path to .upstash.json file
|
||||
|
||||
Commands:
|
||||
|
||||
auth - Login and logout
|
||||
redis - Manage redis database instances
|
||||
kafka - Manage kafka clusters and topics
|
||||
auth - Login and logout
|
||||
redis - Manage redis database instances
|
||||
kafka - Manage kafka clusters and topics
|
||||
team - Manage your teams and their members
|
||||
|
||||
Environment variables:
|
||||
|
@ -5,7 +5,7 @@ date: "2023-02-13"
|
||||
url: https://console.upstash.com/ratelimit
|
||||
repository: upstash/core-analytics
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ date: "2022-12-12"
|
||||
url: https://upstash.com/blog/edge-flags-beta
|
||||
repository: upstash/edge-flags
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ Whether you want to ship without breaking things, run A/B tests or just want to
|
||||
feature flags are a great way to dynamically change the behaviour of your app without redeploying. We're excited to announce the public
|
||||
beta release of our new feature flagging library: [@upstash/edge-flags](https://github.com/upstash/edge-flags).
|
||||
|
||||
*Edge Flags*, as the name implies, is a feature flag solution built to run at the edge. It is using [Upstash Redis](https://upstash.com/), a globally replicated serverless Redis service, to store configuration and is
|
||||
*Edge Flags*, as the name implies, is a feature flag solution built to run at the edge. It is using [Upstash Redis](https://upstash.com/), a globally replicated serverless Redis service, to store configuration and is
|
||||
designed to work with [Next.js](https://nextjs.org) and [Vercel](https://vercel.com). We want to support other frameworks in the future, so if you have a suggestion, please let us know!
|
||||
|
||||
With the ability to toggle features on and off at the edge, you can quickly respond
|
||||
|
@ -5,7 +5,7 @@ date: "2022-01-08"
|
||||
url: https://upstash.com/kafka
|
||||
repository: upstash/upstash-kafka
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ description: Near realtime analytics for your ratelimits. Integrated into the @u
|
||||
repository: upstash/ratelimit"
|
||||
url: https://console.upstash.com/ratelimit
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
TODO:
|
||||
TODO:
|
||||
|
@ -5,7 +5,7 @@ date: "2022-06-06"
|
||||
url: https://upstash.com/blog/upstash-ratelimit
|
||||
repository: upstash/ratelimit
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
In today's digital age, serverless computing has become increasingly popular due to its scalability and cost-efficiency. One of the challenges of serverless computing is to manage resources efficiently, and one critical aspect of this is rate limiting. Rate limiting is a technique that limits the number of requests a client can make to a server over a given period. This technique can prevent abuse, improve performance, and reduce costs. One npm package that helps implement rate limiting for serverless applications is @upstash/ratelimit, built on top of Upstash Redis.
|
||||
@ -46,4 +46,4 @@ const { success } = await ratelimit.limit("identifier")
|
||||
|
||||
In the code above, we initialize Upstash with our Upstash Redis credentials and define our rate limiting rules. We then call the `limit` function, passing the identifier. The function returns a Promise that resolves with `success` and some other useful data.
|
||||
|
||||
`@upstash/ratelimit` is a useful npm package for serverless rate limiting that simplifies the process of implementing rate limiting for serverless applications. The package is built on top of Upstash Redis, which provides a complete solution for serverless applications. With `@upstash/ratelimit`, serverless developers can easily implement rate limiting, which can help prevent abuse, improve performance, and reduce costs.
|
||||
`@upstash/ratelimit` is a useful npm package for serverless rate limiting that simplifies the process of implementing rate limiting for serverless applications. The package is built on top of Upstash Redis, which provides a complete solution for serverless applications. With `@upstash/ratelimit`, serverless developers can easily implement rate limiting, which can help prevent abuse, improve performance, and reduce costs.
|
||||
|
@ -5,8 +5,8 @@ date: "2023-02-05"
|
||||
url: https://upstash.com
|
||||
repository: upstash/react-ui
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
`@upstash/react-ui` is powering the CLI in your browser on [console.upstash.com](https://console.upstash.com). It allows you to interact with your Upstash Redis database in a simple and intuitive way.
|
||||
`@upstash/react-ui` is powering the CLI in your browser on [console.upstash.com](https://console.upstash.com). It allows you to interact with your Upstash Redis database in a simple and intuitive way.
|
||||
|
@ -5,7 +5,7 @@ date: "2022-03-14"
|
||||
url: https://upstash.com/redis
|
||||
repository: upstash/upstash-redis
|
||||
published: true
|
||||
|
||||
|
||||
---
|
||||
|
||||
Upstash is a cloud-based service provider that offers a Redis-compatible service. In addition to that, we have also created an npm package called `@upstash/redis`. This package provides a strongly typed Redis client that uses HTTP instead of TCP to communicate with the database, making it perfect for serverless and edge runtimes.
|
||||
@ -42,4 +42,4 @@ console.log(data)
|
||||
await redis.lpush('elements', 'magnesium')
|
||||
data = await redis.lrange('elements', 0, 100 )
|
||||
console.log(data)
|
||||
```
|
||||
```
|
||||
|
@ -5,4 +5,4 @@ description: A library to record and analyse web page traffic and user behaviour
|
||||
published: true
|
||||
---
|
||||
|
||||
Coming soon
|
||||
Coming soon
|
||||
|
@ -52,4 +52,4 @@
|
||||
"@opentelemetry/semantic-conventions": "1.13.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,45 +3,45 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const redis = Redis.fromEnv();
|
||||
export const config = {
|
||||
runtime: "edge",
|
||||
runtime: "edge",
|
||||
};
|
||||
|
||||
export default async function incr(req: NextRequest): Promise<NextResponse> {
|
||||
if (req.method !== "POST") {
|
||||
return new NextResponse("use POST", { status: 405 });
|
||||
}
|
||||
if (req.headers.get("Content-Type") !== "application/json") {
|
||||
return new NextResponse("must be json", { status: 400 });
|
||||
}
|
||||
if (req.method !== "POST") {
|
||||
return new NextResponse("use POST", { status: 405 });
|
||||
}
|
||||
if (req.headers.get("Content-Type") !== "application/json") {
|
||||
return new NextResponse("must be json", { status: 400 });
|
||||
}
|
||||
|
||||
const body = await req.json();
|
||||
let slug: string | undefined = undefined;
|
||||
if ("slug" in body) {
|
||||
slug = body.slug;
|
||||
}
|
||||
if (!slug) {
|
||||
return new NextResponse("Slug not found", { status: 400 });
|
||||
}
|
||||
const ip = req.ip;
|
||||
if (ip) {
|
||||
// Hash the IP in order to not store it directly in your db.
|
||||
const buf = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
new TextEncoder().encode(ip),
|
||||
);
|
||||
const hash = Array.from(new Uint8Array(buf))
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
const body = await req.json();
|
||||
let slug: string | undefined = undefined;
|
||||
if ("slug" in body) {
|
||||
slug = body.slug;
|
||||
}
|
||||
if (!slug) {
|
||||
return new NextResponse("Slug not found", { status: 400 });
|
||||
}
|
||||
const ip = req.ip;
|
||||
if (ip) {
|
||||
// Hash the IP in order to not store it directly in your db.
|
||||
const buf = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
new TextEncoder().encode(ip),
|
||||
);
|
||||
const hash = Array.from(new Uint8Array(buf))
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
|
||||
// deduplicate the ip for each slug
|
||||
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
|
||||
nx: true,
|
||||
ex: 24 * 60 * 60,
|
||||
});
|
||||
if (!isNew) {
|
||||
new NextResponse(null, { status: 202 });
|
||||
}
|
||||
}
|
||||
await redis.incr(["pageviews", "projects", slug].join(":"));
|
||||
return new NextResponse(null, { status: 202 });
|
||||
// deduplicate the ip for each slug
|
||||
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
|
||||
nx: true,
|
||||
ex: 24 * 60 * 60,
|
||||
});
|
||||
if (!isNew) {
|
||||
new NextResponse(null, { status: 202 });
|
||||
}
|
||||
}
|
||||
await redis.incr(["pageviews", "projects", slug].join(":"));
|
||||
return new NextResponse(null, { status: 202 });
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user