Skip to content
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "submodules/chat"]
path = submodules/chat
url = https://github.com/lightfastai/chat.git
86 changes: 43 additions & 43 deletions apps/www/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,58 @@ import { NextConfig } from "next";

import "~/env";

import { mergeNextConfig } from "@vendor/next/merge-config";
import {
config as vendorConfig,
withBetterStack,
withSentry,
config as vendorConfig,
withBetterStack,
withSentry,
} from "@vendor/next/next-config-builder";
import { mergeNextConfig } from "@vendor/next/merge-config";

import { env } from "~/env";

let config: NextConfig = withBetterStack(
mergeNextConfig(vendorConfig, {
reactStrictMode: true,

/** Enables hot reloading for local packages without a build step */
transpilePackages: [
"@repo/ui",
"@vendor/security",
"@vendor/analytics",
"@vendor/email",
"@vendor/clerk",
"@vendor/inngest",
"@vendor/observability",
"@vendor/next",
"@vendor/upstash",
"@repo/lightfast-config",
"@repo/lightfast-email",
"@repo/lightfast-react",
"@repo/lib",
],

/** We already do linting and typechecking as separate tasks in CI */
// eslint: { ignoreDuringBuilds: true },
// typescript: { ignoreBuildErrors: true },

// Add automatic static optimization where possible
experimental: {
// For Next.js 15.3+
optimizeCss: true,
optimizePackageImports: [
"@repo/ui",
"jotai",
"lucide-react",
"react-confetti",
],
// Faster navigation for production
// ppr: true,
},
})
mergeNextConfig(vendorConfig, {
reactStrictMode: true,

/** Enables hot reloading for local packages without a build step */
transpilePackages: [
"@repo/ui",
"@vendor/security",
"@vendor/analytics",
"@vendor/email",
"@vendor/clerk",
"@vendor/inngest",
"@vendor/observability",
"@vendor/next",
"@vendor/upstash",
"@repo/lightfast-config",
"@repo/lightfast-email",
"@repo/lightfast-react",
"@repo/lib",
],

/** We already do linting and typechecking as separate tasks in CI */
// eslint: { ignoreDuringBuilds: true },
// typescript: { ignoreBuildErrors: true },

// Add automatic static optimization where possible
experimental: {
// For Next.js 15.3+
optimizeCss: true,
optimizePackageImports: [
"@repo/ui",
"jotai",
"lucide-react",
"react-confetti",
],
// Faster navigation for production
// ppr: true,
},
}),
);

if (env.VERCEL) {
config = withSentry(config);
config = withSentry(config);
}

export default config;
66 changes: 33 additions & 33 deletions apps/www/src/app/(app)/(landing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,42 @@ import { ZapIcon } from "lucide-react";

import { siteConfig } from "@repo/lightfast-config";
import { Icons } from "@repo/ui/components/icons";
import { Button } from "@repo/ui/components/ui/button";
import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
import { Button } from "@repo/ui/components/ui/button";

export default function HomePage() {
return (
<LightfastCustomGridBackground.Root
marginVertical={`15vh`}
marginHorizontal={`20vw`}
marginVerticalMobile={`20vh`}
marginHorizontalMobile={`20vw`}
>
<LightfastCustomGridBackground.Container className="p-4 sm:p-8">
<div className="relative h-full w-full">
<div className="absolute top-0 right-0 left-0">
<p className="text-foreground max-w-2xl text-2xl font-bold sm:text-3xl lg:text-4xl">
Crafting tomorrow's AI backbone with open-source infrastructure.
</p>
</div>
return (
<LightfastCustomGridBackground.Root
marginVertical={`15vh`}
marginHorizontal={`20vw`}
marginVerticalMobile={`20vh`}
marginHorizontalMobile={`20vw`}
>
<LightfastCustomGridBackground.Container className="p-4 sm:p-8">
<div className="relative h-full w-full">
<div className="absolute top-0 right-0 left-0">
<p className="text-foreground max-w-2xl text-2xl font-bold sm:text-3xl lg:text-4xl">
Crafting tomorrow's AI backbone with open-source infrastructure.
</p>
</div>

<div className="absolute bottom-0 left-0">
<Icons.logoShort className="text-primary w-10 h-6" />
</div>
<div className="absolute bottom-0 left-0">
<Icons.logoShort className="text-primary h-6 w-10" />
</div>

<div className="absolute right-0 bottom-0">
<Button asChild variant="outline">
<Link
className="text-foreground flex items-center"
href={siteConfig.links.chat.href}
>
<ZapIcon className="mr-1 h-4 w-4" />
Get Started
</Link>
</Button>
</div>
</div>
</LightfastCustomGridBackground.Container>
</LightfastCustomGridBackground.Root>
);
<div className="absolute right-0 bottom-0">
<Button asChild variant="outline">
<Link
className="text-foreground flex items-center"
href={siteConfig.links.chat.href}
>
<ZapIcon className="mr-1 h-4 w-4" />
Get Started
</Link>
</Button>
</div>
</div>
</LightfastCustomGridBackground.Container>
</LightfastCustomGridBackground.Root>
);
}
52 changes: 24 additions & 28 deletions apps/www/src/app/(health)/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { env } from "~/env";

export const runtime = "edge";

/**
* Health check endpoint for monitoring services like BetterStack.
*
*
* Authentication:
* - If HEALTH_CHECK_AUTH_TOKEN is set, requires Bearer token authentication
* - If not set, endpoint is public (for development/testing)
*
*
* BetterStack Configuration:
* 1. Set HTTP method: GET
* 2. Add header: Authorization: Bearer <your-token>
Expand All @@ -19,63 +20,58 @@ export const runtime = "edge";
export function GET(request: NextRequest) {
// Check if authentication is configured
const authToken = env.HEALTH_CHECK_AUTH_TOKEN;

if (authToken) {
// Extract bearer token from Authorization header
const authHeader = request.headers.get("authorization");

if (!authHeader) {
return NextResponse.json(
{ error: "Authorization required" },
{ status: 401 }
{ status: 401 },
);
}

// Check for Bearer token format
const bearerRegex = /^Bearer\s+(.+)$/i;
const bearerMatch = bearerRegex.exec(authHeader);
if (!bearerMatch) {
return NextResponse.json(
{ error: "Invalid authorization format" },
{ status: 401 }
{ status: 401 },
);
}

const providedToken = bearerMatch[1];

// Constant-time comparison to prevent timing attacks
const encoder = new TextEncoder();
const expectedBytes = encoder.encode(authToken);
const providedBytes = encoder.encode(providedToken);

if (expectedBytes.length !== providedBytes.length) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

let valid = true;
for (let i = 0; i < expectedBytes.length; i++) {
if (expectedBytes[i] !== providedBytes[i]) {
valid = false;
}
}

if (!valid) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}

// Log monitoring access for observability (optional)
const userAgent = request.headers.get("user-agent") ?? "unknown";
const isLikelyBetterStack = userAgent.toLowerCase().includes("betterstack") ||
userAgent.toLowerCase().includes("uptime") ||
userAgent.toLowerCase().includes("monitor");

const isLikelyBetterStack =
userAgent.toLowerCase().includes("betterstack") ||
userAgent.toLowerCase().includes("uptime") ||
userAgent.toLowerCase().includes("monitor");

// Return health status
const response = NextResponse.json(
{
Expand All @@ -100,12 +96,12 @@ export function GET(request: NextRequest) {
status: 200,
},
);

// Prevent caching of health check responses
response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate");
response.headers.set("Pragma", "no-cache");
response.headers.set("Expires", "0");

return response;
}

Expand All @@ -116,7 +112,7 @@ export function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
"Allow": "GET, OPTIONS",
Allow: "GET, OPTIONS",
"Cache-Control": "no-store",
},
});
Expand Down
7 changes: 5 additions & 2 deletions apps/www/src/app/global-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { captureException } from "@sentry/nextjs";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from "geist/font/sans";

import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
import {
ErrorCode,
LightfastErrorPage,
} from "@repo/ui/components/lightfast-error-page";
import { Button } from "@repo/ui/components/ui/button";
import { cn } from "@repo/ui/lib/utils";
import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
import { LightfastErrorPage, ErrorCode } from "@repo/ui/components/lightfast-error-page";

interface GlobalErrorProperties {
readonly error: NextError & { digest?: string };
Expand Down
Loading