Skip to content

Commit 96b1788

Browse files
MagnunAVFAzioneniciojcbsfilhojotanarciso
authored
feat: Nextjs routing and edge runtime (#226)
Co-authored-by: enicio <jessandro42@gmail.com> Co-authored-by: José Filho <jc.bsfilho@gmail.com> Co-authored-by: jotanarciso <joaoznarciso@gmail.com>
1 parent e3e0857 commit 96b1788

File tree

442 files changed

+51400
-422
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

442 files changed

+51400
-422
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
/.yarn
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
.pnpm-debug.log*
28+
29+
# local env files
30+
.env*
31+
!.env*.example
32+
33+
# vercel
34+
.vercel
35+
36+
# typescript
37+
*.tsbuildinfo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
save-exact=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.next
2+
pnpm-lock.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3+
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4+
5+
// List of extensions which should be recommended for users of this workspace.
6+
"recommendations": ["bradlc.vscode-tailwindcss", "esbenp.prettier-vscode"],
7+
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
8+
"unwantedRecommendations": []
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"typescript.tsdk": "node_modules/.pnpm/typescript@4.9.4/node_modules/typescript/lib",
3+
"typescript.enablePromptUseWorkspaceTsdk": true
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Next.js App Router Playground
2+
3+
Next.js recently introduced the App Router with support for:
4+
5+
- **Layouts:** Easily share UI while preserving state and avoiding re-renders.
6+
- **Server Components:** Making server-first the default for the most dynamic applications.
7+
- **Streaming:** Display instant loading states and stream in updates.
8+
- **Suspense for Data Fetching:** `async`/`await` support and the `use` hook for component-level fetching.
9+
10+
The App Router can coexist with the existing `pages` directory for incremental adoption. While you **don't need to use the App Router** when upgrading to Next.js 13, we're laying the foundations to build complex interfaces while shipping less JavaScript.
11+
12+
## Running Locally
13+
14+
1. Install dependencies:
15+
16+
```sh
17+
npm i
18+
```
19+
20+
2. Build with vulcan:
21+
```
22+
DEBUG=true NODE_OPTIONS="--max-old-space-size=4096" npx edge-functions@latest build --preset next --mode compute
23+
```
24+
25+
3. Start the dev server:
26+
27+
```
28+
DEBUG=true NODE_OPTIONS="--max-old-space-size=4096" npx edge-functions@latest dev
29+
```
30+
31+
## Documentation
32+
33+
https://nextjs.org/docs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type Category = {
2+
name: string;
3+
slug: string;
4+
count: number;
5+
parent: string | null;
6+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { notFound } from 'next/navigation';
2+
import type { Category } from './category';
3+
4+
// `server-only` guarantees any modules that import code in file
5+
// will never run on the client. Even though this particular api
6+
// doesn't currently use sensitive environment variables, it's
7+
// good practise to add `server-only` preemptively.
8+
import 'server-only';
9+
10+
export async function getCategories({ parent }: { parent?: string } = {}) {
11+
const res = await fetch(
12+
`https://app-router-api.vercel.app/api/categories${
13+
parent ? `?parent=${parent}` : ''
14+
}`,
15+
);
16+
17+
if (!res.ok) {
18+
// Render the closest `error.js` Error Boundary
19+
throw new Error('Something went wrong!');
20+
}
21+
22+
const categories = (await res.json()) as Category[];
23+
24+
if (categories.length === 0) {
25+
// Render the closest `not-found.js` Error Boundary
26+
notFound();
27+
}
28+
29+
return categories;
30+
}
31+
32+
export async function getCategory({ slug }: { slug: string }) {
33+
const res = await fetch(
34+
`https://app-router-api.vercel.app/api/categories${
35+
slug ? `?slug=${slug}` : ''
36+
}`,
37+
);
38+
39+
if (!res.ok) {
40+
// Render the closest `error.js` Error Boundary
41+
throw new Error('Something went wrong!');
42+
}
43+
44+
const category = (await res.json()) as Category;
45+
46+
if (!category) {
47+
// Render the closest `not-found.js` Error Boundary
48+
notFound();
49+
}
50+
51+
return category;
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export type Product = {
2+
id: string;
3+
stock: number;
4+
rating: number;
5+
name: string;
6+
description: string;
7+
price: Price;
8+
isBestSeller: boolean;
9+
leadTime: number;
10+
image?: string;
11+
imageBlur?: string;
12+
discount?: Discount;
13+
usedPrice?: UsedPrice;
14+
};
15+
16+
type Price = {
17+
amount: number;
18+
currency: Currency;
19+
scale: number;
20+
};
21+
22+
type Currency = {
23+
code: string;
24+
base: number;
25+
exponent: number;
26+
};
27+
28+
type Discount = {
29+
percent: number;
30+
expires?: number;
31+
};
32+
33+
type UsedPrice = {
34+
amount: number;
35+
currency: Currency;
36+
scale: number;
37+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { notFound } from 'next/navigation';
2+
import type { Review } from './review';
3+
4+
// `server-only` guarantees any modules that import code in file
5+
// will never run on the client. Even though this particular api
6+
// doesn't currently use sensitive environment variables, it's
7+
// good practise to add `server-only` preemptively.
8+
import 'server-only';
9+
10+
export async function getReviews() {
11+
const res = await fetch(`https://app-router-api.vercel.app/api/reviews`);
12+
13+
if (!res.ok) {
14+
// Render the closest `error.js` Error Boundary
15+
throw new Error('Something went wrong!');
16+
}
17+
18+
const reviews = (await res.json()) as Review[];
19+
20+
if (reviews.length === 0) {
21+
// Render the closest `not-found.js` Error Boundary
22+
notFound();
23+
}
24+
25+
return reviews;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type Review = {
2+
id: string;
3+
name: string;
4+
rating: number;
5+
text: string;
6+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { getCategory } from '#/app/api/categories/getCategories';
2+
import { Boundary } from '#/ui/boundary';
3+
import { notFound } from 'next/navigation';
4+
import { Counter } from '../../context-click-counter';
5+
6+
export default async function Page({
7+
params,
8+
}: {
9+
params: { categorySlug: string; subCategorySlug: string };
10+
}) {
11+
const category = await getCategory({ slug: params.subCategorySlug });
12+
13+
return (
14+
<Boundary labels={['Page [Server Component]']} animateRerendering={false}>
15+
<div className="space-y-8">
16+
<h1 className="text-xl font-medium text-gray-400/80">
17+
{category.name}
18+
</h1>
19+
20+
<Counter />
21+
</div>
22+
</Boundary>
23+
);
24+
}
25+
26+
export const runtime = "experimental-edge";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { getCategories, getCategory } from '#/app/api/categories/getCategories';
2+
import { Boundary } from '#/ui/boundary';
3+
import { TabGroup } from '#/ui/tab-group';
4+
import { Counter } from '../context-click-counter';
5+
6+
export default async function Layout({
7+
children,
8+
params,
9+
}: {
10+
children: React.ReactNode;
11+
params: { categorySlug: string };
12+
}) {
13+
const category = await getCategory({ slug: params.categorySlug });
14+
const categories = await getCategories({ parent: params.categorySlug });
15+
16+
return (
17+
<Boundary labels={['Layout [Server Component]']} animateRerendering={false}>
18+
<div className="space-y-9">
19+
<TabGroup
20+
path={`/context/${category.slug}`}
21+
items={[
22+
{
23+
text: 'All',
24+
},
25+
...categories.map((x) => ({
26+
text: x.name,
27+
slug: x.slug,
28+
})),
29+
]}
30+
/>
31+
<Counter />
32+
<div>{children}</div>
33+
</div>
34+
</Boundary>
35+
);
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { getCategory } from '#/app/api/categories/getCategories';
2+
import { Boundary } from '#/ui/boundary';
3+
import { Counter } from '../context-click-counter';
4+
5+
export default async function Page({
6+
params,
7+
}: {
8+
params: { categorySlug: string };
9+
}) {
10+
const category = await getCategory({ slug: params.categorySlug });
11+
12+
return (
13+
<Boundary labels={['Page [Server Component]']} animateRerendering={false}>
14+
<div className="space-y-8">
15+
<h1 className="text-xl font-medium text-gray-400/80">
16+
All {category.name}
17+
</h1>
18+
19+
<Counter />
20+
</div>
21+
</Boundary>
22+
);
23+
}
24+
25+
export const runtime = "experimental-edge";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client';
2+
3+
import { useCounter } from './counter-context';
4+
import React from 'react';
5+
import { Boundary } from '#/ui/boundary';
6+
7+
const ContextClickCounter = () => {
8+
const [count, setCount] = useCounter();
9+
10+
return (
11+
<Boundary
12+
labels={['Counter Context [Client Component]']}
13+
color="blue"
14+
size="small"
15+
animateRerendering={false}
16+
>
17+
<button
18+
onClick={() => setCount(count + 1)}
19+
className="rounded-lg bg-gray-700 px-3 py-1 text-sm font-medium tabular-nums text-gray-100 hover:bg-gray-500 hover:text-white"
20+
>
21+
{count} Clicks
22+
</button>
23+
</Boundary>
24+
);
25+
};
26+
27+
export const Counter = () => {
28+
const [count] = useCounter();
29+
30+
return (
31+
<Boundary
32+
labels={['Counter Context [Client Component]']}
33+
color="blue"
34+
size="small"
35+
animateRerendering={false}
36+
>
37+
<div className="span text-xl font-bold text-white">
38+
<span className="tabular-nums">{count}</span> Clicks
39+
</div>
40+
</Boundary>
41+
);
42+
};
43+
44+
export default ContextClickCounter;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client';
2+
3+
import React from 'react';
4+
5+
const CounterContext = React.createContext<
6+
[number, React.Dispatch<React.SetStateAction<number>>] | undefined
7+
>(undefined);
8+
9+
export function CounterProvider({ children }: { children: React.ReactNode }) {
10+
const [count, setCount] = React.useState(0);
11+
return (
12+
<CounterContext.Provider value={[count, setCount]}>
13+
{children}
14+
</CounterContext.Provider>
15+
);
16+
}
17+
18+
export function useCounter() {
19+
const context = React.useContext(CounterContext);
20+
if (context === undefined) {
21+
throw new Error('useCounter must be used within a CounterProvider');
22+
}
23+
return context;
24+
}

0 commit comments

Comments
 (0)