Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"drizzle-kit": "1.0.0-rc.4",
"drizzle-orm": "1.0.0-rc.4",
"hono": "^4.12.31",
"next-intl": "^4.13.3",
"next-intl": "^4.13.7",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"ws": "^8.21.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/vitnode.api.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { google } from "@ai-sdk/google";
import { blogApiPlugin } from "@vitnode/blog/config.api";
import { coreRelations } from "@vitnode/core/database/relations";
// import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local";
import { buildApiConfig } from "@vitnode/core/vitnode.config";
import { exampleApiPlugin } from "@vitnode/example/config.api";
Expand All @@ -8,7 +9,6 @@ import { NodemailerEmailAdapter } from "@vitnode/nodemailer";
// import { S3StorageAdapter } from "@vitnode/s3";
import { SupabaseStorageAdapter } from "@vitnode/supabase-storage";
import { config } from "dotenv";
import { coreRelations } from "@vitnode/core/database/relations";
import { drizzle } from "drizzle-orm/postgres-js";

config({
Expand Down
24 changes: 22 additions & 2 deletions apps/docs/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,36 @@ import { vitNodeNextConfig } from "@vitnode/core/config/next.config";
import { createMDX } from "fumadocs-mdx/next";
import type { NextConfig } from "next";

import { i18n } from "./src/i18n";

const withMDX = createMDX();

const withBundleAnalyzer = nextAnalyzer({
enabled: process.env.ANALYZE === "true",
});

/**
* `/docs` has no section of its own, so it forwards to the first one.
*
* Handled here rather than in the page: the page reads its slug inside a
* `<Suspense>` boundary now, so a redirect decided there would stream after a
* 200 instead of being a real HTTP redirect. `localePrefix` is `as-needed`, so
* the default locale is unprefixed and every other locale gets its own entry.
*/
const docsIndexRedirects = [
"",
...i18n.locales
.filter(locale => locale.code !== i18n.defaultLocale)
.map(locale => `/${locale.code}`),
].map(prefix => ({
source: `${prefix}/docs`,
destination: `${prefix}/docs/dev`,
permanent: false,
}));

const nextConfig: NextConfig = {
reactCompiler: true,
// cacheComponents: true,
// partialPrefetching: true,
redirects: async () => docsIndexRedirects,
};

export default withBundleAnalyzer(withMDX(vitNodeNextConfig(nextConfig)));
6 changes: 3 additions & 3 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"hono": "^4.12.31",
"lucide-react": "^1.25.0",
"motion": "^12.42.2",
"next": "16.3.0-preview.9",
"next-intl": "^4.13.3",
"next": "16.3.1",
"next-intl": "^4.13.7",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-hook-form": "^7.82.0",
Expand All @@ -50,7 +50,7 @@
"sonner": "^2.0.7"
},
"devDependencies": {
"@next/bundle-analyzer": "^16.2.11",
"@next/bundle-analyzer": "^16.3.1",
"@playwright/test": "^1.61.1",
"@react-email/ui": "^6.9.0",
"@tailwindcss/postcss": "^4.3.3",
Expand Down
65 changes: 53 additions & 12 deletions apps/docs/src/app/[locale]/(docs)/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import type { Metadata } from "next";

import { redirect } from "@vitnode/core/lib/navigation";
import { Skeleton } from "@vitnode/core/components/ui/skeleton";
import { getBreadcrumbItems } from "fumadocs-core/breadcrumb";
import { Step, Steps } from "fumadocs-ui/components/steps";
import defaultMdxComponents from "fumadocs-ui/mdx";
import { DocsBody, DocsPage } from "fumadocs-ui/page";
import { notFound } from "next/navigation";
import { Suspense } from "react";

import { Preview } from "@/components/fumadocs/preview";
import { source } from "@/lib/source";

import { ViewOptions } from "./page.client";

export default async function Page(
props: PageProps<"/[locale]/docs/[[...slug]]">,
) {
const params = await props.params;
if (!params.slug) {
await redirect("/docs/dev");
}
const page = source.getPage(params.slug);
/**
* Everything on a docs page comes from the slug - title, description, table of
* contents and body - so this is where `params` is read.
*
* Reading it here instead of in the page body is what keeps the fumadocs
* sidebar and nav in the App Shell: they are identical for every docs URL, so
* one prefetch is shared across every link and only this region streams.
*/
async function DocsArticle({
params,
}: Pick<PageProps<"/[locale]/docs/[[...slug]]">, "params">) {
const { slug } = await params;
const page = source.getPage(slug);
if (!page) notFound();
const MDX = page.data.body;

Expand Down Expand Up @@ -53,9 +59,44 @@ export default async function Page(
);
}

// export function generateStaticParams() {
// return source.generateParams();
// }
/**
* Mirrors the article's own layout - heading, description, then body copy - so
* the swap to real content doesn't shift the page.
*/
const DocsArticleSkeleton = () => (
<DocsPage tableOfContent={{ style: "clerk", single: false }} toc={[]}>
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-4">
<Skeleton className="h-10 w-2/3" />
<Skeleton className="h-9 w-28" />
</div>
<Skeleton className="h-6 w-full max-w-lg" />
</div>

<DocsBody>
<div className="space-y-3">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-4/5" />
<Skeleton className="mt-8 h-6 w-1/3" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-11/12" />
</div>
</DocsBody>
</DocsPage>
);

export default function Page(props: PageProps<"/[locale]/docs/[[...slug]]">) {
return (
<Suspense fallback={<DocsArticleSkeleton />}>
<DocsArticle params={props.params} />
</Suspense>
);
}

export function generateStaticParams() {
return source.generateParams();
}

export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const MyFilesTableView = dynamic(async () =>
})),
);

export const instant = false;

export const generateMetadata = async () => {
const t = await getTranslations("core.files");

Expand Down Expand Up @@ -41,7 +43,7 @@ export default async function Page(
<div className="container mx-auto space-y-6 p-4">
<HeaderContent desc={t("desc")} h1={t("title")} />

<React.Suspense fallback={<DataTableSkeleton columns={7} />}>
<React.Suspense fallback={<DataTableSkeleton columns={7} toolbar />}>
<MyFilesTableView {...props} />
</React.Suspense>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ import { getTranslations } from "next-intl/server";

import { SignInView } from "@vitnode/core/views/auth/sign-in/sign-in-view";

export const generateMetadata = async ({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> => {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "core.global" });
export const generateMetadata = async (): Promise<Metadata> => {
const t = await getTranslations("core.global");

return {
title: t("login"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,8 @@ import { getTranslations } from "next-intl/server";

import { PasswordResetView } from "@vitnode/core/views/auth/password-reset/password-reset-view";

export const generateMetadata = async ({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> => {
const { locale } = await params;
const t = await getTranslations({
locale,
namespace: "core.auth.reset_password",
});
export const generateMetadata = async (): Promise<Metadata> => {
const t = await getTranslations("core.auth.reset_password");

return {
title: t("title"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CallbackSSOView } from "@vitnode/core/views/auth/sso/callback/callback-sso-view";

export const instant = false;

export default async function Page({
params,
searchParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@ import { getTranslations } from "next-intl/server";

import { SignUpView } from "@vitnode/core/views/auth/sign-up/sign-up-view";

export const generateMetadata = async ({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> => {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "core.global" });
export const generateMetadata = async (): Promise<Metadata> => {
const t = await getTranslations("core.global");

return {
title: t("register"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const metadata: Metadata = {
},
};

// instant = false: kept on purpose. Every route under this layout is gated on a
// signed-in user in `LayoutSettings`, which calls `notFound()` when there isn't
// one. Moving that read inside `<Suspense>` would turn a real 404 into a 200
// shell that later swaps to a not-found body, so the gate stays where it is and
// the segment is allowed to block.
export const instant = false;

export default function Layout(
props: React.ComponentProps<typeof LayoutSettings>,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const instant = false;

export default function BreadcrumbSlot() {
return null;
}
2 changes: 2 additions & 0 deletions apps/docs/src/app/[locale]/(main)/[...rest]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { notFound } from "next/navigation";

export const instant = false;

export default function RestPage() {
notFound();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { Metadata } from "next/dist/types";

import React from "react";

import {
ContentAdminView,
type ContentAdminViewProps,
ContentAdminViewSkeleton,
getContentLabels,
resolveContentType,
} from "@vitnode/core/views/admin/views/content/content-admin-view";
Expand All @@ -19,5 +22,9 @@ export const generateMetadata = async ({
};

export default function ContentAdminPage(props: ContentAdminViewProps) {
return <ContentAdminView {...props} />;
return (
<React.Suspense fallback={<ContentAdminViewSkeleton />}>
<ContentAdminView {...props} />
</React.Suspense>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default async function Page(
<div className="p-4">
<HeaderContent desc={t("desc")} h1={t("title")} />

<React.Suspense fallback={<DataTableSkeleton columns={7} />}>
<React.Suspense fallback={<DataTableSkeleton columns={7} toolbar />}>
<QueueTableView {...props} />
</React.Suspense>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default async function Page(
<div className="p-4">
<HeaderContent desc={t("desc")} h1={t("title")} />

<React.Suspense fallback={<DataTableSkeleton columns={8} />}>
<React.Suspense fallback={<DataTableSkeleton columns={8} toolbar />}>
<FilesTableView {...props} />
</React.Suspense>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default async function Page(
{canCreate && <CreateUserAdmin />}
</HeaderContent>

<React.Suspense fallback={<DataTableSkeleton columns={2} />}>
<React.Suspense fallback={<DataTableSkeleton columns={2} toolbar />}>
<UsersAdminView {...props} />
</React.Suspense>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default async function Page(
{canCreate && <ActionsRolesAdmin />}
</HeaderContent>

<React.Suspense fallback={<DataTableSkeleton columns={2} />}>
<React.Suspense fallback={<DataTableSkeleton columns={2} toolbar />}>
<RolesAdminView {...props} />
</React.Suspense>
</div>
Expand Down
Loading
Loading