diff --git a/examples/e2e/app-pages-router/.env b/examples/e2e/app-pages-router/.env new file mode 100644 index 00000000..5d8e1786 --- /dev/null +++ b/examples/e2e/app-pages-router/.env @@ -0,0 +1 @@ +SOME_ENV_VAR=foo \ No newline at end of file diff --git a/examples/e2e/app-pages-router/.gitignore b/examples/e2e/app-pages-router/.gitignore new file mode 100644 index 00000000..28379183 --- /dev/null +++ b/examples/e2e/app-pages-router/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +.open-next +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/examples/e2e/app-pages-router/CHANGELOG.md b/examples/e2e/app-pages-router/CHANGELOG.md new file mode 100644 index 00000000..bb60e202 --- /dev/null +++ b/examples/e2e/app-pages-router/CHANGELOG.md @@ -0,0 +1,43 @@ +# app-pages-router + +## 0.1.6 + +### Patch Changes + +- Updated dependencies [[`7eda030388880d8ad25d3f4692e24bac31b7ec4f`](https://github.com/opennextjs/opennextjs-aws/commit/7eda030388880d8ad25d3f4692e24bac31b7ec4f), [`e5678b39e0f3c21d3e30d08a89f5cb0acdd3d050`](https://github.com/opennextjs/opennextjs-aws/commit/e5678b39e0f3c21d3e30d08a89f5cb0acdd3d050), [`1981a47dd3dbc77066d2bf5cad5d5d406fecb010`](https://github.com/opennextjs/opennextjs-aws/commit/1981a47dd3dbc77066d2bf5cad5d5d406fecb010), [`b4ad0f0e0f6069ca87f3b72c23d655cedebc86e5`](https://github.com/opennextjs/opennextjs-aws/commit/b4ad0f0e0f6069ca87f3b72c23d655cedebc86e5)]: + - @opennextjs/aws@3.4.1 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies [[`e8f6dc8c7a421e316f5fbed03dcb82bb860c5249`](https://github.com/opennextjs/opennextjs-aws/commit/e8f6dc8c7a421e316f5fbed03dcb82bb860c5249), [`00ce837cb98e5902316f26163c9fb927058f956c`](https://github.com/opennextjs/opennextjs-aws/commit/00ce837cb98e5902316f26163c9fb927058f956c), [`d1cea5601943afaa197d56f931593234f351c441`](https://github.com/opennextjs/opennextjs-aws/commit/d1cea5601943afaa197d56f931593234f351c441), [`6884444cb929ab60c074c918954d24100f4e9668`](https://github.com/opennextjs/opennextjs-aws/commit/6884444cb929ab60c074c918954d24100f4e9668), [`86916bfd9246a63f321352bb11346eeb0ca3f6da`](https://github.com/opennextjs/opennextjs-aws/commit/86916bfd9246a63f321352bb11346eeb0ca3f6da), [`eaa9ef8daf2fc454139c77ce0e100cb48da15561`](https://github.com/opennextjs/opennextjs-aws/commit/eaa9ef8daf2fc454139c77ce0e100cb48da15561), [`ae7fb9c5d24ecf3eeb99682aa34bcbe0adb45675`](https://github.com/opennextjs/opennextjs-aws/commit/ae7fb9c5d24ecf3eeb99682aa34bcbe0adb45675), [`e708ec4d9f4c87d3249a01382482347d295ed28a`](https://github.com/opennextjs/opennextjs-aws/commit/e708ec4d9f4c87d3249a01382482347d295ed28a)]: + - @opennextjs/aws@3.4.0 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies [[`9595714ac23e5f131b879d04d5cfb2a5d11bdbdd`](https://github.com/opennextjs/opennextjs-aws/commit/9595714ac23e5f131b879d04d5cfb2a5d11bdbdd), [`4e88b47935523de1d15da067b56105bd6be91e47`](https://github.com/opennextjs/opennextjs-aws/commit/4e88b47935523de1d15da067b56105bd6be91e47), [`7140ca56e1e88d7a7cae327eceb3ef8c2fde2a1e`](https://github.com/opennextjs/opennextjs-aws/commit/7140ca56e1e88d7a7cae327eceb3ef8c2fde2a1e)]: + - @opennextjs/aws@3.3.1 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies [[`4d328e3fc306b878e9497986baa65bfd1d4de66a`](https://github.com/opennextjs/opennextjs-aws/commit/4d328e3fc306b878e9497986baa65bfd1d4de66a), [`2b2a48b70ae95b5e600ac2e4b7f2df8702c5c26e`](https://github.com/opennextjs/opennextjs-aws/commit/2b2a48b70ae95b5e600ac2e4b7f2df8702c5c26e), [`f685ddea8f8a5c82591dc02713aff7138f2d9896`](https://github.com/opennextjs/opennextjs-aws/commit/f685ddea8f8a5c82591dc02713aff7138f2d9896), [`ef1fe48d570863266c271e5dedaf02b943849ded`](https://github.com/opennextjs/opennextjs-aws/commit/ef1fe48d570863266c271e5dedaf02b943849ded), [`8ab921f8b5bd40c7ba109ccef3e59a6c24283fb2`](https://github.com/opennextjs/opennextjs-aws/commit/8ab921f8b5bd40c7ba109ccef3e59a6c24283fb2), [`2202f36ce0f87357b249bd127cdd5e84d6deffd3`](https://github.com/opennextjs/opennextjs-aws/commit/2202f36ce0f87357b249bd127cdd5e84d6deffd3), [`44392ba82990d43e16a614113d9e7d8e257e5bdd`](https://github.com/opennextjs/opennextjs-aws/commit/44392ba82990d43e16a614113d9e7d8e257e5bdd), [`4dea7ea2f5ffd1848e51502c88d2efcc1896bb8c`](https://github.com/opennextjs/opennextjs-aws/commit/4dea7ea2f5ffd1848e51502c88d2efcc1896bb8c), [`0ac604e5867497cc93fb677b5ebc28ef87e057f8`](https://github.com/opennextjs/opennextjs-aws/commit/0ac604e5867497cc93fb677b5ebc28ef87e057f8), [`1ece6b479bb4e0309892ffbd1200870821a410c4`](https://github.com/opennextjs/opennextjs-aws/commit/1ece6b479bb4e0309892ffbd1200870821a410c4), [`697681bf9ce25212ce4e2e94d886ca425428280d`](https://github.com/opennextjs/opennextjs-aws/commit/697681bf9ce25212ce4e2e94d886ca425428280d)]: + - @opennextjs/aws@3.3.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies [[`6f798debb575b157acb2f5068658f95ace0fae50`](https://github.com/opennextjs/opennextjs-aws/commit/6f798debb575b157acb2f5068658f95ace0fae50), [`fe600ac6f5e513376cf233a5d2ce68affaa3aa5a`](https://github.com/opennextjs/opennextjs-aws/commit/fe600ac6f5e513376cf233a5d2ce68affaa3aa5a), [`5f0cbc8feac9eec728c27bb3b7ff5c3f3bc26716`](https://github.com/opennextjs/opennextjs-aws/commit/5f0cbc8feac9eec728c27bb3b7ff5c3f3bc26716), [`8b51108d9aee7e5ed3027c1ceda99091b579951d`](https://github.com/opennextjs/opennextjs-aws/commit/8b51108d9aee7e5ed3027c1ceda99091b579951d), [`b999c4e9a38499680bed77ddeb94b62a3301c0fa`](https://github.com/opennextjs/opennextjs-aws/commit/b999c4e9a38499680bed77ddeb94b62a3301c0fa), [`ba84259d2e35e79a562a7e3f055e350a03c9d651`](https://github.com/opennextjs/opennextjs-aws/commit/ba84259d2e35e79a562a7e3f055e350a03c9d651)]: + - @opennextjs/aws@3.2.2 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies [[`cf33973f3fbab73e77898fdd072a00a1f037257a`](https://github.com/opennextjs/opennextjs-aws/commit/cf33973f3fbab73e77898fdd072a00a1f037257a), [`77d87e7a870fad6afad022bf75aca18c8656c268`](https://github.com/opennextjs/opennextjs-aws/commit/77d87e7a870fad6afad022bf75aca18c8656c268), [`a43b82b4cb68889371ac8260aefef9e04eefb037`](https://github.com/opennextjs/opennextjs-aws/commit/a43b82b4cb68889371ac8260aefef9e04eefb037), [`bfa1a8c4056bd691fb57617dd6287693e51071b4`](https://github.com/opennextjs/opennextjs-aws/commit/bfa1a8c4056bd691fb57617dd6287693e51071b4), [`5839217411012d1df2874d299daa977ba3701c2c`](https://github.com/opennextjs/opennextjs-aws/commit/5839217411012d1df2874d299daa977ba3701c2c), [`dfc174d88b7bcc54eede09c98d9443dd84b93fd8`](https://github.com/opennextjs/opennextjs-aws/commit/dfc174d88b7bcc54eede09c98d9443dd84b93fd8)]: + - @opennextjs/aws@3.2.1 diff --git a/examples/e2e/app-pages-router/README.md b/examples/e2e/app-pages-router/README.md new file mode 100644 index 00000000..dd87bc02 --- /dev/null +++ b/examples/e2e/app-pages-router/README.md @@ -0,0 +1,3 @@ +# App Pages Router + +This project uses both the App and Pages router. diff --git a/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/[song]/page.tsx b/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/[song]/page.tsx new file mode 100644 index 00000000..f83f84b4 --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/[song]/page.tsx @@ -0,0 +1,29 @@ +import { getSong } from "@example/shared/api"; +import Modal from "@example/shared/components/Modal"; + +type Props = { + params: Promise<{ + album: string; + song: string; + }>; +}; +export default async function SongPage(props: Props) { + const params = await props.params; + const song = await getSong(params.album, params.song); + return ( + +

Modal

+ Album: {decodeURIComponent(params.album)} +
+ {/*
+
+ ); +} diff --git a/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/page.tsx b/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/page.tsx new file mode 100644 index 00000000..463c0c96 --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/@modal/(.)[album]/page.tsx @@ -0,0 +1,11 @@ +import Modal from "@example/shared/components/Modal"; + +type Props = { + params: Promise<{ + artist: string; + }>; +}; +export default async function ArtistPage(props: Props) { + const params = await props.params; + return Artists {params.artist}; +} diff --git a/examples/e2e/app-pages-router/app/albums/@modal/default.tsx b/examples/e2e/app-pages-router/app/albums/@modal/default.tsx new file mode 100644 index 00000000..6ddf1b76 --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/@modal/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null; +} diff --git a/examples/e2e/app-pages-router/app/albums/[album]/[song]/page.tsx b/examples/e2e/app-pages-router/app/albums/[album]/[song]/page.tsx new file mode 100644 index 00000000..87e15694 --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/[album]/[song]/page.tsx @@ -0,0 +1,25 @@ +import { getSong } from "@example/shared/api"; + +type Props = { + params: Promise<{ + album: string; + song: string; + }>; +}; +export default async function Song(props: Props) { + const params = await props.params; + const song = await getSong(params.album, params.song); + + return ( +
+

Not Modal

+ {decodeURIComponent(params.album)} + +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/albums/[album]/page.tsx b/examples/e2e/app-pages-router/app/albums/[album]/page.tsx new file mode 100644 index 00000000..8d3a5537 --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/[album]/page.tsx @@ -0,0 +1,3 @@ +export default function ArtistPage() { + return
Artist
; +} diff --git a/examples/e2e/app-pages-router/app/albums/layout.tsx b/examples/e2e/app-pages-router/app/albums/layout.tsx new file mode 100644 index 00000000..0de9cfdc --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/layout.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from "react"; + +export default function Layout({ children, modal }: { children: ReactNode; modal: ReactNode }) { + return ( +
+ {children} + {modal} +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/albums/page.tsx b/examples/e2e/app-pages-router/app/albums/page.tsx new file mode 100644 index 00000000..6471009b --- /dev/null +++ b/examples/e2e/app-pages-router/app/albums/page.tsx @@ -0,0 +1,13 @@ +import { getAlbums } from "@example/shared/api"; +import Album from "@example/shared/components/Album"; + +export default async function AlbumPage() { + const albums = await getAlbums(); + return ( +
+ {albums.map((album) => ( + + ))} +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/api/client/route.ts b/examples/e2e/app-pages-router/app/api/client/route.ts new file mode 100644 index 00000000..e9c204ba --- /dev/null +++ b/examples/e2e/app-pages-router/app/api/client/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + return NextResponse.json({ + hello: "client", + }); +} diff --git a/examples/e2e/app-pages-router/app/api/host/route.ts b/examples/e2e/app-pages-router/app/api/host/route.ts new file mode 100644 index 00000000..16630b34 --- /dev/null +++ b/examples/e2e/app-pages-router/app/api/host/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + return NextResponse.json({ + url: request.url, + }); +} diff --git a/examples/e2e/app-pages-router/app/api/page.tsx b/examples/e2e/app-pages-router/app/api/page.tsx new file mode 100644 index 00000000..0771ac5a --- /dev/null +++ b/examples/e2e/app-pages-router/app/api/page.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useCallback, useState } from "react"; + +/** + * Make /api/hello call exclusively on the client + * - we already know SSR can fetch itself w/o issues + */ +export default function Page() { + const [data, setData] = useState(); + + const onClientClick = useCallback(async () => { + const { protocol, host } = window.location; + const url = `${protocol}//${host}`; + const r = await fetch(`${url}/api/client`); + const d = await r.json(); + setData(d); + }, []); + + const onMiddlewareClick = useCallback(async () => { + const { protocol, host } = window.location; + const url = `${protocol}//${host}`; + const r = await fetch(`${url}/api/middleware`); + const d = await r.json(); + setData(d); + }, []); + + return ( +
+
API: {data ? JSON.stringify(data, null, 2) : "N/A"}
+ + + +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/globals.css b/examples/e2e/app-pages-router/app/globals.css new file mode 100644 index 00000000..e0936eda --- /dev/null +++ b/examples/e2e/app-pages-router/app/globals.css @@ -0,0 +1,23 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) + rgb(var(--background-start-rgb)); +} diff --git a/examples/e2e/app-pages-router/app/image-optimization/page.tsx b/examples/e2e/app-pages-router/app/image-optimization/page.tsx new file mode 100644 index 00000000..3bc0ee04 --- /dev/null +++ b/examples/e2e/app-pages-router/app/image-optimization/page.tsx @@ -0,0 +1,9 @@ +import Image from "next/image"; + +export default function ImageOptimization() { + return ( +
+ Corporate Holiday Card +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/isr/page.tsx b/examples/e2e/app-pages-router/app/isr/page.tsx new file mode 100644 index 00000000..3eadac36 --- /dev/null +++ b/examples/e2e/app-pages-router/app/isr/page.tsx @@ -0,0 +1,9 @@ +async function getTime() { + return new Date().toISOString(); +} + +export const revalidate = 10; +export default async function ISR() { + const time = getTime(); + return
Time: {time}
; +} diff --git a/examples/e2e/app-pages-router/app/layout.tsx b/examples/e2e/app-pages-router/app/layout.tsx new file mode 100644 index 00000000..f281f848 --- /dev/null +++ b/examples/e2e/app-pages-router/app/layout.tsx @@ -0,0 +1,22 @@ +import "./globals.css"; + +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Nextjs App Router", + description: "Generated by create next app", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + +
Header
+ {children} + + + ); +} diff --git a/examples/e2e/app-pages-router/app/page.tsx b/examples/e2e/app-pages-router/app/page.tsx new file mode 100644 index 00000000..f770b063 --- /dev/null +++ b/examples/e2e/app-pages-router/app/page.tsx @@ -0,0 +1,48 @@ +import Nav from "@example/shared/components/Nav"; + +export default function Home() { + return ( + <> +

App Router

+
+ + + + + + + + + +
+

Pages Router

+
+ + +
+ + ); +} diff --git a/examples/e2e/app-pages-router/app/parallel/@a/a-page/page.tsx b/examples/e2e/app-pages-router/app/parallel/@a/a-page/page.tsx new file mode 100644 index 00000000..8108d277 --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/@a/a-page/page.tsx @@ -0,0 +1,3 @@ +export default function APage() { + return
A Page
; +} diff --git a/examples/e2e/app-pages-router/app/parallel/@a/page.tsx b/examples/e2e/app-pages-router/app/parallel/@a/page.tsx new file mode 100644 index 00000000..5e7ee146 --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/@a/page.tsx @@ -0,0 +1,10 @@ +import Link from "next/link"; + +export default function A() { + return ( +
+

Parallel Route A

+ Go to a-page +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/parallel/@b/b-page/page.tsx b/examples/e2e/app-pages-router/app/parallel/@b/b-page/page.tsx new file mode 100644 index 00000000..1750356e --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/@b/b-page/page.tsx @@ -0,0 +1,3 @@ +export default function BPage() { + return
B Page
; +} diff --git a/examples/e2e/app-pages-router/app/parallel/@b/page.tsx b/examples/e2e/app-pages-router/app/parallel/@b/page.tsx new file mode 100644 index 00000000..b217f84e --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/@b/page.tsx @@ -0,0 +1,11 @@ +import Link from "next/link"; + +export default function B() { + return ( +
+

Parallel Route B

+ + Go to b-page +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/parallel/layout.tsx b/examples/e2e/app-pages-router/app/parallel/layout.tsx new file mode 100644 index 00000000..5925ac09 --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/layout.tsx @@ -0,0 +1,42 @@ +"use client"; +import { useState } from "react"; + +import type { ReactNode } from "react"; + +export default function Layout({ a, b, children }: { children: ReactNode; a: ReactNode; b: ReactNode }) { + const [routeA, setRouteA] = useState(false); + const [routeB, setRouteB] = useState(false); + + return ( +
+
+ + +
+ + {routeA && a} + {routeB && b} + {/* {children} */} +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/parallel/page.tsx b/examples/e2e/app-pages-router/app/parallel/page.tsx new file mode 100644 index 00000000..67e08591 --- /dev/null +++ b/examples/e2e/app-pages-router/app/parallel/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return null; +} diff --git a/examples/e2e/app-pages-router/app/redirect-destination/page.tsx b/examples/e2e/app-pages-router/app/redirect-destination/page.tsx new file mode 100644 index 00000000..1fe01ac3 --- /dev/null +++ b/examples/e2e/app-pages-router/app/redirect-destination/page.tsx @@ -0,0 +1,3 @@ +export default function RedirectDestination() { + return
Redirect Destination
; +} diff --git a/examples/e2e/app-pages-router/app/rewrite-destination/page.tsx b/examples/e2e/app-pages-router/app/rewrite-destination/page.tsx new file mode 100644 index 00000000..67a7bab2 --- /dev/null +++ b/examples/e2e/app-pages-router/app/rewrite-destination/page.tsx @@ -0,0 +1,9 @@ +export default async function RewriteDestination(props: { searchParams: Promise<{ a: string }> }) { + const searchParams = await props.searchParams; + return ( +
+
Rewritten Destination
+
a: {searchParams.a}
+
+ ); +} diff --git a/examples/e2e/app-pages-router/app/server-actions/client.tsx b/examples/e2e/app-pages-router/app/server-actions/client.tsx new file mode 100644 index 00000000..8f3bbda3 --- /dev/null +++ b/examples/e2e/app-pages-router/app/server-actions/client.tsx @@ -0,0 +1,26 @@ +"use client"; +import { useCallback, useState, useTransition } from "react"; + +import type { Song as SongType } from "@example/shared/api"; +import { getSong } from "@example/shared/api"; +import Song from "@example/shared/components/Album/Song"; + +export default function Client() { + const [isPending, startTransition] = useTransition(); + const [song, setSong] = useState(); + + const onClick = useCallback(() => { + startTransition(async () => { + const song = await getSong("Hold Me In Your Arms", "I'm never gonna give you up"); + setSong(song); + }); + }, []); + + return ( +
+ + {isPending &&
☎️ing Server Actions...
} + {song && } +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/server-actions/page.tsx b/examples/e2e/app-pages-router/app/server-actions/page.tsx new file mode 100644 index 00000000..f15c66fc --- /dev/null +++ b/examples/e2e/app-pages-router/app/server-actions/page.tsx @@ -0,0 +1,10 @@ +import Client from "./client"; + +export default function Page() { + return ( +
+

Server Actions

+ +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/ssr/layout.tsx b/examples/e2e/app-pages-router/app/ssr/layout.tsx new file mode 100644 index 00000000..68e3e07e --- /dev/null +++ b/examples/e2e/app-pages-router/app/ssr/layout.tsx @@ -0,0 +1,10 @@ +import type { PropsWithChildren } from "react"; + +export default function Layout({ children }: PropsWithChildren) { + return ( +
+

SSR

+ {children} +
+ ); +} diff --git a/examples/e2e/app-pages-router/app/ssr/loading.tsx b/examples/e2e/app-pages-router/app/ssr/loading.tsx new file mode 100644 index 00000000..fc80ef06 --- /dev/null +++ b/examples/e2e/app-pages-router/app/ssr/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return
Loading...
; +} diff --git a/examples/e2e/app-pages-router/app/ssr/page.tsx b/examples/e2e/app-pages-router/app/ssr/page.tsx new file mode 100644 index 00000000..377a746c --- /dev/null +++ b/examples/e2e/app-pages-router/app/ssr/page.tsx @@ -0,0 +1,22 @@ +import { headers } from "next/headers"; + +async function getTime() { + const res = await new Promise((resolve) => { + setTimeout(() => { + resolve(new Date().toISOString()); + }, 1500); + }); + return res; +} + +export default async function SSR() { + const time = await getTime(); + const headerList = await headers(); + return ( +
+

Time: {time}

+
{headerList.get("host")}
+
Env: {process.env.SOME_ENV_VAR}
+
+ ); +} diff --git a/examples/e2e/app-pages-router/e2e/api.test.ts b/examples/e2e/app-pages-router/e2e/api.test.ts new file mode 100644 index 00000000..14bb2f56 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/api.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from "@playwright/test"; + +test("API call from client", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/api"]').click(); + + await page.waitForURL("/api"); + + let el = page.getByText("API: N/A"); + await expect(el).toBeVisible(); + + await page.getByRole("button", { name: "Call /api/client" }).click(); + el = page.getByText('API: { "hello": "client" }'); + await expect(el).toBeVisible(); +}); + +test("API call from middleware", async ({ page }) => { + await page.goto("/"); + await page.getByRole("link", { name: "/API" }).click(); + + await page.waitForURL("/api"); + + let el = page.getByText("API: N/A"); + await expect(el).toBeVisible(); + + await page.getByRole("button", { name: "Call /api/middleware" }).click(); + el = page.getByText('API: { "hello": "middleware" }'); + await expect(el).toBeVisible(); +}); + +test("API call from middleware with top-level await", async ({ request }) => { + const response = await request.get("/api/middlewareTopLevelAwait"); + const data = await response.json(); + expect(data).toEqual({ hello: "top-level-await" }); +}); diff --git a/examples/e2e/app-pages-router/e2e/host.test.ts b/examples/e2e/app-pages-router/e2e/host.test.ts new file mode 100644 index 00000000..98141106 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/host.test.ts @@ -0,0 +1,11 @@ +import { expect, test } from "@playwright/test"; + +/** + * Tests that the request.url is the deployed host and not localhost + */ +test("Request.url is host", async ({ baseURL, page }) => { + await page.goto("/api/host"); + + const el = page.getByText(`{"url":"${baseURL}/api/host"}`); + await expect(el).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/image-optimization.test.ts b/examples/e2e/app-pages-router/e2e/image-optimization.test.ts new file mode 100644 index 00000000..64e02051 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/image-optimization.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from "@playwright/test"; + +test("Image Optimization", async ({ page }) => { + await page.goto("/"); + + const imageResponsePromise = page.waitForResponse(/corporate_holiday_card.jpg/); + await page.locator('[href="/image-optimization"]').click(); + const imageResponse = await imageResponsePromise; + + await page.waitForURL("/image-optimization"); + + const imageContentType = imageResponse.headers()["content-type"]; + expect(imageContentType).toBe("image/webp"); + + const el = page.locator("img"); + await expect(el).toHaveJSProperty("complete", true); + await expect(el).not.toHaveJSProperty("naturalWidth", 0); +}); diff --git a/examples/e2e/app-pages-router/e2e/isr.test.ts b/examples/e2e/app-pages-router/e2e/isr.test.ts new file mode 100644 index 00000000..4d0d35c0 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/isr.test.ts @@ -0,0 +1,41 @@ +import { expect, test } from "@playwright/test"; + +test("Incremental Static Regeneration", async ({ page }) => { + test.setTimeout(60000); + await page.goto("/"); + await page.locator('[href="/isr"]').click(); + // Load the page a couple times to regenerate ISR + + let el = page.getByText("Time:"); + // Track the static time + let time = await el.textContent(); + let newTime: typeof time; + let tempTime = time; + do { + await page.waitForTimeout(1000); + await page.reload(); + time = tempTime; + el = page.getByText("Time:"); + newTime = await el.textContent(); + tempTime = newTime; + } while (time !== newTime); + await page.reload(); + + await page.waitForTimeout(1000); + el = page.getByText("Time:"); + const midTime = await el.textContent(); + // Expect that the time is still stale + expect(midTime).toEqual(newTime); + + // Wait 10 + 1 seconds for ISR to regenerate time + await page.waitForTimeout(11000); + let finalTime = newTime; + do { + await page.waitForTimeout(2000); + el = page.getByText("Time:"); + finalTime = await el.textContent(); + await page.reload(); + } while (newTime === finalTime); + + expect(newTime).not.toEqual(finalTime); +}); diff --git a/examples/e2e/app-pages-router/e2e/middleware.redirect.test.ts b/examples/e2e/app-pages-router/e2e/middleware.redirect.test.ts new file mode 100644 index 00000000..108a0ef0 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/middleware.redirect.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from "@playwright/test"; + +test("Middleware Redirect", async ({ page, context }) => { + await page.goto("/"); + await page.locator('[href="/redirect"]').click(); + + // URL is immediately redirected + await page.waitForURL("/redirect-destination"); + let el = page.getByText("Redirect Destination", { exact: true }); + await expect(el).toBeVisible(); + + // Loading page should also redirect + await page.goto("/redirect"); + await page.waitForURL("/redirect-destination"); + expect(await context.cookies().then((res) => res.find((cookie) => cookie.name === "test")?.value)).toBe( + "success" + ); + el = page.getByText("Redirect Destination", { exact: true }); + await expect(el).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/middleware.rewrite.test.ts b/examples/e2e/app-pages-router/e2e/middleware.rewrite.test.ts new file mode 100644 index 00000000..ba09abd6 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/middleware.rewrite.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from "@playwright/test"; + +test("Middleware Rewrite", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/rewrite"]').click(); + + await page.waitForURL("/rewrite"); + let el = page.getByText("Rewritten Destination", { exact: true }); + await expect(el).toBeVisible(); + el = page.getByText("a: b", { exact: true }); + await expect(el).toBeVisible(); + // Loading page should also rewrite + await page.goto("/rewrite"); + await page.waitForURL("/rewrite"); + el = page.getByText("Rewritten Destination", { exact: true }); + await expect(el).toBeVisible(); + el = page.getByText("a: b", { exact: true }); + await expect(el).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/modals.test.ts b/examples/e2e/app-pages-router/e2e/modals.test.ts new file mode 100644 index 00000000..5a443024 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/modals.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from "@playwright/test"; + +test("Route modal and interception", async ({ page }) => { + await page.goto("/"); + await page.getByRole("link", { name: "Albums" }).click(); + await page.getByRole("link", { name: "Song: I'm never gonna give you up Year: 1965" }).click(); + + await page.waitForURL(`/albums/Hold%20Me%20In%20Your%20Arms/I'm%20never%20gonna%20give%20you%20up`); + + const modal = page.getByText("Modal", { exact: true }); + await expect(modal).toBeVisible(); + + // Reload the page to load non intercepted modal + await page.reload(); + await page.waitForURL(`/albums/Hold%20Me%20In%20Your%20Arms/I'm%20never%20gonna%20give%20you%20up`); + const notModal = page.getByText("Not Modal", { exact: true }); + await expect(notModal).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/pages_isr.test.ts b/examples/e2e/app-pages-router/e2e/pages_isr.test.ts new file mode 100644 index 00000000..93abbd26 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/pages_isr.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "@playwright/test"; + +test("Incremental Static Regeneration", async ({ page }) => { + test.setTimeout(60000); + await page.goto("/"); + await page.locator('[href="/pages_isr"]').click(); + + await page.waitForURL("/pages_isr"); + // Load the page a couple times to regenerate ISR + + let el = page.getByText("Time:"); + // Track the static time + let time = await el.textContent(); + let newTime: typeof time; + let tempTime = time; + do { + await page.waitForTimeout(1000); + await page.reload(); + time = tempTime; + el = page.getByText("Time:"); + newTime = await el.textContent(); + tempTime = newTime; + } while (time !== newTime); + await page.reload(); + await page.waitForTimeout(1000); + el = page.getByText("Time:"); + const midTime = await el.textContent(); + // Expect that the time is still stale + expect(midTime).toEqual(newTime); + + // Wait 10 + 1 seconds for ISR to regenerate time + await page.waitForTimeout(11000); + let finalTime = newTime; + do { + await page.waitForTimeout(2000); + el = page.getByText("Time:"); + finalTime = await el.textContent(); + await page.reload(); + } while (newTime === finalTime); + + expect(newTime).not.toEqual(finalTime); +}); diff --git a/examples/e2e/app-pages-router/e2e/pages_ssr.test.ts b/examples/e2e/app-pages-router/e2e/pages_ssr.test.ts new file mode 100644 index 00000000..77e33ac3 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/pages_ssr.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "@playwright/test"; + +test("Server Side Render", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/pages_ssr"]').click(); + + await page.waitForURL("/pages_ssr"); + let el = page.getByText("Time:"); + await expect(el).toBeVisible(); + let time = await el.textContent(); + + await page.reload(); + + el = page.getByText("Time:"); + let newTime = await el.textContent(); + await expect(el).toBeVisible(); + + for (let i = 0; i < 5; i++) { + await page.reload(); + el = page.getByText("Time:"); + newTime = await el.textContent(); + await expect(el).toBeVisible(); + expect(time).not.toEqual(newTime); + time = newTime; + await page.waitForTimeout(250); + } +}); diff --git a/examples/e2e/app-pages-router/e2e/parallel.test.ts b/examples/e2e/app-pages-router/e2e/parallel.test.ts new file mode 100644 index 00000000..8f3bf80f --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/parallel.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "@playwright/test"; + +test("Parallel routes", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/parallel"]').click(); + + await page.waitForURL("/parallel"); + + // Neither are selected, so A/B shouldn't be rendered + let routeA = page.getByText("Parallel Route A"); + let routeB = page.getByText("Parallel Route B"); + await expect(routeA).not.toBeVisible(); + await expect(routeB).not.toBeVisible(); + + // Enable A, which should be visible but not B + await page.locator('input[name="a"]').check(); + routeA = page.getByText("Parallel Route A"); + await expect(routeA).toBeVisible(); + await expect(routeB).not.toBeVisible(); + + // Enable B, both should be visible + await page.locator('input[name="b"]').check(); + routeB = page.getByText("Parallel Route B"); + await expect(routeA).toBeVisible(); + await expect(routeB).toBeVisible(); + + // Click on A, should go to a-page + await page.getByText("Go to a-page").click(); + await page.waitForURL("/parallel/a-page"); + + // Should render contents of a-page + routeA = page.getByText("A Page"); + await expect(routeA).toBeVisible(); + + // Click on B, should go to b-page + await page.getByText("Go to b-page").click(); + await page.waitForURL("/parallel/b-page"); + + // Should render contents of b-page + routeB = page.getByText("B Page"); + await expect(routeB).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/playwright.config.ts b/examples/e2e/app-pages-router/e2e/playwright.config.ts new file mode 100644 index 00000000..47fa6c2b --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/playwright.config.ts @@ -0,0 +1,54 @@ +import { defineConfig, devices } from "@playwright/test"; +import type nodeProcess from "node:process"; + +declare const process: typeof nodeProcess; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:8792", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + // TODO(vicb): enable all browsers + // { + // name: "firefox", + // use: { ...devices["Desktop Firefox"] }, + // }, + // { + // name: "webkit", + // use: { ...devices["Desktop Safari"] }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: "pnpm preview", + url: "http://localhost:8792", + reuseExistingServer: !process.env.CI, + timeout: 70_000, + }, +}); diff --git a/examples/e2e/app-pages-router/e2e/serverActions.test.ts b/examples/e2e/app-pages-router/e2e/serverActions.test.ts new file mode 100644 index 00000000..106354e0 --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/serverActions.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from "@playwright/test"; + +test("Server Actions", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/server-actions"]').click(); + + await page.waitForURL("/server-actions"); + let el = page.getByText("Song: I'm never gonna give you up"); + await expect(el).not.toBeVisible(); + + await page.getByRole("button", { name: "Fire Server Actions" }).click(); + el = page.getByText("Song: I'm never gonna give you up"); + await expect(el).toBeVisible(); + + // Reload page + await page.reload(); + el = page.getByText("Song: I'm never gonna give you up"); + await expect(el).not.toBeVisible(); + await page.getByRole("button", { name: "Fire Server Actions" }).click(); + el = page.getByText("Song: I'm never gonna give you up"); + await expect(el).toBeVisible(); +}); diff --git a/examples/e2e/app-pages-router/e2e/skip_trailing.test.ts b/examples/e2e/app-pages-router/e2e/skip_trailing.test.ts new file mode 100644 index 00000000..2a7033fd --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/skip_trailing.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from "@playwright/test"; + +test("skipTrailingSlashRedirect redirect", async ({ page }) => { + const response = await page.goto("/ssr"); + + expect(response?.request().redirectedFrom()).toBeNull(); + expect(response?.request().url()).toMatch(/\/ssr$/); +}); diff --git a/examples/e2e/app-pages-router/e2e/ssr.test.ts b/examples/e2e/app-pages-router/e2e/ssr.test.ts new file mode 100644 index 00000000..63020afd --- /dev/null +++ b/examples/e2e/app-pages-router/e2e/ssr.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from "@playwright/test"; + +test("Server Side Render", async ({ page }) => { + await page.goto("/"); + await page.locator('[href="/ssr"]').click(); + + await page.waitForURL("/ssr"); + let el = page.getByText("Time:"); + await expect(el).toBeVisible(); + let time = await el.textContent(); + + await page.reload(); + + el = page.getByText("Time:"); + let newTime = await el.textContent(); + await expect(el).toBeVisible(); + + for (let i = 0; i < 5; i++) { + await page.reload(); + el = page.getByText("Time:"); + newTime = await el.textContent(); + await expect(el).toBeVisible(); + expect(time).not.toEqual(newTime); + time = newTime; + await page.waitForTimeout(250); + } +}); + +test("Server Side Render with env", async ({ page }) => { + await page.goto("/ssr"); + const el = page.getByText("Env:"); + expect(await el.textContent()).toEqual("Env: foo"); +}); diff --git a/examples/e2e/app-pages-router/middleware.ts b/examples/e2e/app-pages-router/middleware.ts new file mode 100644 index 00000000..5dcf1414 --- /dev/null +++ b/examples/e2e/app-pages-router/middleware.ts @@ -0,0 +1,57 @@ +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; + +// Needed to test top-level await +// We are using `setTimeout` to simulate a "long" running operation +// we could have used `Promise.resolve` instead, but it would be running in a different way in the event loop +// @ts-expect-error - It will cause a warning at build time, but it should just work +const topLevelAwait = await new Promise((resolve) => { + setTimeout(() => { + resolve("top-level-await"); + }, 10); +}); + +export function middleware(request: NextRequest) { + const path = request.nextUrl.pathname; //new URL(request.url).pathname; + + const host = request.headers.get("host"); + const protocol = host?.startsWith("localhost") ? "http" : "https"; + if (path === "/redirect") { + const u = new URL("/redirect-destination", `${protocol}://${host}`); + return NextResponse.redirect(u, { + headers: { "set-cookie": "test=success" }, + }); + } + if (path === "/rewrite") { + const u = new URL("/rewrite-destination", `${protocol}://${host}`); + u.searchParams.set("a", "b"); + return NextResponse.rewrite(u); + } + if (path === "/api/middleware") { + return new NextResponse(JSON.stringify({ hello: "middleware" }), { + status: 200, + headers: { + "content-type": "application/json", + }, + }); + } + if (path === "/api/middlewareTopLevelAwait") { + return new NextResponse(JSON.stringify({ hello: topLevelAwait }), { + status: 200, + headers: { + "content-type": "application/json", + }, + }); + } + const rHeaders = new Headers(request.headers); + const r = NextResponse.next({ + request: { + headers: rHeaders, + }, + }); + return r; +} + +export const config = { + matcher: ["/((?!_next|favicon.ico|match|static|fonts|api/auth|og).*)"], +}; diff --git a/examples/e2e/app-pages-router/next.config.ts b/examples/e2e/app-pages-router/next.config.ts new file mode 100644 index 00000000..f8a1d2f4 --- /dev/null +++ b/examples/e2e/app-pages-router/next.config.ts @@ -0,0 +1,19 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + poweredByHeader: false, + cleanDistDir: true, + transpilePackages: ["@example/shared"], + output: "standalone", + // outputFileTracingRoot: "../sst", + typescript: { + ignoreBuildErrors: true, + }, + eslint: { + ignoreDuringBuilds: true, + }, + trailingSlash: true, + skipTrailingSlashRedirect: true, +}; + +export default nextConfig; diff --git a/examples/e2e/app-pages-router/open-next.config.ts b/examples/e2e/app-pages-router/open-next.config.ts new file mode 100644 index 00000000..0f7794ff --- /dev/null +++ b/examples/e2e/app-pages-router/open-next.config.ts @@ -0,0 +1,25 @@ +import type { OpenNextConfig } from "@opennextjs/aws/types/open-next"; + +const config: OpenNextConfig = { + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + // Unused implementation + incrementalCache: "dummy", + tagCache: "dummy", + queue: "dummy", + }, + }, + + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }, +}; + +export default config; diff --git a/examples/e2e/app-pages-router/package.json b/examples/e2e/app-pages-router/package.json new file mode 100644 index 00000000..6002dc18 --- /dev/null +++ b/examples/e2e/app-pages-router/package.json @@ -0,0 +1,34 @@ +{ + "name": "app-pages-router", + "version": "0.1.6", + "private": true, + "scripts": { + "openbuild": "node ../../packages/open-next/dist/index.js build --build-command \"npx turbo build\"", + "dev": "next dev --turbopack --port 3003", + "build": "next build", + "start": "next start --port 3003", + "lint": "next lint", + "clean": "rm -rf .turbo node_modules .next .open-next", + "build:worker": "pnpm opennextjs-cloudflare", + "dev:worker": "wrangler dev --port 8792 --inspector-port 9352", + "preview": "pnpm build:worker && pnpm dev:worker", + "e2e-fix": "playwright test -c e2e/playwright.config.ts" + }, + "dependencies": { + "@opennextjs/cloudflare": "workspace:*", + "@example/shared": "workspace:*", + "next": "catalog:e2e", + "react": "catalog:e2e", + "react-dom": "catalog:e2e" + }, + "devDependencies": { + "@playwright/test": "catalog:", + "@types/node": "catalog:e2e", + "@types/react": "catalog:e2e", + "@types/react-dom": "catalog:e2e", + "autoprefixer": "catalog:e2e", + "postcss": "catalog:e2e", + "tailwindcss": "catalog:e2e", + "typescript": "catalog:default" + } +} diff --git a/examples/e2e/app-pages-router/pages/_app.tsx b/examples/e2e/app-pages-router/pages/_app.tsx new file mode 100644 index 00000000..a9d6fd8d --- /dev/null +++ b/examples/e2e/app-pages-router/pages/_app.tsx @@ -0,0 +1,7 @@ +import "@/styles/globals.css"; + +import type { AppProps } from "next/app"; + +export default function App({ Component, pageProps }: AppProps) { + return ; +} diff --git a/examples/e2e/app-pages-router/pages/_document.tsx b/examples/e2e/app-pages-router/pages/_document.tsx new file mode 100644 index 00000000..ffc3f3cc --- /dev/null +++ b/examples/e2e/app-pages-router/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Head, Html, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/examples/e2e/app-pages-router/pages/api/hello.ts b/examples/e2e/app-pages-router/pages/api/hello.ts new file mode 100644 index 00000000..41b59cbc --- /dev/null +++ b/examples/e2e/app-pages-router/pages/api/hello.ts @@ -0,0 +1,10 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + hello: string; +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ hello: "world" }); +} diff --git a/examples/e2e/app-pages-router/pages/pages_isr/index.tsx b/examples/e2e/app-pages-router/pages/pages_isr/index.tsx new file mode 100644 index 00000000..69af885d --- /dev/null +++ b/examples/e2e/app-pages-router/pages/pages_isr/index.tsx @@ -0,0 +1,14 @@ +import type { InferGetStaticPropsType } from "next"; + +export async function getStaticProps() { + return { + props: { + time: new Date().toISOString(), + }, + revalidate: 10, + }; +} + +export default function Page({ time }: InferGetStaticPropsType) { + return
Time: {time}
; +} diff --git a/examples/e2e/app-pages-router/pages/pages_ssr/index.tsx b/examples/e2e/app-pages-router/pages/pages_ssr/index.tsx new file mode 100644 index 00000000..ba06018d --- /dev/null +++ b/examples/e2e/app-pages-router/pages/pages_ssr/index.tsx @@ -0,0 +1,13 @@ +import type { InferGetServerSidePropsType } from "next"; + +export async function getServerSideProps() { + return { + props: { + time: new Date().toISOString(), + }, + }; +} + +export default function Page({ time }: InferGetServerSidePropsType) { + return
Time: {time}
; +} diff --git a/examples/e2e/app-pages-router/postcss.config.js b/examples/e2e/app-pages-router/postcss.config.js new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/examples/e2e/app-pages-router/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/examples/e2e/app-pages-router/public/favicon.ico b/examples/e2e/app-pages-router/public/favicon.ico new file mode 100644 index 00000000..4ba005f2 Binary files /dev/null and b/examples/e2e/app-pages-router/public/favicon.ico differ diff --git a/examples/e2e/app-pages-router/public/static/corporate_holiday_card.jpg b/examples/e2e/app-pages-router/public/static/corporate_holiday_card.jpg new file mode 100644 index 00000000..0df96ae2 Binary files /dev/null and b/examples/e2e/app-pages-router/public/static/corporate_holiday_card.jpg differ diff --git a/examples/e2e/app-pages-router/public/static/frank.webp b/examples/e2e/app-pages-router/public/static/frank.webp new file mode 100644 index 00000000..b2cc67f0 Binary files /dev/null and b/examples/e2e/app-pages-router/public/static/frank.webp differ diff --git a/examples/e2e/app-pages-router/styles/globals.css b/examples/e2e/app-pages-router/styles/globals.css new file mode 100644 index 00000000..cb7c88f8 --- /dev/null +++ b/examples/e2e/app-pages-router/styles/globals.css @@ -0,0 +1,95 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", + "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0)); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient(#00000080, #00000040, #00000030, #00000020, #00000010, #00000010, #00000080); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) + rgb(var(--background-start-rgb)); +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} diff --git a/examples/e2e/app-pages-router/tailwind.config.ts b/examples/e2e/app-pages-router/tailwind.config.ts new file mode 100644 index 00000000..edaaef36 --- /dev/null +++ b/examples/e2e/app-pages-router/tailwind.config.ts @@ -0,0 +1,15 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: ["./app/**/*.{js,ts,jsx,tsx,mdx}", "../../examples/shared/**/*.{jsx,tsx}"], + theme: { + extend: { + backgroundImage: { + "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", + "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + }, + }, + }, + plugins: [], +}; +export default config; diff --git a/examples/e2e/app-pages-router/tsconfig.json b/examples/e2e/app-pages-router/tsconfig.json new file mode 100644 index 00000000..4116885e --- /dev/null +++ b/examples/e2e/app-pages-router/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "NodeNext", + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"], + "@example/shared": ["../shared"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../utils.ts"], + "exclude": ["node_modules", "open-next.config.ts"] +} diff --git a/examples/e2e/app-pages-router/wrangler.json b/examples/e2e/app-pages-router/wrangler.json new file mode 100644 index 00000000..0ea9def5 --- /dev/null +++ b/examples/e2e/app-pages-router/wrangler.json @@ -0,0 +1,11 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "app-pages-router", + "compatibility_date": "2024-12-30", + "compatibility_flags": ["nodejs_compat"], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS" + } +}