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
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ export default async function Page({

// Note: you could fetch breadcrumb data based on params here
// e.g. title, slug, children/siblings (for dropdowns)
const items = [
...all.map((param, index) => ({
text: param,
// build cumulative path by joining all segments up to current index
href: `/${all.slice(0, index + 1).join('/')}`,
})),
];
const items = all.map((param, index) => ({
text: param,
// build cumulative path by joining all segments up to current index
href: `/${all.slice(0, index + 1).join('/')}`,
}));

return <Breadcrumbs items={items} />;
}
16 changes: 9 additions & 7 deletions examples/app-router-playground/app/api/og/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export async function GET(req: NextRequest): Promise<Response> {
}
}

function LightSvg(): ReactElement {
return (
const LIGHT_SVG: ReactElement = (
<svg
fill="none"
height="441"
Expand Down Expand Up @@ -229,7 +228,7 @@ function LightSvg(): ReactElement {
height="88.5"
rx="13.8"
stroke="url(#paint1_radial_5_3)"
stroke-opacity="0.15"
strokeOpacity="0.15"
strokeWidth="1.5"
transform="rotate(-180 465.75 197.75)"
width="88.5"
Expand All @@ -240,7 +239,7 @@ function LightSvg(): ReactElement {
height="88.5"
rx="13.8"
stroke="url(#paint2_linear_5_3)"
stroke-opacity="0.5"
strokeOpacity="0.5"
strokeWidth="1.5"
transform="rotate(-180 465.75 197.75)"
width="88.5"
Expand Down Expand Up @@ -368,7 +367,10 @@ function LightSvg(): ReactElement {
</clipPath>
</defs>
</svg>
);
);

function LightSvg(): ReactElement {
return LIGHT_SVG;
}

function DarkSvg(): ReactElement {
Expand Down Expand Up @@ -534,7 +536,7 @@ function DarkSvg(): ReactElement {
height="88.0875"
rx="13.5937"
stroke="url(#paint1_radial_1_4)"
stroke-opacity="0.15"
strokeOpacity="0.15"
strokeWidth="1.0875"
width="88.0875"
x="377.456"
Expand All @@ -544,7 +546,7 @@ function DarkSvg(): ReactElement {
height="88.0875"
rx="13.5937"
stroke="url(#paint2_linear_1_4)"
stroke-opacity="0.5"
strokeOpacity="0.5"
strokeWidth="1.0875"
width="88.0875"
x="377.456"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ContextClickCounter = () => {
animateRerendering={false}
>
<button
onClick={() => setCount(count + 1)}
onClick={() => setCount((prev) => prev + 1)}
className="rounded-lg bg-gray-700 px-3 py-1 text-sm font-medium text-gray-100 tabular-nums hover:bg-gray-500 hover:text-white"
>
{count} Clicks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ export default async function Page({
// DEMO:
// This page would normally be prerendered at build time because it doesn't use dynamic APIs.
// That means the loading state wouldn't show. To force one:
// 1. We indicate that we require a user Request before continuing:
await connection();
// 2. Add an artificial delay to make the loading state more noticeable:
await new Promise((resolve) => setTimeout(resolve, 1000));

const { category: categorySlug } = await params;
// 1. We indicate that we require a user Request before continuing.
// 2. Add an artificial delay to make the loading state more noticeable.
const [{ category: categorySlug }] = await Promise.all([
params,
connection(),
new Promise((resolve) => setTimeout(resolve, 1000)),
]);
const category = db.category.find({ where: { slug: categorySlug } });

if (!category) {
Expand Down
13 changes: 7 additions & 6 deletions examples/app-router-playground/app/loading/[section]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ export default async function Page({
// DEMO:
// This page would normally be prerendered at build time because it doesn't use dynamic APIs.
// That means the loading state wouldn't show. To force one:
// 1. We indicate that we require a user Request before continuing:
await connection();
// 2. Add an artificial delay to make the loading state more noticeable:
await new Promise((resolve) => setTimeout(resolve, 1000));

const { section: sectionSlug } = await params;
// 1. We indicate that we require a user Request before continuing.
// 2. Add an artificial delay to make the loading state more noticeable.
const [{ section: sectionSlug }] = await Promise.all([
params,
connection(),
new Promise((resolve) => setTimeout(resolve, 1000)),
]);
const section = db.section.find({ where: { slug: sectionSlug } });
if (!section) {
notFound();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import db from '#/lib/db';
import { Boundary } from '#/ui/boundary';
import { ProductCard } from '#/ui/product-card';
import { cacheTag } from 'next/cache';
import Link from 'next/link';
import SessionButton from './session-button';
import ProductLink from './product-link';

Expand Down Expand Up @@ -69,9 +68,9 @@ export function ProductListSkeleton() {
Available Products
</h1>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{[1, 2, 3, 4, 5, 6].map((i) => (
{[1, 2, 3, 4, 5, 6].map((slot) => (
<div
key={i}
key={`product-skeleton-${slot}`}
className="h-48 animate-pulse rounded-lg bg-gray-800"
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export function RecommendationsSkeleton() {
<div className="flex flex-col gap-4">
<h2 className="text-lg font-semibold text-gray-300">Recommendations</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
{[1, 2, 3].map((slot) => (
<div
key={i}
key={`recommendation-skeleton-${slot}`}
className="h-48 animate-pulse rounded-lg bg-gray-800"
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export default function Loading() {
Recommendations
</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
{[1, 2, 3].map((slot) => (
<div
key={i}
key={`recommendation-skeleton-${slot}`}
className="h-48 animate-pulse rounded-lg bg-gray-800"
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ function RecommendationsSkeleton() {
<div className="flex flex-col gap-4">
<h2 className="text-lg font-semibold text-gray-300">Recommendations</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
{[1, 2, 3].map((slot) => (
<div
key={i}
key={`recommendation-skeleton-${slot}`}
className="h-48 animate-pulse rounded-lg bg-gray-800"
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ function RecommendationsSkeleton() {
<div className="flex flex-col gap-4">
<h2 className="text-lg font-semibold text-gray-300">Recommendations</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
{[1, 2, 3].map((slot) => (
<div
key={i}
key={`recommendation-skeleton-${slot}`}
className="h-48 animate-pulse rounded-lg bg-gray-800"
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ export default async function Page({
// DEMO:
// This page would normally be prerendered at build time because it doesn't use dynamic APIs.
// That means the loading state wouldn't show. To force one:
// 1. We indicate that we require a user Request before continuing:
await connection();
// 2. Add an artificial delay to make the loading state more noticeable:
await new Promise((resolve) => setTimeout(resolve, 1000));

const { category: categorySlug } = await params;
// 1. We indicate that we require a user Request before continuing.
// 2. Add an artificial delay to make the loading state more noticeable.
const [{ category: categorySlug }] = await Promise.all([
params,
connection(),
new Promise((resolve) => setTimeout(resolve, 1000)),
]);
const category = db.category.find({ where: { slug: categorySlug } });
if (!category) {
notFound();
Expand Down
2 changes: 1 addition & 1 deletion examples/app-router-playground/ui/click-counter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function ClickCounter() {

return (
<button
onClick={() => setCount(count + 1)}
onClick={() => setCount((prev) => prev + 1)}
className="rounded-md bg-gray-700 px-3 py-1 text-sm font-semibold whitespace-nowrap text-gray-100 tabular-nums hover:bg-gray-500 hover:text-white"
>
{count} Clicks
Expand Down
43 changes: 35 additions & 8 deletions examples/app-router-playground/ui/codehike.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,42 @@ import {
} from 'codehike/code';
import { MDXProps } from 'mdx/types';
import Image from 'next/image';
import { JSX } from 'react';
import { type ComponentType, JSX } from 'react';
import { z } from 'zod';

const Schema = Block.extend({ col: z.array(Block) });
const EMPTY_COMPONENTS: Record<string, ComponentType<any>> = {};

function getGridColumnKey(col: { children?: unknown }) {
const children = Array.isArray(col.children)
? col.children
: col.children === undefined
? []
: [col.children];

const childKeys = children
.map((child) => {
if (typeof child === 'object' && child !== null && 'key' in child) {
const childKey = (child as { key: string | null }).key;
return childKey ? String(childKey) : '';
}

return '';
})
.filter(Boolean);

return childKeys.join('|') || `column-${children.length}`;
}

export function Grid(props: unknown) {
const data = parseProps(props, Schema);

return (
<div className="my-5 grid grid-cols-1 gap-6 lg:grid-cols-2 [&:first-child]:mt-0 [&:last-child]:mb-0">
{data.col.map((col, index) => (
{data.col.map((col) => (
<div
className="[&>:first-child]:mt-0 [&>:last-child]:mb-0"
key={index}
key={getGridColumnKey(col)}
>
{col.children}
</div>
Expand Down Expand Up @@ -94,7 +116,7 @@ const mark: AnnotationHandler = {
async function MyCode({ codeblock }: { codeblock: RawCode }) {
'use cache';
const highlighted = await highlight(codeblock, 'github-dark');
const { background, ...style } = highlighted.style;
const { background: _background, ...style } = highlighted.style;
return (
<Boundary
label={highlighted.meta}
Expand Down Expand Up @@ -124,23 +146,28 @@ async function MyInlineCode({ codeblock }: { codeblock: RawCode }) {

export function Mdx({
source: MdxComponent,
components = {},
components,
collapsed,
className,
...props
}: {
source: (props: MDXProps) => JSX.Element;
components?: Record<string, React.ComponentType<any>>;
components?: Record<string, ComponentType<any>>;
collapsed?: boolean;
className?: string;
}) {
const resolvedComponents = components ?? EMPTY_COMPONENTS;

return (
<Prose
collapsed={collapsed}
className="prose prose-sm prose-invert prose-h1:font-medium prose-h2:font-medium prose-h3:font-medium prose-h4:font-medium prose-h5:font-medium prose-h6:font-medium prose-pre:mt-0 prose-pre:mb-0 prose-pre:rounded-none prose-pre:bg-transparent max-w-none"
className={clsx(
'prose prose-sm prose-invert prose-h1:font-medium prose-h2:font-medium prose-h3:font-medium prose-h4:font-medium prose-h5:font-medium prose-h6:font-medium prose-pre:mt-0 prose-pre:mb-0 prose-pre:rounded-none prose-pre:bg-transparent max-w-none',
className,
)}
>
<MdxComponent
components={{ MyCode, MyInlineCode, Image, ...components }}
components={{ MyCode, MyInlineCode, Image, ...resolvedComponents }}
{...props}
/>
</Prose>
Expand Down
13 changes: 9 additions & 4 deletions examples/app-router-playground/ui/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,24 @@ export function SkeletonText({
}) {
const words = Array.from({ length: count }, (_, i) => {
const seed = _seed + count + i;
const width =
Math.floor(random(seed) * (maxLength - minLength + 1)) + minLength;

return Math.floor(random(seed) * (maxLength - minLength + 1)) + minLength;
return {
key: `skeleton-word-${seed}`,
width,
};
});

return (
<div
className={clsx('flex flex-wrap gap-x-[1ch] gap-y-[0.65em]', className)}
>
{words.map((width, i) => (
{words.map((word) => (
<div
key={i}
key={word.key}
className="h-[0.6em] rounded-full bg-current"
style={{ width: `${width}ch` }}
style={{ width: `${word.width}ch` }}
/>
))}
</div>
Expand Down
23 changes: 23 additions & 0 deletions examples/app-router-playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@ export default defineConfig({
childEnvironments: ["ssr"],
},
}),

// Vite 8 (rolldown) warns on rollup-only keys injected by vinext.
// Strip those keys from the resolved client build config.
{
name: "strip-rolldown-incompatible-rollup-options",
configResolved(config) {
const clientRollupOptions = (config as any).environments?.client?.build?.rollupOptions;
if (!clientRollupOptions) return;

if (
clientRollupOptions.treeshake &&
typeof clientRollupOptions.treeshake === "object" &&
!Array.isArray(clientRollupOptions.treeshake)
) {
delete clientRollupOptions.treeshake.preset;
}

const output = clientRollupOptions.output;
if (output && typeof output === "object" && !Array.isArray(output)) {
delete output.experimentalMinChunkSize;
}
},
},
],

resolve: {
Expand Down
4 changes: 4 additions & 0 deletions examples/app-router-playground/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import { handleImageOptimization } from "vinext/server/image-optimization";
import handler from "vinext/server/app-router-entry";

interface Fetcher {
fetch(request: Request): Promise<Response>;
}

interface Env {
ASSETS: Fetcher;
IMAGES: {
Expand Down
Loading