-
-
-
- Get started by editing{' '}
- app/page.tsx
+
+
+
+ Get started by editing
+ app/page.jsx
-
-
+
+
+
+
+
+
)
}
diff --git a/packages/create-next-app/templates/app/js/app/page.module.css b/packages/create-next-app/templates/app/js/app/page.module.css
index a978c99de57b3..4732b55fdc237 100644
--- a/packages/create-next-app/templates/app/js/app/page.module.css
+++ b/packages/create-next-app/templates/app/js/app/page.module.css
@@ -1,146 +1,271 @@
-.container {
- padding: 0 2rem;
-}
-
.main {
- min-height: 100vh;
- padding: 4rem 0;
- flex: 1;
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
}
-.footer {
- display: flex;
- flex: 1;
- padding: 2rem 0;
- border-top: 1px solid #eaeaea;
- justify-content: center;
- align-items: center;
+.description {
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
}
-.footer a {
+.description a {
display: flex;
- justify-content: center;
align-items: center;
- flex-grow: 1;
+ justify-content: center;
+ gap: 0.5rem;
}
-.title {
+.description p {
+ position: relative;
margin: 0;
- line-height: 1.15;
- font-size: 4rem;
- font-style: normal;
- font-weight: 800;
- letter-spacing: -0.025em;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
}
-.title a {
- text-decoration: none;
- color: #0070f3;
+.code {
+ font-weight: 700;
+ font-family: var(--font-mono);
}
-.title a:hover,
-.title a:focus,
-.title a:active {
- text-decoration: underline;
+.grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(33%, auto));
+ width: var(--max-width);
+ max-width: 100%;
}
-.title,
-.description {
- text-align: center;
+.card {
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition: background 200ms, border 200ms;
}
-.description {
- margin: 4rem 0;
- line-height: 1.5;
- font-size: 1.5rem;
+.card span {
+ display: inline-block;
+ transition: transform 200ms;
}
-.code {
- background: #fafafa;
- border-radius: 5px;
- padding: 0.75rem;
- font-size: 1.1rem;
- font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
- Bitstream Vera Sans Mono, Courier New, monospace;
+.card h2 {
+ font-weight: 600;
+ margin-bottom: 0.7rem;
}
-.grid {
+.card p {
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 34ch;
+}
+
+.center {
display: flex;
+ justify-content: center;
align-items: center;
+ position: relative;
+ padding: 4rem 0;
+}
+
+.center::before {
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
+}
+
+.center::after {
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
+}
+
+.center::before,
+.center::after {
+ content: '';
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
+}
+
+.logo,
+.thirteen {
+ position: relative;
+}
+
+.thirteen {
+ display: flex;
justify-content: center;
- flex-wrap: wrap;
- max-width: 1200px;
+ align-items: center;
+ width: 75px;
+ height: 75px;
+ padding: 25px 10px;
+ margin-left: 16px;
+ transform: translateZ(0);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ box-shadow: 0px 2px 8px -1px #0000001a;
}
-.card {
- margin: 1rem;
- padding: 1.5rem;
- text-align: left;
- color: inherit;
- text-decoration: none;
- border: 1px solid #eaeaea;
- border-radius: 10px;
- transition: color 0.15s ease, border-color 0.15s ease;
- max-width: 300px;
+.thirteen::before,
+.thirteen::after {
+ content: '';
+ position: absolute;
+ z-index: -1;
}
-.card:hover,
-.card:focus,
-.card:active {
- color: #0070f3;
- border-color: #0070f3;
+/* Conic Gradient Animation */
+.thirteen::before {
+ animation: 6s rotate linear infinite;
+ width: 200%;
+ height: 200%;
+ background: var(--tile-border);
}
-.card h2 {
- margin: 0 0 1rem 0;
- font-size: 1.5rem;
+/* Inner Square */
+.thirteen::after {
+ inset: 0;
+ padding: 1px;
+ border-radius: var(--border-radius);
+ background: linear-gradient(
+ to bottom right,
+ rgba(var(--tile-start-rgb), 1),
+ rgba(var(--tile-end-rgb), 1)
+ );
+ background-clip: content-box;
}
-.card p {
- margin: 0;
- font-size: 1.25rem;
- line-height: 1.5;
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
+
+ .card:hover span {
+ transform: translateX(4px);
+ }
}
-.logo {
- height: 1em;
- margin-left: 0.5rem;
+@media (prefers-reduced-motion) {
+ .thirteen::before {
+ animation: none;
+ }
+
+ .card:hover span {
+ transform: none;
+ }
}
-@media (max-width: 600px) {
+/* Mobile and Tablet */
+@media (max-width: 1023px) {
+ .content {
+ padding: 4rem;
+ }
+
.grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
width: 100%;
- flex-direction: column;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(
+ to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5)
+ );
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(
+ to bottom,
+ transparent 0%,
+ rgb(var(--background-end-rgb)) 40%
+ );
+ z-index: 1;
}
}
@media (prefers-color-scheme: dark) {
- .title {
- background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- text-fill-color: transparent;
- }
- .title a {
- background: linear-gradient(180deg, #0070f3 0%, #0153af 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- text-fill-color: transparent;
- }
- .card,
- .footer {
- border-color: #222;
- }
- .code {
- background: #111;
- }
- .logo img {
+ .vercelLogo {
filter: invert(1);
}
+
+ .logo,
+ .thirteen img {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
+ }
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
}
diff --git a/packages/create-next-app/templates/app/js/gitignore b/packages/create-next-app/templates/app/js/gitignore
index c87c9b392c020..55175ef867e0b 100644
--- a/packages/create-next-app/templates/app/js/gitignore
+++ b/packages/create-next-app/templates/app/js/gitignore
@@ -30,7 +30,3 @@ yarn-error.log*
# vercel
.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
diff --git a/packages/create-next-app/templates/app/js/public/next.svg b/packages/create-next-app/templates/app/js/public/next.svg
new file mode 100644
index 0000000000000..5174b28c565c2
--- /dev/null
+++ b/packages/create-next-app/templates/app/js/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/app/js/public/thirteen.svg b/packages/create-next-app/templates/app/js/public/thirteen.svg
new file mode 100644
index 0000000000000..8977c1bd123cb
--- /dev/null
+++ b/packages/create-next-app/templates/app/js/public/thirteen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/app/js/public/vercel.svg b/packages/create-next-app/templates/app/js/public/vercel.svg
index fbf0e25a651c2..d2f84222734f2 100644
--- a/packages/create-next-app/templates/app/js/public/vercel.svg
+++ b/packages/create-next-app/templates/app/js/public/vercel.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/app/ts/app/globals.css b/packages/create-next-app/templates/app/ts/app/globals.css
index 4f1842163d222..3a54ccd80be42 100644
--- a/packages/create-next-app/templates/app/ts/app/globals.css
+++ b/packages/create-next-app/templates/app/ts/app/globals.css
@@ -1,9 +1,98 @@
-html,
-body {
+: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;
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
- Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+body {
+ color: rbg(--foreground-rgb);
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ rgb(var(--background-end-rgb))
+ )
+ rgb(var(--background-start-rgb));
}
a {
@@ -11,16 +100,8 @@ a {
text-decoration: none;
}
-* {
- box-sizing: border-box;
-}
-
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
- body {
- color: white;
- background: black;
- }
}
diff --git a/packages/create-next-app/templates/app/ts/app/page.module.css b/packages/create-next-app/templates/app/ts/app/page.module.css
index a978c99de57b3..4732b55fdc237 100644
--- a/packages/create-next-app/templates/app/ts/app/page.module.css
+++ b/packages/create-next-app/templates/app/ts/app/page.module.css
@@ -1,146 +1,271 @@
-.container {
- padding: 0 2rem;
-}
-
.main {
- min-height: 100vh;
- padding: 4rem 0;
- flex: 1;
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
}
-.footer {
- display: flex;
- flex: 1;
- padding: 2rem 0;
- border-top: 1px solid #eaeaea;
- justify-content: center;
- align-items: center;
+.description {
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
}
-.footer a {
+.description a {
display: flex;
- justify-content: center;
align-items: center;
- flex-grow: 1;
+ justify-content: center;
+ gap: 0.5rem;
}
-.title {
+.description p {
+ position: relative;
margin: 0;
- line-height: 1.15;
- font-size: 4rem;
- font-style: normal;
- font-weight: 800;
- letter-spacing: -0.025em;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
}
-.title a {
- text-decoration: none;
- color: #0070f3;
+.code {
+ font-weight: 700;
+ font-family: var(--font-mono);
}
-.title a:hover,
-.title a:focus,
-.title a:active {
- text-decoration: underline;
+.grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(33%, auto));
+ width: var(--max-width);
+ max-width: 100%;
}
-.title,
-.description {
- text-align: center;
+.card {
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition: background 200ms, border 200ms;
}
-.description {
- margin: 4rem 0;
- line-height: 1.5;
- font-size: 1.5rem;
+.card span {
+ display: inline-block;
+ transition: transform 200ms;
}
-.code {
- background: #fafafa;
- border-radius: 5px;
- padding: 0.75rem;
- font-size: 1.1rem;
- font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
- Bitstream Vera Sans Mono, Courier New, monospace;
+.card h2 {
+ font-weight: 600;
+ margin-bottom: 0.7rem;
}
-.grid {
+.card p {
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 34ch;
+}
+
+.center {
display: flex;
+ justify-content: center;
align-items: center;
+ position: relative;
+ padding: 4rem 0;
+}
+
+.center::before {
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
+}
+
+.center::after {
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
+}
+
+.center::before,
+.center::after {
+ content: '';
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
+}
+
+.logo,
+.thirteen {
+ position: relative;
+}
+
+.thirteen {
+ display: flex;
justify-content: center;
- flex-wrap: wrap;
- max-width: 1200px;
+ align-items: center;
+ width: 75px;
+ height: 75px;
+ padding: 25px 10px;
+ margin-left: 16px;
+ transform: translateZ(0);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ box-shadow: 0px 2px 8px -1px #0000001a;
}
-.card {
- margin: 1rem;
- padding: 1.5rem;
- text-align: left;
- color: inherit;
- text-decoration: none;
- border: 1px solid #eaeaea;
- border-radius: 10px;
- transition: color 0.15s ease, border-color 0.15s ease;
- max-width: 300px;
+.thirteen::before,
+.thirteen::after {
+ content: '';
+ position: absolute;
+ z-index: -1;
}
-.card:hover,
-.card:focus,
-.card:active {
- color: #0070f3;
- border-color: #0070f3;
+/* Conic Gradient Animation */
+.thirteen::before {
+ animation: 6s rotate linear infinite;
+ width: 200%;
+ height: 200%;
+ background: var(--tile-border);
}
-.card h2 {
- margin: 0 0 1rem 0;
- font-size: 1.5rem;
+/* Inner Square */
+.thirteen::after {
+ inset: 0;
+ padding: 1px;
+ border-radius: var(--border-radius);
+ background: linear-gradient(
+ to bottom right,
+ rgba(var(--tile-start-rgb), 1),
+ rgba(var(--tile-end-rgb), 1)
+ );
+ background-clip: content-box;
}
-.card p {
- margin: 0;
- font-size: 1.25rem;
- line-height: 1.5;
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
+
+ .card:hover span {
+ transform: translateX(4px);
+ }
}
-.logo {
- height: 1em;
- margin-left: 0.5rem;
+@media (prefers-reduced-motion) {
+ .thirteen::before {
+ animation: none;
+ }
+
+ .card:hover span {
+ transform: none;
+ }
}
-@media (max-width: 600px) {
+/* Mobile and Tablet */
+@media (max-width: 1023px) {
+ .content {
+ padding: 4rem;
+ }
+
.grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
width: 100%;
- flex-direction: column;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(
+ to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5)
+ );
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(
+ to bottom,
+ transparent 0%,
+ rgb(var(--background-end-rgb)) 40%
+ );
+ z-index: 1;
}
}
@media (prefers-color-scheme: dark) {
- .title {
- background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- text-fill-color: transparent;
- }
- .title a {
- background: linear-gradient(180deg, #0070f3 0%, #0153af 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- text-fill-color: transparent;
- }
- .card,
- .footer {
- border-color: #222;
- }
- .code {
- background: #111;
- }
- .logo img {
+ .vercelLogo {
filter: invert(1);
}
+
+ .logo,
+ .thirteen img {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
+ }
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
}
diff --git a/packages/create-next-app/templates/app/ts/app/page.tsx b/packages/create-next-app/templates/app/ts/app/page.tsx
index 35bd2649d4cca..47fcc959cf52b 100644
--- a/packages/create-next-app/templates/app/ts/app/page.tsx
+++ b/packages/create-next-app/templates/app/ts/app/page.tsx
@@ -1,57 +1,91 @@
import Image from 'next/image'
+import { Inter } from '@next/font/google'
import styles from './page.module.css'
+const inter = Inter({ subsets: ['latin'] })
+
export default function Home() {
return (
-
-
-
-
-
- Get started by editing{' '}
+
+
+
+ Get started by editing
app/page.tsx
-
-
+
+
+
+
+
+
)
}
diff --git a/packages/create-next-app/templates/app/ts/public/next.svg b/packages/create-next-app/templates/app/ts/public/next.svg
new file mode 100644
index 0000000000000..5174b28c565c2
--- /dev/null
+++ b/packages/create-next-app/templates/app/ts/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/app/ts/public/thirteen.svg b/packages/create-next-app/templates/app/ts/public/thirteen.svg
new file mode 100644
index 0000000000000..8977c1bd123cb
--- /dev/null
+++ b/packages/create-next-app/templates/app/ts/public/thirteen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/app/ts/public/vercel.svg b/packages/create-next-app/templates/app/ts/public/vercel.svg
index fbf0e25a651c2..d2f84222734f2 100644
--- a/packages/create-next-app/templates/app/ts/public/vercel.svg
+++ b/packages/create-next-app/templates/app/ts/public/vercel.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/js/gitignore b/packages/create-next-app/templates/default/js/gitignore
index c87c9b392c020..55175ef867e0b 100644
--- a/packages/create-next-app/templates/default/js/gitignore
+++ b/packages/create-next-app/templates/default/js/gitignore
@@ -30,7 +30,3 @@ yarn-error.log*
# vercel
.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
diff --git a/packages/create-next-app/templates/default/js/next.config.js b/packages/create-next-app/templates/default/js/next.config.js
index a843cbee09afa..ae887958d3c9c 100644
--- a/packages/create-next-app/templates/default/js/next.config.js
+++ b/packages/create-next-app/templates/default/js/next.config.js
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
+ swcMinify: true,
}
module.exports = nextConfig
diff --git a/packages/create-next-app/templates/default/js/pages/_app.js b/packages/create-next-app/templates/default/js/pages/_app.js
index 1e1cec92425c8..048541e25b1dd 100644
--- a/packages/create-next-app/templates/default/js/pages/_app.js
+++ b/packages/create-next-app/templates/default/js/pages/_app.js
@@ -1,7 +1,5 @@
import '../styles/globals.css'
-function MyApp({ Component, pageProps }) {
+export default function App({ Component, pageProps }) {
return
}
-
-export default MyApp
diff --git a/packages/create-next-app/templates/default/js/pages/_document.js b/packages/create-next-app/templates/default/js/pages/_document.js
new file mode 100644
index 0000000000000..54e8bf3e2a290
--- /dev/null
+++ b/packages/create-next-app/templates/default/js/pages/_document.js
@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from 'next/document'
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/create-next-app/templates/default/js/pages/index.js b/packages/create-next-app/templates/default/js/pages/index.js
index 5bf5574341670..dc5a1cce79a7d 100644
--- a/packages/create-next-app/templates/default/js/pages/index.js
+++ b/packages/create-next-app/templates/default/js/pages/index.js
@@ -1,71 +1,123 @@
import Head from 'next/head'
import Image from 'next/image'
+import { Inter } from '@next/font/google'
import styles from '../styles/Home.module.css'
+const inter = Inter({ subsets: ['latin'] })
+
export default function Home() {
return (
-
+ <>
Create Next App
+
-
-
+
+
+ Get started by editing
+ pages/index.js
+
+
+
-
- Get started by editing{' '}
- pages/index.js
-
+
-
-
-
+ >
)
}
diff --git a/packages/create-next-app/templates/default/js/public/next.svg b/packages/create-next-app/templates/default/js/public/next.svg
new file mode 100644
index 0000000000000..5174b28c565c2
--- /dev/null
+++ b/packages/create-next-app/templates/default/js/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/js/public/thirteen.svg b/packages/create-next-app/templates/default/js/public/thirteen.svg
new file mode 100644
index 0000000000000..8977c1bd123cb
--- /dev/null
+++ b/packages/create-next-app/templates/default/js/public/thirteen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/js/public/vercel.svg b/packages/create-next-app/templates/default/js/public/vercel.svg
index fbf0e25a651c2..d2f84222734f2 100644
--- a/packages/create-next-app/templates/default/js/public/vercel.svg
+++ b/packages/create-next-app/templates/default/js/public/vercel.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/js/styles/Home.module.css b/packages/create-next-app/templates/default/js/styles/Home.module.css
index bd50f42ffe6a2..27dfff5ec4cf8 100644
--- a/packages/create-next-app/templates/default/js/styles/Home.module.css
+++ b/packages/create-next-app/templates/default/js/styles/Home.module.css
@@ -1,129 +1,278 @@
-.container {
- padding: 0 2rem;
-}
-
.main {
- min-height: 100vh;
- padding: 4rem 0;
- flex: 1;
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
}
-.footer {
- display: flex;
- flex: 1;
- padding: 2rem 0;
- border-top: 1px solid #eaeaea;
- justify-content: center;
- align-items: center;
+.description {
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
}
-.footer a {
+.description a {
display: flex;
justify-content: center;
align-items: center;
- flex-grow: 1;
+ gap: 0.5rem;
}
-.title a {
- color: #0070f3;
- text-decoration: none;
+.description p {
+ position: relative;
+ margin: 0;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
}
-.title a:hover,
-.title a:focus,
-.title a:active {
- text-decoration: underline;
+.code {
+ font-weight: 700;
+ font-family: var(--font-mono);
}
-.title {
- margin: 0;
- line-height: 1.15;
- font-size: 4rem;
+.grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(25%, auto));
+ width: var(--max-width);
+ max-width: 100%;
}
-.title,
-.description {
- text-align: center;
+.card {
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition: background 200ms, border 200ms;
}
-.description {
- margin: 4rem 0;
- line-height: 1.5;
- font-size: 1.5rem;
+.card span {
+ display: inline-block;
+ transition: transform 200ms;
}
-.code {
- background: #fafafa;
- border-radius: 5px;
- padding: 0.75rem;
- font-size: 1.1rem;
- font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
- Bitstream Vera Sans Mono, Courier New, monospace;
+.card h2 {
+ font-weight: 600;
+ margin-bottom: 0.7rem;
}
-.grid {
+.card p {
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 30ch;
+}
+
+.center {
display: flex;
+ justify-content: center;
align-items: center;
+ position: relative;
+ padding: 4rem 0;
+}
+
+.center::before {
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
+}
+
+.center::after {
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
+}
+
+.center::before,
+.center::after {
+ content: '';
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
+}
+
+.logo,
+.thirteen {
+ position: relative;
+}
+
+.thirteen {
+ display: flex;
justify-content: center;
- flex-wrap: wrap;
- max-width: 800px;
+ align-items: center;
+ width: 75px;
+ height: 75px;
+ padding: 25px 10px;
+ margin-left: 16px;
+ transform: translateZ(0);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ box-shadow: 0px 2px 8px -1px #0000001a;
}
-.card {
- margin: 1rem;
- padding: 1.5rem;
- text-align: left;
- color: inherit;
- text-decoration: none;
- border: 1px solid #eaeaea;
- border-radius: 10px;
- transition: color 0.15s ease, border-color 0.15s ease;
- max-width: 300px;
+.thirteen::before,
+.thirteen::after {
+ content: '';
+ position: absolute;
+ z-index: -1;
}
-.card:hover,
-.card:focus,
-.card:active {
- color: #0070f3;
- border-color: #0070f3;
+/* Conic Gradient Animation */
+.thirteen::before {
+ animation: 6s rotate linear infinite;
+ width: 200%;
+ height: 200%;
+ background: var(--tile-border);
}
-.card h2 {
- margin: 0 0 1rem 0;
- font-size: 1.5rem;
+/* Inner Square */
+.thirteen::after {
+ inset: 0;
+ padding: 1px;
+ border-radius: var(--border-radius);
+ background: linear-gradient(
+ to bottom right,
+ rgba(var(--tile-start-rgb), 1),
+ rgba(var(--tile-end-rgb), 1)
+ );
+ background-clip: content-box;
}
-.card p {
- margin: 0;
- font-size: 1.25rem;
- line-height: 1.5;
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
+
+ .card:hover span {
+ transform: translateX(4px);
+ }
}
-.logo {
- height: 1em;
- margin-left: 0.5rem;
+@media (prefers-reduced-motion) {
+ .thirteen::before {
+ animation: none;
+ }
+
+ .card:hover span {
+ transform: none;
+ }
}
-@media (max-width: 600px) {
+/* Mobile */
+@media (max-width: 700px) {
+ .content {
+ padding: 4rem;
+ }
+
.grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
width: 100%;
- flex-direction: column;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(
+ to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5)
+ );
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(
+ to bottom,
+ transparent 0%,
+ rgb(var(--background-end-rgb)) 40%
+ );
+ z-index: 1;
+ }
+}
+
+/* Tablet and Smaller Desktop */
+@media (min-width: 701px) and (max-width: 1120px) {
+ .grid {
+ grid-template-columns: repeat(2, 50%);
}
}
@media (prefers-color-scheme: dark) {
- .card,
- .footer {
- border-color: #222;
+ .vercelLogo {
+ filter: invert(1);
}
- .code {
- background: #111;
+
+ .logo,
+ .thirteen img {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
}
- .logo img {
- filter: invert(1);
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
}
}
diff --git a/packages/create-next-app/templates/default/js/styles/globals.css b/packages/create-next-app/templates/default/js/styles/globals.css
index 4f1842163d222..3a54ccd80be42 100644
--- a/packages/create-next-app/templates/default/js/styles/globals.css
+++ b/packages/create-next-app/templates/default/js/styles/globals.css
@@ -1,9 +1,98 @@
-html,
-body {
+: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;
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
- Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+body {
+ color: rbg(--foreground-rgb);
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ rgb(var(--background-end-rgb))
+ )
+ rgb(var(--background-start-rgb));
}
a {
@@ -11,16 +100,8 @@ a {
text-decoration: none;
}
-* {
- box-sizing: border-box;
-}
-
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
- body {
- color: white;
- background: black;
- }
}
diff --git a/packages/create-next-app/templates/default/ts/pages/_document.tsx b/packages/create-next-app/templates/default/ts/pages/_document.tsx
new file mode 100644
index 0000000000000..54e8bf3e2a290
--- /dev/null
+++ b/packages/create-next-app/templates/default/ts/pages/_document.tsx
@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from 'next/document'
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/create-next-app/templates/default/ts/pages/index.tsx b/packages/create-next-app/templates/default/ts/pages/index.tsx
index cdffa57ca8254..c08b1b34a0f94 100644
--- a/packages/create-next-app/templates/default/ts/pages/index.tsx
+++ b/packages/create-next-app/templates/default/ts/pages/index.tsx
@@ -1,71 +1,123 @@
import Head from 'next/head'
import Image from 'next/image'
+import { Inter } from '@next/font/google'
import styles from '../styles/Home.module.css'
+const inter = Inter({ subsets: ['latin'] })
+
export default function Home() {
return (
-
+ <>
Create Next App
+
-
-
+
+
+ Get started by editing
+ pages/index.tsx
+
+
+
-
- Get started by editing{' '}
- pages/index.tsx
-
+
-
-
-
+ >
)
}
diff --git a/packages/create-next-app/templates/default/ts/public/next.svg b/packages/create-next-app/templates/default/ts/public/next.svg
new file mode 100644
index 0000000000000..5174b28c565c2
--- /dev/null
+++ b/packages/create-next-app/templates/default/ts/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/ts/public/thirteen.svg b/packages/create-next-app/templates/default/ts/public/thirteen.svg
new file mode 100644
index 0000000000000..8977c1bd123cb
--- /dev/null
+++ b/packages/create-next-app/templates/default/ts/public/thirteen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/ts/public/vercel.svg b/packages/create-next-app/templates/default/ts/public/vercel.svg
index fbf0e25a651c2..d2f84222734f2 100644
--- a/packages/create-next-app/templates/default/ts/public/vercel.svg
+++ b/packages/create-next-app/templates/default/ts/public/vercel.svg
@@ -1,4 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/packages/create-next-app/templates/default/ts/styles/Home.module.css b/packages/create-next-app/templates/default/ts/styles/Home.module.css
index bd50f42ffe6a2..27dfff5ec4cf8 100644
--- a/packages/create-next-app/templates/default/ts/styles/Home.module.css
+++ b/packages/create-next-app/templates/default/ts/styles/Home.module.css
@@ -1,129 +1,278 @@
-.container {
- padding: 0 2rem;
-}
-
.main {
- min-height: 100vh;
- padding: 4rem 0;
- flex: 1;
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
}
-.footer {
- display: flex;
- flex: 1;
- padding: 2rem 0;
- border-top: 1px solid #eaeaea;
- justify-content: center;
- align-items: center;
+.description {
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
}
-.footer a {
+.description a {
display: flex;
justify-content: center;
align-items: center;
- flex-grow: 1;
+ gap: 0.5rem;
}
-.title a {
- color: #0070f3;
- text-decoration: none;
+.description p {
+ position: relative;
+ margin: 0;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
}
-.title a:hover,
-.title a:focus,
-.title a:active {
- text-decoration: underline;
+.code {
+ font-weight: 700;
+ font-family: var(--font-mono);
}
-.title {
- margin: 0;
- line-height: 1.15;
- font-size: 4rem;
+.grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(25%, auto));
+ width: var(--max-width);
+ max-width: 100%;
}
-.title,
-.description {
- text-align: center;
+.card {
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition: background 200ms, border 200ms;
}
-.description {
- margin: 4rem 0;
- line-height: 1.5;
- font-size: 1.5rem;
+.card span {
+ display: inline-block;
+ transition: transform 200ms;
}
-.code {
- background: #fafafa;
- border-radius: 5px;
- padding: 0.75rem;
- font-size: 1.1rem;
- font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
- Bitstream Vera Sans Mono, Courier New, monospace;
+.card h2 {
+ font-weight: 600;
+ margin-bottom: 0.7rem;
}
-.grid {
+.card p {
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 30ch;
+}
+
+.center {
display: flex;
+ justify-content: center;
align-items: center;
+ position: relative;
+ padding: 4rem 0;
+}
+
+.center::before {
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
+}
+
+.center::after {
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
+}
+
+.center::before,
+.center::after {
+ content: '';
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
+}
+
+.logo,
+.thirteen {
+ position: relative;
+}
+
+.thirteen {
+ display: flex;
justify-content: center;
- flex-wrap: wrap;
- max-width: 800px;
+ align-items: center;
+ width: 75px;
+ height: 75px;
+ padding: 25px 10px;
+ margin-left: 16px;
+ transform: translateZ(0);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ box-shadow: 0px 2px 8px -1px #0000001a;
}
-.card {
- margin: 1rem;
- padding: 1.5rem;
- text-align: left;
- color: inherit;
- text-decoration: none;
- border: 1px solid #eaeaea;
- border-radius: 10px;
- transition: color 0.15s ease, border-color 0.15s ease;
- max-width: 300px;
+.thirteen::before,
+.thirteen::after {
+ content: '';
+ position: absolute;
+ z-index: -1;
}
-.card:hover,
-.card:focus,
-.card:active {
- color: #0070f3;
- border-color: #0070f3;
+/* Conic Gradient Animation */
+.thirteen::before {
+ animation: 6s rotate linear infinite;
+ width: 200%;
+ height: 200%;
+ background: var(--tile-border);
}
-.card h2 {
- margin: 0 0 1rem 0;
- font-size: 1.5rem;
+/* Inner Square */
+.thirteen::after {
+ inset: 0;
+ padding: 1px;
+ border-radius: var(--border-radius);
+ background: linear-gradient(
+ to bottom right,
+ rgba(var(--tile-start-rgb), 1),
+ rgba(var(--tile-end-rgb), 1)
+ );
+ background-clip: content-box;
}
-.card p {
- margin: 0;
- font-size: 1.25rem;
- line-height: 1.5;
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
+
+ .card:hover span {
+ transform: translateX(4px);
+ }
}
-.logo {
- height: 1em;
- margin-left: 0.5rem;
+@media (prefers-reduced-motion) {
+ .thirteen::before {
+ animation: none;
+ }
+
+ .card:hover span {
+ transform: none;
+ }
}
-@media (max-width: 600px) {
+/* Mobile */
+@media (max-width: 700px) {
+ .content {
+ padding: 4rem;
+ }
+
.grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
width: 100%;
- flex-direction: column;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(
+ to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5)
+ );
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(
+ to bottom,
+ transparent 0%,
+ rgb(var(--background-end-rgb)) 40%
+ );
+ z-index: 1;
+ }
+}
+
+/* Tablet and Smaller Desktop */
+@media (min-width: 701px) and (max-width: 1120px) {
+ .grid {
+ grid-template-columns: repeat(2, 50%);
}
}
@media (prefers-color-scheme: dark) {
- .card,
- .footer {
- border-color: #222;
+ .vercelLogo {
+ filter: invert(1);
}
- .code {
- background: #111;
+
+ .logo,
+ .thirteen img {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
}
- .logo img {
- filter: invert(1);
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
}
}
diff --git a/packages/create-next-app/templates/default/ts/styles/globals.css b/packages/create-next-app/templates/default/ts/styles/globals.css
index 4f1842163d222..3a54ccd80be42 100644
--- a/packages/create-next-app/templates/default/ts/styles/globals.css
+++ b/packages/create-next-app/templates/default/ts/styles/globals.css
@@ -1,9 +1,98 @@
-html,
-body {
+: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;
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
- Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+body {
+ color: rbg(--foreground-rgb);
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ rgb(var(--background-end-rgb))
+ )
+ rgb(var(--background-start-rgb));
}
a {
@@ -11,16 +100,8 @@ a {
text-decoration: none;
}
-* {
- box-sizing: border-box;
-}
-
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
- body {
- color: white;
- background: black;
- }
}
diff --git a/packages/create-next-app/templates/index.ts b/packages/create-next-app/templates/index.ts
index 0863fc87f46cb..77cb9bd066642 100644
--- a/packages/create-next-app/templates/index.ts
+++ b/packages/create-next-app/templates/index.ts
@@ -62,7 +62,7 @@ export const installTemplate = async ({
/**
* Default dependencies.
*/
- const dependencies = ['react', 'react-dom', 'next']
+ const dependencies = ['react', 'react-dom', 'next', '@next/font']
/**
* TypeScript projects will have type definitions and other devDependencies.
*/
diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json
index 47086fd1b4763..714546a439e17 100644
--- a/packages/eslint-config-next/package.json
+++ b/packages/eslint-config-next/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "ESLint configuration used by NextJS.",
"main": "index.js",
"license": "MIT",
@@ -9,7 +9,7 @@
"directory": "packages/eslint-config-next"
},
"dependencies": {
- "@next/eslint-plugin-next": "13.0.7-canary.0",
+ "@next/eslint-plugin-next": "13.0.7-canary.1",
"@rushstack/eslint-patch": "^1.1.3",
"@typescript-eslint/parser": "^5.42.0",
"eslint-import-resolver-node": "^0.3.6",
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index bfc45f16a5c46..ee0eabd541361 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "ESLint plugin for NextJS.",
"main": "dist/index.js",
"license": "MIT",
diff --git a/packages/font/package.json b/packages/font/package.json
index 2336f0e622f60..c7ffc7b8a6145 100644
--- a/packages/font/package.json
+++ b/packages/font/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/font",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 52502b564cfed..af87dfcb43bb1 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 677499b7ca04f..a7c0266a507cd 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
diff --git a/packages/next-env/package.json b/packages/next-env/package.json
index 2e61458eec8e0..94b718992f34a 100644
--- a/packages/next-env/package.json
+++ b/packages/next-env/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/env",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"keywords": [
"react",
"next",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index 08773d8f49b4e..9bad890f09d95 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index 7350790fb2693..2585a62e1c745 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json
index b12f852947b35..d9aede9326438 100644
--- a/packages/next-polyfill-module/package.json
+++ b/packages/next-polyfill-module/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-module",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)",
"main": "dist/polyfill-module.js",
"license": "MIT",
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index a94252a80ec4a..67003ea9da198 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json
index afdef09838eb1..d18896a57b925 100644
--- a/packages/next-swc/package.json
+++ b/packages/next-swc/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/swc",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"private": true,
"scripts": {
"build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin --js false native",
diff --git a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts
index 577fc98d4b17d..03c53fcf7e4eb 100644
--- a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts
+++ b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts
@@ -115,8 +115,8 @@ const postcssFontLoaderPlugn = ({
const isRange = (value: string) => value.trim().includes(' ')
const formattedFontFamilies = [
formatFamily(fontFamily),
- ...fallbackFonts,
...(adjustFontFallbackFamily ? [adjustFontFallbackFamily] : []),
+ ...fallbackFonts,
].join(', ')
// Add class with family, weight and style
const classRule = new postcss.Rule({ selector: '.className' })
diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts
index 622d837a01b04..57e2ff6ee13a3 100644
--- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts
+++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts
@@ -561,7 +561,6 @@ export class FlightClientEntryPlugin {
return shouldInvalidate
}
- // TODO-APP: make sure dependsOn is added for layouts/pages
addEntry(
compilation: any,
context: string,
diff --git a/packages/next/client/components/app-router-headers.ts b/packages/next/client/components/app-router-headers.ts
index de00963e625e7..12d2a3dccd025 100644
--- a/packages/next/client/components/app-router-headers.ts
+++ b/packages/next/client/components/app-router-headers.ts
@@ -1,6 +1,7 @@
export const RSC = 'RSC' as const
export const NEXT_ROUTER_STATE_TREE = 'Next-Router-State-Tree' as const
export const NEXT_ROUTER_PREFETCH = 'Next-Router-Prefetch' as const
+export const FETCH_CACHE_HEADER = 'x-vercel-sc-headers' as const
export const RSC_VARY_HEADER =
`${RSC}, ${NEXT_ROUTER_STATE_TREE}, ${NEXT_ROUTER_PREFETCH}` as const
diff --git a/packages/next/client/components/app-router.tsx b/packages/next/client/components/app-router.tsx
index 15b1420fa5c94..7de15e24bdd31 100644
--- a/packages/next/client/components/app-router.tsx
+++ b/packages/next/client/components/app-router.tsx
@@ -95,7 +95,6 @@ export async function fetchServerResponse(
}
// Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode.
-// TODO-APP: move this back into AppRouter
let initialParallelRoutes: CacheNode['parallelRoutes'] =
typeof window === 'undefined' ? null! : new Map()
@@ -254,7 +253,6 @@ function Router({
const routerInstance: AppRouterInstance = {
back: () => window.history.back(),
forward: () => window.history.forward(),
- // TODO-APP: implement prefetching of flight
prefetch: async (href) => {
// If prefetch has already been triggered, don't trigger it again.
if (prefetched.has(href)) {
@@ -264,7 +262,6 @@ function Router({
const url = new URL(href, location.origin)
try {
const routerTree = window.history.state?.tree || initialTree
- // TODO-APP: handle case where history.state is not the new router history entry
const serverResponse = await fetchServerResponse(
url,
// initialTree is used when history.state.tree is missing because the history state is set in `useEffect` below, it being missing means this is the hydration case.
@@ -301,8 +298,6 @@ function Router({
React.startTransition(() => {
dispatch({
type: ACTION_REFRESH,
-
- // TODO-APP: revisit if this needs to be passed.
cache: {
status: CacheStates.LAZY_INITIALIZED,
data: null,
@@ -363,8 +358,7 @@ function Router({
return
}
- // TODO-APP: this case happens when pushState/replaceState was called outside of Next.js or when the history entry was pushed by the old router.
- // It reloads the page in this case but we might have to revisit this as the old router ignores it.
+ // This case happens when the history entry was pushed by the `pages` router.
if (!state.__NA) {
window.location.reload()
return
diff --git a/packages/next/client/components/layout-router.tsx b/packages/next/client/components/layout-router.tsx
index 0c6b265239ef5..6f85280383185 100644
--- a/packages/next/client/components/layout-router.tsx
+++ b/packages/next/client/components/layout-router.tsx
@@ -209,7 +209,6 @@ export function InnerLayoutRouter({
// If cache node has a data request we have to unwrap response by `use` and update the cache.
if (childNode.data) {
- // TODO-APP: error case
/**
* Flight response data
*/
@@ -229,7 +228,6 @@ export function InnerLayoutRouter({
setTimeout(() => {
// @ts-ignore startTransition exists
React.startTransition(() => {
- // TODO-APP: handle redirect
changeByServerResponse(fullTree, flightData, overrideCanonicalUrl)
})
})
diff --git a/packages/next/client/components/navigation.ts b/packages/next/client/components/navigation.ts
index 99bc8f4c8d463..2c8990a9e64a8 100644
--- a/packages/next/client/components/navigation.ts
+++ b/packages/next/client/components/navigation.ts
@@ -109,7 +109,6 @@ export {
useServerInsertedHTML,
} from '../../shared/lib/server-inserted-html'
-// TODO-APP: Move the other router context over to this one
/**
* Get the router methods. For example router.push('/dashboard')
*/
diff --git a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
index bb27bc94aedbd..84d83cd46f58c 100644
--- a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
+++ b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx
@@ -139,7 +139,7 @@ export const Errors: React.FC = function Errors({ errors }) {
const [displayState, setDisplayState] = React.useState<
'minimized' | 'fullscreen' | 'hidden'
- >('minimized')
+ >('fullscreen')
const [activeIdx, setActiveIndex] = React.useState(0)
const previous = React.useCallback((e?: MouseEvent | TouchEvent) => {
e?.preventDefault()
diff --git a/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx b/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx
index e9d9648b87f67..5e68fb856175e 100644
--- a/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx
+++ b/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx
@@ -14,7 +14,7 @@ export type RootLayoutErrorProps = { missingTags: string[] }
export const RootLayoutError: React.FC =
function BuildError({ missingTags }) {
const message =
- 'Please make sure to include the following tags in your root layout: , .\n\n' +
+ 'Please make sure to include the following tags in your root layout: , , .\n\n' +
`Missing required root layout tag${
missingTags.length === 1 ? '' : 's'
}: ` +
diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts
index 350163fe646fe..6828faf6eb0a2 100644
--- a/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts
+++ b/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts
@@ -214,7 +214,6 @@ function getArgumentsForLineNumber(
function guessEditor(): string[] {
// Explicit config always wins
if (process.env.REACT_EDITOR) {
- // TODO-APP: fix this type
return shellQuote.parse(process.env.REACT_EDITOR) as any
}
diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts
index 91405df55e1b6..615e7cddbda08 100644
--- a/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts
+++ b/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts
@@ -55,7 +55,6 @@ export function useWebsocketPing(
sendMessage(
JSON.stringify({
event: 'ping',
- // TODO-APP: fix case for dynamic parameters, this will be resolved wrong currently.
tree,
appDirRoute: true,
})
diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts
index 1a3760124e47d..93898186419ee 100644
--- a/packages/next/client/components/reducer.ts
+++ b/packages/next/client/components/reducer.ts
@@ -54,7 +54,6 @@ export function createHrefFromUrl(
/**
* Invalidate cache one level down from the router state.
*/
-// TODO-APP: Verify if this needs to be recursive.
function invalidateCacheByRouterState(
newCache: CacheNode,
existingCache: CacheNode,
@@ -472,12 +471,6 @@ function createOptimisticTree(
result[3] = 'refetch'
}
- // TODO-APP: Revisit
- // Add url into the tree
- // if (isFirstSegment) {
- // result[2] = href
- // }
-
return result
}
@@ -495,10 +488,6 @@ function applyRouterStatePatchToTree(
if (flightSegmentPath.length === 1) {
const tree: FlightRouterState = [...treePatch]
- // TODO-APP: revisit
- // if (url) {
- // tree[2] = url
- // }
return tree
}
@@ -539,11 +528,6 @@ function applyRouterStatePatchToTree(
tree[4] = true
}
- // TODO-APP: Revisit
- // if (url) {
- // tree[2] = url
- // }
-
return tree
}
@@ -812,7 +796,6 @@ function clientReducer(
canonicalUrl: mutable.canonicalUrlOverride
? mutable.canonicalUrlOverride
: href,
- // TODO-APP: verify mpaNavigation not being set is correct here.
pushRef: {
pendingPush,
mpaNavigation: mutable.mpaNavigation,
@@ -834,7 +817,6 @@ function clientReducer(
canonicalUrl: mutable.canonicalUrlOverride
? mutable.canonicalUrlOverride
: href,
- // TODO-APP: verify mpaNavigation not being set is correct here.
pushRef: {
pendingPush,
mpaNavigation: false,
@@ -877,7 +859,6 @@ function clientReducer(
)
if (hardNavigate) {
- // TODO-APP: segments.slice(1) strips '', we can get rid of '' altogether.
// Copy subTreeData for the root node of the cache.
cache.subTreeData = state.cache.subTreeData
@@ -1333,7 +1314,6 @@ function clientReducer(
const { url, serverResponse } = action
const [flightData, canonicalUrlOverride] = serverResponse
- // TODO-APP: Implement prefetch for hard navigation
if (typeof flightData === 'string') {
return state
}
diff --git a/packages/next/client/components/static-generation-async-storage.ts b/packages/next/client/components/static-generation-async-storage.ts
index 7a43c8ad70bba..570f6c4dcf247 100644
--- a/packages/next/client/components/static-generation-async-storage.ts
+++ b/packages/next/client/components/static-generation-async-storage.ts
@@ -7,6 +7,9 @@ export interface StaticGenerationStore {
fetchRevalidate?: number
isStaticGeneration?: boolean
forceStatic?: boolean
+ incrementalCache?: import('../../server/lib/incremental-cache').IncrementalCache
+ pendingRevalidates?: Promise[]
+ isRevalidate?: boolean
}
export let staticGenerationAsyncStorage:
diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx
index 80d34bc9b0dda..967a9cda6c200 100644
--- a/packages/next/client/link.tsx
+++ b/packages/next/client/link.tsx
@@ -522,6 +522,11 @@ const Link = React.forwardRef(
// Prefetch the URL if we haven't already and it's visible.
React.useEffect(() => {
+ // in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page.
+ if (process.env.NODE_ENV !== 'production') {
+ return
+ }
+
if (!router) {
return
}
diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts
index 98cfab1b32c46..e99098f073429 100644
--- a/packages/next/export/worker.ts
+++ b/packages/next/export/worker.ts
@@ -12,7 +12,7 @@ import '../server/node-polyfill-fetch'
import { loadRequireHook } from '../build/webpack/require-hook'
import { extname, join, dirname, sep } from 'path'
-import { promises } from 'fs'
+import fs, { promises } from 'fs'
import AmpHtmlValidator from 'next/dist/compiled/amphtml-validator'
import { loadComponents } from '../server/load-components'
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic'
@@ -33,6 +33,7 @@ import { REDIRECT_ERROR_CODE } from '../client/components/redirect'
import { DYNAMIC_ERROR_CODE } from '../client/components/hooks-server-context'
import { NOT_FOUND_ERROR_CODE } from '../client/components/not-found'
import { NEXT_DYNAMIC_NO_SSR_CODE } from '../shared/lib/no-ssr-error'
+import { IncrementalCache } from '../server/lib/incremental-cache'
loadRequireHook()
@@ -99,6 +100,7 @@ interface RenderOpts {
domainLocales?: DomainLocale[]
trailingSlash?: boolean
supportsDynamicHTML?: boolean
+ incrementalCache?: import('../server/lib/incremental-cache').IncrementalCache
}
// expose AsyncLocalStorage on globalThis for react usage
@@ -311,6 +313,31 @@ export default async function exportPage({
// and bail when dynamic dependencies are detected
// only fully static paths are fully generated here
if (isAppDir) {
+ curRenderOpts.incrementalCache = new IncrementalCache({
+ dev: false,
+ requestHeaders: {},
+ flushToDisk: true,
+ maxMemoryCacheSize: 50 * 1024 * 1024,
+ getPrerenderManifest: () => ({
+ version: 3,
+ routes: {},
+ dynamicRoutes: {},
+ preview: {
+ previewModeEncryptionKey: '',
+ previewModeId: '',
+ previewModeSigningKey: '',
+ },
+ notFoundRoutes: [],
+ }),
+ fs: {
+ readFile: (f) => fs.promises.readFile(f, 'utf8'),
+ readFileSync: (f) => fs.readFileSync(f, 'utf8'),
+ writeFile: (f, d) => fs.promises.writeFile(f, d, 'utf8'),
+ mkdir: (dir) => fs.promises.mkdir(dir, { recursive: true }),
+ stat: (f) => fs.promises.stat(f),
+ },
+ serverDistDir: join(distDir, 'server'),
+ })
const { renderToHTMLOrFlight } =
require('../server/app-render') as typeof import('../server/app-render')
diff --git a/packages/next/package.json b/packages/next/package.json
index 1d259c42396ee..fd90ccf9f450d 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -75,7 +75,7 @@
]
},
"dependencies": {
- "@next/env": "13.0.7-canary.0",
+ "@next/env": "13.0.7-canary.1",
"@swc/helpers": "0.4.14",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
@@ -125,11 +125,11 @@
"@hapi/accept": "5.0.2",
"@napi-rs/cli": "2.12.0",
"@napi-rs/triples": "1.1.0",
- "@next/polyfill-module": "13.0.7-canary.0",
- "@next/polyfill-nomodule": "13.0.7-canary.0",
- "@next/react-dev-overlay": "13.0.7-canary.0",
- "@next/react-refresh-utils": "13.0.7-canary.0",
- "@next/swc": "13.0.7-canary.0",
+ "@next/polyfill-module": "13.0.7-canary.1",
+ "@next/polyfill-nomodule": "13.0.7-canary.1",
+ "@next/react-dev-overlay": "13.0.7-canary.1",
+ "@next/react-refresh-utils": "13.0.7-canary.1",
+ "@next/swc": "13.0.7-canary.1",
"@segment/ajv-human-errors": "2.1.2",
"@taskr/clear": "1.1.0",
"@taskr/esnext": "1.1.0",
diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx
index 3f084381a02fc..472103a98c974 100644
--- a/packages/next/server/app-render.tsx
+++ b/packages/next/server/app-render.tsx
@@ -81,6 +81,7 @@ function preloadComponent(Component: any, props: any) {
return Component
}
+const CACHE_ONE_YEAR = 31536000
const INTERNAL_HEADERS_INSTANCE = Symbol('internal for headers readonly')
function readonlyHeadersError() {
@@ -177,6 +178,8 @@ export type RenderOptsPartial = {
assetPrefix?: string
fontLoaderManifest?: FontLoaderManifest
isBot?: boolean
+ incrementalCache?: import('./lib/incremental-cache').IncrementalCache
+ isRevalidate?: boolean
}
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
@@ -248,12 +251,115 @@ function patchFetch(ComponentMod: any) {
const originFetch = globalThis.fetch
globalThis.fetch = async (input, init) => {
const staticGenerationStore =
- 'getStore' in staticGenerationAsyncStorage
+ ('getStore' in staticGenerationAsyncStorage
? staticGenerationAsyncStorage.getStore()
- : staticGenerationAsyncStorage
+ : staticGenerationAsyncStorage) || {}
+
+ const {
+ isStaticGeneration,
+ fetchRevalidate,
+ pathname,
+ incrementalCache,
+ isRevalidate,
+ } = (staticGenerationStore || {}) as StaticGenerationStore
+
+ let revalidate: number | undefined | boolean
+
+ if (typeof init?.next?.revalidate === 'number') {
+ revalidate = init.next.revalidate
+ }
+ if (init?.next?.revalidate === false) {
+ revalidate = CACHE_ONE_YEAR
+ }
+
+ if (
+ !staticGenerationStore.fetchRevalidate ||
+ (typeof revalidate === 'number' &&
+ revalidate < staticGenerationStore.fetchRevalidate)
+ ) {
+ staticGenerationStore.fetchRevalidate = revalidate
+ }
+
+ let cacheKey: string | undefined
+
+ const doOriginalFetch = async () => {
+ return originFetch(input, init).then(async (res) => {
+ if (
+ incrementalCache &&
+ cacheKey &&
+ typeof revalidate === 'number' &&
+ revalidate > 0
+ ) {
+ const clonedRes = res.clone()
+
+ let base64Body = ''
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ let string = ''
+ new Uint8Array(await clonedRes.arrayBuffer()).forEach((byte) => {
+ string += String.fromCharCode(byte)
+ })
+ base64Body = btoa(string)
+ } else {
+ base64Body = Buffer.from(await clonedRes.arrayBuffer()).toString(
+ 'base64'
+ )
+ }
+
+ await incrementalCache.set(
+ cacheKey,
+ {
+ kind: 'FETCH',
+ isStale: false,
+ age: 0,
+ data: {
+ headers: Object.fromEntries(clonedRes.headers.entries()),
+ body: base64Body,
+ },
+ revalidate,
+ },
+ revalidate,
+ true
+ )
+ }
+ return res
+ })
+ }
- const { isStaticGeneration, fetchRevalidate, pathname } =
- staticGenerationStore || {}
+ if (incrementalCache && typeof revalidate === 'number' && revalidate > 0) {
+ cacheKey = await incrementalCache?.fetchCacheKey(input.toString(), init)
+ const entry = await incrementalCache.get(cacheKey, true)
+
+ if (entry?.value && entry.value.kind === 'FETCH') {
+ // when stale and is revalidating we wait for fresh data
+ // so the revalidated entry has the updated data
+ if (!isRevalidate || !entry.isStale) {
+ if (entry.isStale) {
+ if (!staticGenerationStore.pendingRevalidates) {
+ staticGenerationStore.pendingRevalidates = []
+ }
+ staticGenerationStore.pendingRevalidates.push(
+ doOriginalFetch().catch(console.error)
+ )
+ }
+
+ const resData = entry.value.data
+ let decodedBody = ''
+
+ // TODO: handle non-text response bodies
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ decodedBody = atob(resData.body)
+ } else {
+ decodedBody = Buffer.from(resData.body, 'base64').toString()
+ }
+
+ return new Response(decodedBody, {
+ headers: resData.headers,
+ status: resData.status,
+ })
+ }
+ }
+ }
if (staticGenerationStore && isStaticGeneration) {
if (init && typeof init === 'object') {
@@ -295,7 +401,7 @@ function patchFetch(ComponentMod: any) {
if (hasNextConfig) delete init.next
}
}
- return originFetch(input, init)
+ return doOriginalFetch()
}
}
@@ -774,9 +880,7 @@ export async function renderToHTMLOrFlight(
supportsDynamicHTML,
} = renderOpts
- if (process.env.NODE_ENV === 'production') {
- patchFetch(ComponentMod)
- }
+ patchFetch(ComponentMod)
const generateStaticHTML = supportsDynamicHTML !== true
const staticGenerationAsyncStorage = ComponentMod.staticGenerationAsyncStorage
@@ -1114,7 +1218,7 @@ export async function renderToHTMLOrFlight(
// otherwise
if (layoutOrPageMod.dynamic === 'force-static') {
staticGenerationStore.forceStatic = true
- } else {
+ } else if (layoutOrPageMod.dynamic !== 'error') {
staticGenerationStore.forceStatic = false
}
}
@@ -1485,7 +1589,6 @@ export async function renderToHTMLOrFlight(
// Flight data that is going to be passed to the browser.
// Currently a single item array but in the future multiple patches might be combined in a single request.
const flightData: FlightData = [
- // TODO-APP: change walk to output without ''
(
await walkTreeWithFlightRouterState({
createSegmentPath: (child) => child,
@@ -1691,7 +1794,6 @@ export async function renderToHTMLOrFlight(
res.statusCode = 404
}
- // TODO-APP: show error overlay in development. `element` should probably be wrapped in AppRouter for this case.
const renderStream = await renderToInitialStream({
ReactDOMServer,
element: (
@@ -1729,6 +1831,10 @@ export async function renderToHTMLOrFlight(
}
const renderResult = new RenderResult(await bodyResult())
+ if (staticGenerationStore.pendingRevalidates) {
+ await Promise.all(staticGenerationStore.pendingRevalidates)
+ }
+
if (isStaticGeneration) {
const htmlResult = await streamToBufferedResult(renderResult)
@@ -1747,6 +1853,9 @@ export async function renderToHTMLOrFlight(
await generateFlight()
)
+ if (staticGenerationStore?.forceStatic === false) {
+ staticGenerationStore.fetchRevalidate = 0
+ }
;(renderOpts as any).pageData = filteredFlightData
;(renderOpts as any).revalidate =
typeof staticGenerationStore?.fetchRevalidate === 'undefined'
@@ -1763,6 +1872,8 @@ export async function renderToHTMLOrFlight(
isStaticGeneration,
inUse: true,
pathname,
+ incrementalCache: renderOpts.incrementalCache,
+ isRevalidate: renderOpts.isRevalidate,
}
const tryGetPreviewData =
diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts
index b91508e5ad1ba..3cf525acb8e55 100644
--- a/packages/next/server/base-server.ts
+++ b/packages/next/server/base-server.ts
@@ -78,6 +78,7 @@ import {
RSC,
RSC_VARY_HEADER,
FLIGHT_PARAMETERS,
+ FETCH_CACHE_HEADER,
} from '../client/components/app-router-headers'
export type FindComponentsResult = {
@@ -310,6 +311,10 @@ export default abstract class Server {
res: BaseNextResponse
): void
+ protected abstract getIncrementalCache(options: {
+ requestHeaders: Record
+ }): import('./lib/incremental-cache').IncrementalCache
+
protected abstract getResponseCache(options: {
dev: boolean
}): ResponseCacheBase
@@ -1283,11 +1288,22 @@ export default abstract class Server {
ssgCacheKey =
ssgCacheKey === '/index' && pathname === '/' ? '/' : ssgCacheKey
}
+ const incrementalCache = this.getIncrementalCache({
+ requestHeaders: Object.assign({}, req.headers),
+ })
+ if (
+ this.nextConfig.experimental.fetchCache &&
+ (opts.runtime !== 'experimental-edge' ||
+ (this.serverOptions as any).webServerConfig)
+ ) {
+ delete req.headers[FETCH_CACHE_HEADER]
+ }
+ let isRevalidate = false
const doRender: () => Promise = async () => {
let pageData: any
let body: RenderResult | null
- let sprRevalidate: number | false
+ let isrRevalidate: number | false
let isNotFound: boolean | undefined
let isRedirect: boolean | undefined
@@ -1312,6 +1328,12 @@ export default abstract class Server {
const renderOpts: RenderOpts = {
...components,
...opts,
+ ...(isAppPath && this.nextConfig.experimental.fetchCache
+ ? {
+ incrementalCache,
+ isRevalidate: this.minimalMode || isRevalidate,
+ }
+ : {}),
isDataReq,
resolvedUrl,
locale,
@@ -1346,7 +1368,7 @@ export default abstract class Server {
body = renderResult
// TODO: change this to a different passing mechanism
pageData = (renderOpts as any).pageData
- sprRevalidate = (renderOpts as any).revalidate
+ isrRevalidate = (renderOpts as any).revalidate
isNotFound = (renderOpts as any).isNotFound
isRedirect = (renderOpts as any).isRedirect
@@ -1361,7 +1383,7 @@ export default abstract class Server {
}
value = { kind: 'PAGE', html: body, pageData }
}
- return { revalidate: sprRevalidate, value }
+ return { revalidate: isrRevalidate, value }
}
const cacheEntry = await this.responseCache.get(
@@ -1371,6 +1393,10 @@ export default abstract class Server {
const isDynamicPathname = isDynamicRoute(pathname)
const didRespond = hasResolved || res.sent
+ if (hadCache) {
+ isRevalidate = true
+ }
+
if (!staticPaths) {
;({ staticPaths, fallbackMode } = hasStaticPaths
? await this.getStaticPaths({ pathname })
@@ -1486,6 +1512,7 @@ export default abstract class Server {
}
},
{
+ incrementalCache,
isManualRevalidate,
isPrefetch: req.headers.purpose === 'prefetch',
}
diff --git a/packages/next/server/config-schema.ts b/packages/next/server/config-schema.ts
index 2567e5298d615..ba1128cf18b3a 100644
--- a/packages/next/server/config-schema.ts
+++ b/packages/next/server/config-schema.ts
@@ -277,6 +277,9 @@ const configSchema = {
fallbackNodePolyfills: {
type: 'boolean',
},
+ fetchCache: {
+ type: 'boolean',
+ },
forceSwcTransforms: {
type: 'boolean',
},
diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts
index 8659f99f4f020..0a56331d28ceb 100644
--- a/packages/next/server/config-shared.ts
+++ b/packages/next/server/config-shared.ts
@@ -79,6 +79,7 @@ export interface NextJsWebpackConfig {
}
export interface ExperimentalConfig {
+ fetchCache?: boolean
allowMiddlewareResponseBody?: boolean
skipMiddlewareUrlNormalize?: boolean
skipTrailingSlashRedirect?: boolean
@@ -565,6 +566,7 @@ export const defaultConfig: NextConfig = {
swcMinify: true,
output: !!process.env.NEXT_PRIVATE_STANDALONE ? 'standalone' : undefined,
experimental: {
+ fetchCache: false,
middlewarePrefetch: 'flexible',
optimisticClientCache: true,
runtime: undefined,
diff --git a/packages/next/server/lib/incremental-cache/fetch-cache.ts b/packages/next/server/lib/incremental-cache/fetch-cache.ts
new file mode 100644
index 0000000000000..613f50c22ca6c
--- /dev/null
+++ b/packages/next/server/lib/incremental-cache/fetch-cache.ts
@@ -0,0 +1,162 @@
+import LRUCache from 'next/dist/compiled/lru-cache'
+import { FETCH_CACHE_HEADER } from '../../../client/components/app-router-headers'
+import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './'
+
+let memoryCache: LRUCache | undefined
+
+export default class FetchCache implements CacheHandler {
+ private headers: Record
+ private cacheEndpoint: string
+ private debug: boolean
+
+ constructor(ctx: CacheHandlerContext) {
+ if (ctx.maxMemoryCacheSize && !memoryCache) {
+ memoryCache = new LRUCache({
+ max: ctx.maxMemoryCacheSize,
+ length({ value }) {
+ if (!value) {
+ return 25
+ } else if (value.kind === 'REDIRECT') {
+ return JSON.stringify(value.props).length
+ } else if (value.kind === 'IMAGE') {
+ throw new Error('invariant image should not be incremental-cache')
+ } else if (value.kind === 'FETCH') {
+ return JSON.stringify(value.data || '').length
+ }
+ // rough estimate of size of cache value
+ return (
+ value.html.length + (JSON.stringify(value.pageData)?.length || 0)
+ )
+ },
+ })
+ }
+ this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE
+ this.headers = {}
+ this.headers['Content-Type'] = 'application/json'
+
+ if (FETCH_CACHE_HEADER in ctx._requestHeaders) {
+ const newHeaders = JSON.parse(
+ ctx._requestHeaders[FETCH_CACHE_HEADER] as string
+ )
+ for (const k in newHeaders) {
+ this.headers[k] = newHeaders[k]
+ }
+ }
+ this.cacheEndpoint = `https://${ctx._requestHeaders['x-vercel-sc-host']}${
+ ctx._requestHeaders['x-vercel-sc-basepath'] || ''
+ }`
+ if (this.debug) {
+ console.log('using cache endpoint', this.cacheEndpoint)
+ }
+ }
+
+ public async get(key: string, fetchCache?: boolean) {
+ if (!fetchCache) return null
+
+ let data = memoryCache?.get(key)
+
+ // get data from fetch cache
+ if (!data) {
+ try {
+ const start = Date.now()
+ const res = await fetch(
+ `${this.cacheEndpoint}/v1/suspense-cache/getItems`,
+ {
+ method: 'POST',
+ body: JSON.stringify([key]),
+ headers: this.headers,
+ }
+ )
+
+ if (!res.ok) {
+ console.error(await res.text())
+ throw new Error(`invalid response from cache ${res.status}`)
+ }
+
+ const items = await res.json()
+ const item = items[key]
+
+ if (!item || !item.value) {
+ console.log({ item })
+ throw new Error(`invalid item returned`)
+ }
+
+ const cached = JSON.parse(item.value)
+
+ if (!cached || cached.kind !== 'FETCH') {
+ this.debug && console.log({ cached })
+ throw new Error(`invalid cache value`)
+ }
+
+ data = {
+ lastModified: Date.now() - item.age * 1000,
+ value: cached,
+ }
+ if (this.debug) {
+ console.log(
+ 'got fetch cache entry duration:',
+ Date.now() - start,
+ data
+ )
+ }
+
+ if (data) {
+ memoryCache?.set(key, data)
+ }
+ } catch (err) {
+ // unable to get data from fetch-cache
+ console.error(`Failed to get from fetch-cache`, err)
+ }
+ }
+ return data || null
+ }
+
+ public async set(
+ key: string,
+ data: CacheHandlerValue['value'],
+ fetchCache?: boolean
+ ) {
+ if (!fetchCache) return
+
+ memoryCache?.set(key, {
+ value: data,
+ lastModified: Date.now(),
+ })
+
+ try {
+ const start = Date.now()
+ const body = JSON.stringify([
+ {
+ id: key,
+ value: JSON.stringify(data),
+ },
+ ])
+
+ const res = await fetch(
+ `${this.cacheEndpoint}/v1/suspense-cache/setItems`,
+ {
+ method: 'POST',
+ headers: this.headers,
+ body: body,
+ }
+ )
+
+ if (!res.ok) {
+ this.debug && console.log(await res.text())
+ throw new Error(`invalid response ${res.status}`)
+ }
+
+ if (this.debug) {
+ console.log(
+ 'successfully set to fetch-cache duration:',
+ Date.now() - start,
+ body
+ )
+ }
+ } catch (err) {
+ // unable to set to fetch-cache
+ console.error(`Failed to update fetch cache`, err)
+ }
+ return
+ }
+}
diff --git a/packages/next/server/lib/incremental-cache/file-system-cache.ts b/packages/next/server/lib/incremental-cache/file-system-cache.ts
index 85f43051ba260..fcba747e46e70 100644
--- a/packages/next/server/lib/incremental-cache/file-system-cache.ts
+++ b/packages/next/server/lib/incremental-cache/file-system-cache.ts
@@ -2,11 +2,12 @@ import LRUCache from 'next/dist/compiled/lru-cache'
import path from '../../../shared/lib/isomorphic/path'
import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './'
+let memoryCache: LRUCache | undefined
+
export default class FileSystemCache implements CacheHandler {
private fs: CacheHandlerContext['fs']
private flushToDisk?: CacheHandlerContext['flushToDisk']
private serverDistDir: CacheHandlerContext['serverDistDir']
- private memoryCache?: LRUCache
private appDir: boolean
constructor(ctx: CacheHandlerContext) {
@@ -15,8 +16,8 @@ export default class FileSystemCache implements CacheHandler {
this.serverDistDir = ctx.serverDistDir
this.appDir = !!ctx._appDir
- if (ctx.maxMemoryCacheSize) {
- this.memoryCache = new LRUCache({
+ if (ctx.maxMemoryCacheSize && !memoryCache) {
+ memoryCache = new LRUCache({
max: ctx.maxMemoryCacheSize,
length({ value }) {
if (!value) {
@@ -25,6 +26,8 @@ export default class FileSystemCache implements CacheHandler {
return JSON.stringify(value.props).length
} else if (value.kind === 'IMAGE') {
throw new Error('invariant image should not be incremental-cache')
+ } else if (value.kind === 'FETCH') {
+ return JSON.stringify(value.data || '').length
}
// rough estimate of size of cache value
return (
@@ -35,40 +38,55 @@ export default class FileSystemCache implements CacheHandler {
}
}
- public async get(key: string) {
- let data = this.memoryCache?.get(key)
+ public async get(key: string, fetchCache?: boolean) {
+ let data = memoryCache?.get(key)
// let's check the disk for seed data
if (!data) {
try {
- const { filePath: htmlPath, isAppPath } = await this.getFsPath(
- `${key}.html`
- )
- const html = await this.fs.readFile(htmlPath)
- const pageData = isAppPath
- ? await this.fs.readFile(
- (
- await this.getFsPath(`${key}.rsc`, true)
- ).filePath
- )
- : JSON.parse(
- await this.fs.readFile(
- await (
- await this.getFsPath(`${key}.json`, false)
+ const { filePath, isAppPath } = await this.getFsPath({
+ pathname: fetchCache ? key : `${key}.html`,
+ fetchCache,
+ })
+ const fileData = await this.fs.readFile(filePath)
+ const { mtime } = await this.fs.stat(filePath)
+
+ if (fetchCache) {
+ const lastModified = mtime.getTime()
+ data = {
+ lastModified,
+ value: JSON.parse(fileData),
+ }
+ } else {
+ const pageData = isAppPath
+ ? await this.fs.readFile(
+ (
+ await this.getFsPath({ pathname: `${key}.rsc`, appDir: true })
).filePath
)
- )
- const { mtime } = await this.fs.stat(htmlPath)
+ : JSON.parse(
+ await this.fs.readFile(
+ await (
+ await this.getFsPath({
+ pathname: `${key}.json`,
+ appDir: false,
+ })
+ ).filePath
+ )
+ )
+ data = {
+ lastModified: mtime.getTime(),
+ value: {
+ kind: 'PAGE',
+ html: fileData,
+ pageData,
+ },
+ }
+ }
- data = {
- lastModified: mtime.getTime(),
- value: {
- kind: 'PAGE',
- html,
- pageData,
- },
+ if (data) {
+ memoryCache?.set(key, data)
}
- this.memoryCache?.set(key, data)
} catch (_) {
// unable to get data from disk
}
@@ -77,41 +95,66 @@ export default class FileSystemCache implements CacheHandler {
}
public async set(key: string, data: CacheHandlerValue['value']) {
- if (!this.flushToDisk) return
-
- this.memoryCache?.set(key, {
+ memoryCache?.set(key, {
value: data,
lastModified: Date.now(),
})
+ if (!this.flushToDisk) return
if (data?.kind === 'PAGE') {
const isAppPath = typeof data.pageData === 'string'
- const { filePath: htmlPath } = await this.getFsPath(
- `${key}.html`,
- isAppPath
- )
+ const { filePath: htmlPath } = await this.getFsPath({
+ pathname: `${key}.html`,
+ appDir: isAppPath,
+ })
await this.fs.mkdir(path.dirname(htmlPath))
await this.fs.writeFile(htmlPath, data.html)
await this.fs.writeFile(
(
- await this.getFsPath(
- `${key}.${isAppPath ? 'rsc' : 'json'}`,
- isAppPath
- )
+ await this.getFsPath({
+ pathname: `${key}.${isAppPath ? 'rsc' : 'json'}`,
+ appDir: isAppPath,
+ })
).filePath,
isAppPath ? data.pageData : JSON.stringify(data.pageData)
)
+ } else if (data?.kind === 'FETCH') {
+ const { filePath } = await this.getFsPath({
+ pathname: key,
+ fetchCache: true,
+ })
+ await this.fs.mkdir(path.dirname(filePath))
+ await this.fs.writeFile(filePath, JSON.stringify(data))
}
}
- private async getFsPath(
- pathname: string,
+ private async getFsPath({
+ pathname,
+ appDir,
+ fetchCache,
+ }: {
+ pathname: string
appDir?: boolean
- ): Promise<{
+ fetchCache?: boolean
+ }): Promise<{
filePath: string
isAppPath: boolean
}> {
+ if (fetchCache) {
+ // we store in .next/cache/fetch-cache so it can be persisted
+ // across deploys
+ return {
+ filePath: path.join(
+ this.serverDistDir,
+ '..',
+ 'cache',
+ 'fetch-cache',
+ pathname
+ ),
+ isAppPath: false,
+ }
+ }
let isAppPath = false
let filePath = path.join(this.serverDistDir, 'pages', pathname)
diff --git a/packages/next/server/lib/incremental-cache/index.ts b/packages/next/server/lib/incremental-cache/index.ts
index 81a325ff59509..3805d418f101a 100644
--- a/packages/next/server/lib/incremental-cache/index.ts
+++ b/packages/next/server/lib/incremental-cache/index.ts
@@ -3,6 +3,7 @@ import FileSystemCache from './file-system-cache'
import { PrerenderManifest } from '../../../build'
import path from '../../../shared/lib/isomorphic/path'
import { normalizePagePath } from '../../../shared/lib/page-path/normalize-page-path'
+import FetchCache from './fetch-cache'
import {
IncrementalCacheValue,
IncrementalCacheEntry,
@@ -18,7 +19,8 @@ export interface CacheHandlerContext {
flushToDisk?: boolean
serverDistDir: string
maxMemoryCacheSize?: number
- _appDir?: boolean
+ _appDir: boolean
+ _requestHeaders: IncrementalCache['requestHeaders']
}
export interface CacheHandlerValue {
@@ -30,13 +32,17 @@ export class CacheHandler {
// eslint-disable-next-line
constructor(_ctx: CacheHandlerContext) {}
- public async get(_key: string): Promise {
+ public async get(
+ _key: string,
+ _fetchCache?: boolean
+ ): Promise {
return {} as any
}
public async set(
_key: string,
- _data: IncrementalCacheValue | null
+ _data: IncrementalCacheValue | null,
+ _fetchCache?: boolean
): Promise {}
}
@@ -44,13 +50,18 @@ export class IncrementalCache {
dev?: boolean
cacheHandler: CacheHandler
prerenderManifest: PrerenderManifest
+ requestHeaders: Record
+ minimalMode?: boolean
constructor({
fs,
dev,
appDir,
flushToDisk,
+ fetchCache,
+ minimalMode,
serverDistDir,
+ requestHeaders,
maxMemoryCacheSize,
getPrerenderManifest,
incrementalCacheHandlerPath,
@@ -58,8 +69,11 @@ export class IncrementalCache {
fs: CacheFs
dev: boolean
appDir?: boolean
+ fetchCache?: boolean
+ minimalMode?: boolean
serverDistDir: string
flushToDisk?: boolean
+ requestHeaders: IncrementalCache['requestHeaders']
maxMemoryCacheSize?: number
incrementalCacheHandlerPath?: string
getPrerenderManifest: () => PrerenderManifest
@@ -71,11 +85,17 @@ export class IncrementalCache {
cacheHandlerMod = cacheHandlerMod.default || cacheHandlerMod
}
+ if (minimalMode && fetchCache) {
+ cacheHandlerMod = FetchCache
+ }
+
if (process.env.__NEXT_TEST_MAX_ISR_CACHE) {
// Allow cache size to be overridden for testing purposes
maxMemoryCacheSize = parseInt(process.env.__NEXT_TEST_MAX_ISR_CACHE, 10)
}
this.dev = dev
+ this.minimalMode = minimalMode
+ this.requestHeaders = requestHeaders
this.prerenderManifest = getPrerenderManifest()
this.cacheHandler = new (cacheHandlerMod as typeof CacheHandler)({
dev,
@@ -83,7 +103,8 @@ export class IncrementalCache {
flushToDisk,
serverDistDir,
maxMemoryCacheSize,
- _appDir: appDir,
+ _appDir: !!appDir,
+ _requestHeaders: requestHeaders,
})
}
@@ -110,19 +131,75 @@ export class IncrementalCache {
return revalidateAfter
}
- _getPathname(pathname: string) {
- return normalizePagePath(pathname)
+ _getPathname(pathname: string, fetchCache?: boolean) {
+ return fetchCache ? pathname : normalizePagePath(pathname)
+ }
+
+ // x-ref: https://github.com/facebook/react/blob/2655c9354d8e1c54ba888444220f63e836925caa/packages/react/src/ReactFetch.js#L23
+ async fetchCacheKey(url: string, init: RequestInit = {}): Promise {
+ const cacheString = JSON.stringify([
+ url,
+ init.method,
+ init.headers,
+ init.mode,
+ init.redirect,
+ init.credentials,
+ init.referrer,
+ init.referrerPolicy,
+ init.integrity,
+ init.next,
+ init.cache,
+ ])
+ let cacheKey: string
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ function bufferToHex(buffer: ArrayBuffer): string {
+ return Array.prototype.map
+ .call(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, '0'))
+ .join('')
+ }
+ const buffer = new TextEncoder().encode(cacheString)
+ cacheKey = bufferToHex(await crypto.subtle.digest('SHA-256', buffer))
+ } else {
+ const crypto = require('crypto') as typeof import('crypto')
+ cacheKey = crypto.createHash('sha256').update(cacheString).digest('hex')
+ }
+ return cacheKey
}
// get data from cache if available
- async get(pathname: string): Promise {
+ async get(
+ pathname: string,
+ fetchCache?: boolean
+ ): Promise {
// we don't leverage the prerender cache in dev mode
// so that getStaticProps is always called for easier debugging
if (this.dev) return null
- pathname = this._getPathname(pathname)
+ pathname = this._getPathname(pathname, fetchCache)
let entry: IncrementalCacheEntry | null = null
- const cacheData = await this.cacheHandler.get(pathname)
+ const cacheData = await this.cacheHandler.get(pathname, fetchCache)
+
+ if (cacheData?.value?.kind === 'FETCH') {
+ const data = cacheData.value.data
+ const age = Math.round(
+ (Date.now() - (cacheData.lastModified || 0)) / 1000
+ )
+ const revalidate = cacheData.value.revalidate
+
+ return {
+ isStale: age > revalidate,
+ value: {
+ kind: 'FETCH',
+ data,
+ age,
+ revalidate,
+ isStale: age > revalidate,
+ },
+ revalidateAfter:
+ (cacheData.lastModified || Date.now()) + revalidate * 1000,
+ }
+ }
const curRevalidate =
this.prerenderManifest.routes[toRoute(pathname)]?.initialRevalidateSeconds
@@ -159,7 +236,7 @@ export class IncrementalCache {
curRevalidate,
revalidateAfter,
}
- this.set(pathname, entry.value, curRevalidate)
+ this.set(pathname, entry.value, curRevalidate, fetchCache)
}
return entry
}
@@ -168,16 +245,17 @@ export class IncrementalCache {
async set(
pathname: string,
data: IncrementalCacheValue | null,
- revalidateSeconds?: number | false
+ revalidateSeconds?: number | false,
+ fetchCache?: boolean
) {
if (this.dev) return
- pathname = this._getPathname(pathname)
+ pathname = this._getPathname(pathname, fetchCache)
try {
// we use the prerender manifest memory instance
// to store revalidate timings for calculating
// revalidateAfter values so we update this on set
- if (typeof revalidateSeconds !== 'undefined') {
+ if (typeof revalidateSeconds !== 'undefined' && !fetchCache) {
this.prerenderManifest.routes[pathname] = {
dataRoute: path.posix.join(
'/_next/data',
@@ -187,7 +265,7 @@ export class IncrementalCache {
initialRevalidateSeconds: revalidateSeconds,
}
}
- await this.cacheHandler.set(pathname, data)
+ await this.cacheHandler.set(pathname, data, fetchCache)
} catch (error) {
console.warn('Failed to update prerender cache for', pathname, error)
}
diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts
index db89c1cfe9dc2..058f23239ac2e 100644
--- a/packages/next/server/next-server.ts
+++ b/packages/next/server/next-server.ts
@@ -228,15 +228,7 @@ export default class NextNodeServer extends BaseServer {
}
if (!this.minimalMode) {
- const { ImageOptimizerCache } =
- require('./image-optimizer') as typeof import('./image-optimizer')
- this.imageResponseCache = new ResponseCache(
- new ImageOptimizerCache({
- distDir: this.distDir,
- nextConfig: this.nextConfig,
- }),
- this.minimalMode
- )
+ this.imageResponseCache = new ResponseCache(this.minimalMode)
}
if (!options.dev) {
@@ -278,12 +270,23 @@ export default class NextNodeServer extends BaseServer {
loadEnvConfig(this.dir, dev, Log, forceReload)
}
- protected getResponseCache({ dev }: { dev: boolean }) {
- const incrementalCache = new IncrementalCache({
+ protected getIncrementalCache({
+ requestHeaders,
+ }: {
+ requestHeaders: IncrementalCache['requestHeaders']
+ }) {
+ const dev = !!this.renderOpts.dev
+ // incremental-cache is request specific with a shared
+ // although can have shared caches in module scope
+ // per-cache handler
+ return new IncrementalCache({
fs: this.getCacheFilesystem(),
dev,
- serverDistDir: this.serverDistDir,
+ requestHeaders,
appDir: this.hasAppDir,
+ minimalMode: this.minimalMode,
+ serverDistDir: this.serverDistDir,
+ fetchCache: this.nextConfig.experimental.fetchCache,
maxMemoryCacheSize: this.nextConfig.experimental.isrMemoryCacheSize,
flushToDisk:
!this.minimalMode && this.nextConfig.experimental.isrFlushToDisk,
@@ -303,8 +306,10 @@ export default class NextNodeServer extends BaseServer {
}
},
})
+ }
- return new ResponseCache(incrementalCache, this.minimalMode)
+ protected getResponseCache() {
+ return new ResponseCache(this.minimalMode)
}
protected getPublicDir(): string {
@@ -383,7 +388,15 @@ export default class NextNodeServer extends BaseServer {
finished: true,
}
}
- const { getHash, ImageOptimizerCache, sendResponse, ImageError } =
+ const { ImageOptimizerCache } =
+ require('./image-optimizer') as typeof import('./image-optimizer')
+
+ const imageOptimizerCache = new ImageOptimizerCache({
+ distDir: this.distDir,
+ nextConfig: this.nextConfig,
+ })
+
+ const { getHash, sendResponse, ImageError } =
require('./image-optimizer') as typeof import('./image-optimizer')
if (!this.imageResponseCache) {
@@ -391,7 +404,6 @@ export default class NextNodeServer extends BaseServer {
'invariant image optimizer cache was not initialized'
)
}
-
const imagesConfig = this.nextConfig.images
if (imagesConfig.loader !== 'default') {
@@ -434,7 +446,9 @@ export default class NextNodeServer extends BaseServer {
revalidate: maxAge,
}
},
- {}
+ {
+ incrementalCache: imageOptimizerCache,
+ }
)
if (cacheEntry?.value?.kind !== 'IMAGE') {
diff --git a/packages/next/server/node-web-streams-helper.ts b/packages/next/server/node-web-streams-helper.ts
index c82ff473824f2..a2062a66778f2 100644
--- a/packages/next/server/node-web-streams-helper.ts
+++ b/packages/next/server/node-web-streams-helper.ts
@@ -288,15 +288,19 @@ export function createRootLayoutValidatorStream(
getTree: () => FlightRouterState
): TransformStream {
let foundHtml = false
+ let foundHead = false
let foundBody = false
return new TransformStream({
async transform(chunk, controller) {
- if (!foundHtml || !foundBody) {
+ if (!foundHtml || !foundHead || !foundBody) {
const content = decodeText(chunk)
if (!foundHtml && content.includes('>
previousCacheItem?: {
key: string
@@ -19,8 +18,7 @@ export default class ResponseCache {
}
minimalMode?: boolean
- constructor(incrementalCache: IncrementalCache, minimalMode: boolean) {
- this.incrementalCache = incrementalCache
+ constructor(minimalMode: boolean) {
this.pendingResponses = new Map()
this.minimalMode = minimalMode
}
@@ -31,8 +29,10 @@ export default class ResponseCache {
context: {
isManualRevalidate?: boolean
isPrefetch?: boolean
+ incrementalCache: IncrementalCache
}
): Promise {
+ const { incrementalCache } = context
// ensure manual revalidate doesn't block normal requests
const pendingResponseKey = key
? `${key}-${context.isManualRevalidate ? '1' : '0'}`
@@ -41,6 +41,7 @@ export default class ResponseCache {
const pendingResponse = pendingResponseKey
? this.pendingResponses.get(pendingResponseKey)
: null
+
if (pendingResponse) {
return pendingResponse
}
@@ -92,9 +93,15 @@ export default class ResponseCache {
let cachedResponse: IncrementalCacheItem = null
try {
cachedResponse =
- key && !this.minimalMode ? await this.incrementalCache.get(key) : null
+ key && !this.minimalMode ? await incrementalCache.get(key) : null
if (cachedResponse && !context.isManualRevalidate) {
+ if (cachedResponse.value?.kind === 'FETCH') {
+ throw new Error(
+ `invariant: unexpected cachedResponse of kind fetch in response cache`
+ )
+ }
+
resolve({
isStale: cachedResponse.isStale,
revalidate: cachedResponse.curRevalidate,
@@ -136,7 +143,7 @@ export default class ResponseCache {
expiresAt: Date.now() + 1000,
}
} else {
- await this.incrementalCache.set(
+ await incrementalCache.set(
key,
cacheEntry.value?.kind === 'PAGE'
? {
@@ -159,7 +166,7 @@ export default class ResponseCache {
// when a getStaticProps path is erroring we automatically re-set the
// existing cache under a new expiration to prevent non-stop retrying
if (cachedResponse && key) {
- await this.incrementalCache.set(
+ await incrementalCache.set(
key,
cachedResponse.value,
Math.min(Math.max(cachedResponse.revalidate || 3, 3), 30)
diff --git a/packages/next/server/response-cache/types.ts b/packages/next/server/response-cache/types.ts
index 63c2d37c4f659..a6459121b62cf 100644
--- a/packages/next/server/response-cache/types.ts
+++ b/packages/next/server/response-cache/types.ts
@@ -7,10 +7,19 @@ export interface ResponseCacheBase {
context: {
isManualRevalidate?: boolean
isPrefetch?: boolean
+ incrementalCache: IncrementalCache
}
): Promise
}
+export interface CachedFetchValue {
+ kind: 'FETCH'
+ data: any
+ isStale: boolean
+ age: number
+ revalidate: number
+}
+
export interface CachedRedirectValue {
kind: 'REDIRECT'
props: Object
@@ -53,6 +62,7 @@ export type IncrementalCacheValue =
| CachedRedirectValue
| IncrementalCachedPageValue
| CachedImageValue
+ | CachedFetchValue
export type ResponseCacheValue =
| CachedRedirectValue
diff --git a/packages/next/server/response-cache/web.ts b/packages/next/server/response-cache/web.ts
index 619b0bd81f18a..0fea0028a5e0f 100644
--- a/packages/next/server/response-cache/web.ts
+++ b/packages/next/server/response-cache/web.ts
@@ -24,6 +24,7 @@ export default class WebResponseCache {
context: {
isManualRevalidate?: boolean
isPrefetch?: boolean
+ incrementalCache: any
}
): Promise {
// ensure manual revalidate doesn't block normal requests
diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts
index 5cfdaca5c6331..e236f6b881f75 100644
--- a/packages/next/server/web-server.ts
+++ b/packages/next/server/web-server.ts
@@ -54,6 +54,9 @@ export default class NextWebServer extends BaseServer {
// For the web server layer, compression is automatically handled by the
// upstream proxy (edge runtime or node server) and we can simply skip here.
}
+ protected getIncrementalCache() {
+ return {} as any
+ }
protected getResponseCache() {
return new WebResponseCache(this.minimalMode)
}
diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts
index 062dca353ae0b..9595dd6208461 100644
--- a/packages/next/server/web/adapter.ts
+++ b/packages/next/server/web/adapter.ts
@@ -11,6 +11,7 @@ import { NextURL } from './next-url'
import { stripInternalSearchParams } from '../internal-utils'
import { normalizeRscPath } from '../../shared/lib/router/utils/app-paths'
import {
+ FETCH_CACHE_HEADER,
NEXT_ROUTER_PREFETCH,
NEXT_ROUTER_STATE_TREE,
RSC,
@@ -45,6 +46,7 @@ const FLIGHT_PARAMETERS = [
[RSC],
[NEXT_ROUTER_STATE_TREE],
[NEXT_ROUTER_PREFETCH],
+ [FETCH_CACHE_HEADER],
] as const
export async function adapter(params: {
diff --git a/packages/next/shared/lib/router/adapters.tsx b/packages/next/shared/lib/router/adapters.tsx
index 9bf0096abad2b..ed237d3525fa0 100644
--- a/packages/next/shared/lib/router/adapters.tsx
+++ b/packages/next/shared/lib/router/adapters.tsx
@@ -117,7 +117,14 @@ export function PathnameContextProviderAdapter({
// any query strings), so it should have that stripped. Read more about the
// `asPath` option over at:
// https://nextjs.org/docs/api-reference/next/router#router-object
- const url = new URL(router.asPath, 'http://f')
+ let url: URL
+ try {
+ url = new URL(router.asPath, 'http://f')
+ } catch (_) {
+ // fallback to / for invalid asPath values e.g. //
+ return '/'
+ }
+
return url.pathname
}, [router.asPath, router.isFallback, router.isReady, router.pathname])
diff --git a/packages/next/types/global.d.ts b/packages/next/types/global.d.ts
index e07479561b08e..2332e513e6729 100644
--- a/packages/next/types/global.d.ts
+++ b/packages/next/types/global.d.ts
@@ -39,7 +39,7 @@ interface Window {
}
interface NextFetchRequestConfig {
- revalidate?: number
+ revalidate?: number | false
}
interface RequestInit {
diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts
index c2550b9bf3e8d..f1186464bb04d 100644
--- a/packages/next/types/index.d.ts
+++ b/packages/next/types/index.d.ts
@@ -40,7 +40,6 @@ declare module 'react' {
nonce?: string
}
- // TODO-APP: check if this is the right type.
function use(promise: Promise | React.Context): T
function cache(fn: T): T
}
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index eb26abaab334c..5e82ebe994746 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index 5b509c84a7895..987a5c924d18a 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "13.0.7-canary.0",
+ "version": "13.0.7-canary.1",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7ada15e64a742..a116c08f070ab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -22,6 +22,7 @@ importers:
'@edge-runtime/jest-environment': 2.0.0
'@fullhuman/postcss-purgecss': 1.3.0
'@mdx-js/loader': ^1.5.1
+ '@mdx-js/react': ^1.6.18
'@next/bundle-analyzer': workspace:*
'@next/env': workspace:*
'@next/eslint-plugin-next': workspace:*
@@ -79,7 +80,9 @@ importers:
alex: 9.1.0
amphtml-validator: 1.0.35
async-sema: 3.0.1
+ body-parser: 1.20.1
browserslist: 4.20.2
+ buffer: 5.6.0
chalk: 5.0.1
cheerio: 0.22.0
cookie: 0.4.1
@@ -105,6 +108,7 @@ importers:
express: 4.17.0
faker: 5.5.3
faunadb: 2.6.1
+ find-up: 4.1.0
firebase: 7.14.5
flat: 5.0.2
form-data: 4.0.0
@@ -113,16 +117,19 @@ importers:
glob: 7.1.6
gzip-size: 5.1.1
html-validator: 5.1.18
+ http-proxy: 1.18.1
husky: 8.0.0
image-size: 0.9.3
is-animated: 2.0.2
isomorphic-unfetch: 3.0.0
jest: 27.0.6
jest-extended: 1.2.1
+ json5: 2.2.1
ky: 0.19.1
ky-universal: 0.6.0
lerna: 4.0.0
lint-staged: 10.1.7
+ lodash: 4.17.20
lost: 8.3.1
minimatch: 3.0.4
moment: ^2.24.0
@@ -162,7 +169,9 @@ importers:
selenium-webdriver: 4.0.0-beta.4
semver: 7.3.7
shell-quote: 1.7.3
+ strip-ansi: 6.0.0
styled-components: 6.0.0-beta.5
+ styled-jsx: 5.1.0
styled-jsx-plugin-postcss: 3.0.2
swr: 2.0.0-rc.0
tailwindcss: 1.1.3
@@ -171,9 +180,12 @@ importers:
tsec: 0.2.1
turbo: 1.6.3
typescript: 4.8.2
+ unfetch: 4.2.0
wait-port: 0.2.2
webpack: 5.74.0
webpack-bundle-analyzer: 4.7.0
+ whatwg-fetch: 3.0.0
+ ws: 8.2.3
devDependencies:
'@babel/core': 7.18.0
'@babel/eslint-parser': 7.18.2_uxoojzahptggrua2tvdiqlh7xm
@@ -185,6 +197,7 @@ importers:
'@edge-runtime/jest-environment': 2.0.0
'@fullhuman/postcss-purgecss': 1.3.0
'@mdx-js/loader': 1.6.22_react@18.2.0
+ '@mdx-js/react': 1.6.22_react@18.2.0
'@next/bundle-analyzer': link:packages/next-bundle-analyzer
'@next/env': link:packages/next-env
'@next/eslint-plugin-next': link:packages/eslint-plugin-next
@@ -242,7 +255,9 @@ importers:
alex: 9.1.0
amphtml-validator: 1.0.35
async-sema: 3.0.1
+ body-parser: 1.20.1
browserslist: 4.20.2
+ buffer: 5.6.0
chalk: 5.0.1
cheerio: 0.22.0
cookie: 0.4.1
@@ -268,6 +283,7 @@ importers:
express: 4.17.0
faker: 5.5.3
faunadb: 2.6.1
+ find-up: 4.1.0
firebase: 7.14.5
flat: 5.0.2
form-data: 4.0.0
@@ -276,16 +292,19 @@ importers:
glob: 7.1.6
gzip-size: 5.1.1
html-validator: 5.1.18
+ http-proxy: 1.18.1
husky: 8.0.0
image-size: 0.9.3
is-animated: 2.0.2
isomorphic-unfetch: 3.0.0
jest: 27.0.6_node-notifier@8.0.1
jest-extended: 1.2.1
+ json5: 2.2.1
ky: 0.19.1
ky-universal: 0.6.0_ky@0.19.1
lerna: 4.0.0
lint-staged: 10.1.7
+ lodash: 4.17.20
lost: 8.3.1
minimatch: 3.0.4
moment: 2.24.0
@@ -325,7 +344,9 @@ importers:
selenium-webdriver: 4.0.0-beta.4
semver: 7.3.7
shell-quote: 1.7.3
+ strip-ansi: 6.0.0
styled-components: 6.0.0-beta.5_biqbaboplfbrettd7655fr4n2y
+ styled-jsx: 5.1.0_uuaxwgga6hqycsez5ok7v2wg4i
styled-jsx-plugin-postcss: 3.0.2
swr: 2.0.0-rc.0_react@18.2.0
tailwindcss: 1.1.3
@@ -334,9 +355,12 @@ importers:
tsec: 0.2.1_sbe2uaqno6akssxfwbhgeg7v2q
turbo: 1.6.3
typescript: 4.8.2
+ unfetch: 4.2.0
wait-port: 0.2.2
webpack: 5.74.0_@swc+core@1.2.203
webpack-bundle-analyzer: 4.7.0
+ whatwg-fetch: 3.0.0
+ ws: 8.2.3
bench/vercel:
specifiers:
@@ -410,7 +434,7 @@ importers:
packages/eslint-config-next:
specifiers:
- '@next/eslint-plugin-next': 13.0.7-canary.0
+ '@next/eslint-plugin-next': 13.0.7-canary.1
'@rushstack/eslint-patch': ^1.1.3
'@typescript-eslint/parser': ^5.42.0
eslint-import-resolver-node: ^0.3.6
@@ -478,12 +502,12 @@ importers:
'@hapi/accept': 5.0.2
'@napi-rs/cli': 2.12.0
'@napi-rs/triples': 1.1.0
- '@next/env': 13.0.7-canary.0
- '@next/polyfill-module': 13.0.7-canary.0
- '@next/polyfill-nomodule': 13.0.7-canary.0
- '@next/react-dev-overlay': 13.0.7-canary.0
- '@next/react-refresh-utils': 13.0.7-canary.0
- '@next/swc': 13.0.7-canary.0
+ '@next/env': 13.0.7-canary.1
+ '@next/polyfill-module': 13.0.7-canary.1
+ '@next/polyfill-nomodule': 13.0.7-canary.1
+ '@next/react-dev-overlay': 13.0.7-canary.1
+ '@next/react-refresh-utils': 13.0.7-canary.1
+ '@next/swc': 13.0.7-canary.1
'@segment/ajv-human-errors': 2.1.2
'@swc/helpers': 0.4.14
'@taskr/clear': 1.1.0
@@ -8823,6 +8847,26 @@ packages:
- supports-color
dev: true
+ /body-parser/1.20.1:
+ resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.4
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.11.0
+ raw-body: 2.5.1
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/boolbase/1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -9083,6 +9127,11 @@ packages:
engines: {node: '>= 0.8'}
dev: true
+ /bytes/3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/cacache/15.3.0:
resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
engines: {node: '>= 10'}
@@ -9594,7 +9643,6 @@ packages:
/client-only/0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
- dev: false
/cliui/5.0.0:
resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}
@@ -10915,6 +10963,11 @@ packages:
engines: {node: '>= 0.6'}
dev: true
+ /depd/2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/deprecation/2.3.1:
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
dev: true
@@ -10930,6 +10983,11 @@ packages:
resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==}
dev: true
+ /destroy/1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dev: true
+
/detab/2.0.4:
resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==}
dependencies:
@@ -11253,7 +11311,7 @@ packages:
dev: true
/ee-first/1.1.1:
- resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: true
/ejs/3.1.8:
@@ -12532,7 +12590,7 @@ packages:
dev: true
/find-up/2.1.0:
- resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=}
+ resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==}
engines: {node: '>=4'}
dependencies:
locate-path: 2.0.0
@@ -13815,6 +13873,17 @@ packages:
toidentifier: 1.0.0
dev: true
+ /http-errors/2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+ dev: true
+
/http-parser-js/0.4.10:
resolution: {integrity: sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=}
dev: true
@@ -14814,7 +14883,7 @@ packages:
resolution: {integrity: sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==}
dependencies:
node-fetch: 2.6.7
- unfetch: 4.1.0
+ unfetch: 4.2.0
transitivePeerDependencies:
- encoding
dev: true
@@ -16455,6 +16524,10 @@ packages:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
dev: true
+ /lodash/4.17.20:
+ resolution: {integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==}
+ dev: true
+
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -16796,7 +16869,7 @@ packages:
dev: true
/media-typer/0.3.0:
- resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: true
@@ -19951,6 +20024,13 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
+ /qs/6.11.0:
+ resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.4
+ dev: true
+
/qs/6.5.2:
resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==}
engines: {node: '>=0.6'}
@@ -19961,11 +20041,6 @@ packages:
engines: {node: '>=0.6'}
dev: true
- /qs/6.9.1:
- resolution: {integrity: sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==}
- engines: {node: '>=0.6'}
- dev: true
-
/querystring-es3/0.2.1:
resolution: {integrity: sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=}
engines: {node: '>=0.4.x'}
@@ -20056,6 +20131,16 @@ packages:
unpipe: 1.0.0
dev: true
+ /raw-body/2.5.1:
+ resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+ dev: true
+
/rc/1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@@ -20817,7 +20902,7 @@ packages:
dev: true
/requires-port/1.0.0:
- resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true
/reselect/4.1.6:
@@ -21278,7 +21363,7 @@ packages:
jszip: 3.7.1
rimraf: 3.0.2
tmp: 0.2.1
- ws: 8.2.3
+ ws: 8.4.2
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -21406,6 +21491,10 @@ packages:
resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==}
dev: true
+ /setprototypeof/1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ dev: true
+
/sha.js/2.4.11:
resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
hasBin: true
@@ -21820,6 +21909,11 @@ packages:
engines: {node: '>= 0.6'}
dev: true
+ /statuses/2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/stream-browserify/3.0.0:
resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
dependencies:
@@ -21928,8 +22022,8 @@ packages:
call-bind: 1.0.2
define-properties: 1.1.3
es-abstract: 1.19.1
- get-intrinsic: 1.1.1
- has-symbols: 1.0.2
+ get-intrinsic: 1.1.2
+ has-symbols: 1.0.3
internal-slot: 1.0.3
regexp.prototype.flags: 1.3.1
side-channel: 1.0.4
@@ -22165,7 +22259,6 @@ packages:
'@babel/core': 7.18.0
client-only: 0.0.1
react: 18.2.0
- dev: false
/stylehacks/4.0.3:
resolution: {integrity: sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==}
@@ -22193,7 +22286,7 @@ packages:
formidable: 1.2.1
methods: 1.1.2
mime: 1.6.0
- qs: 6.9.1
+ qs: 6.11.0
readable-stream: 2.3.7
transitivePeerDependencies:
- supports-color
@@ -22707,6 +22800,11 @@ packages:
engines: {node: '>=0.6'}
dev: true
+ /toidentifier/1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+ dev: true
+
/totalist/1.1.0:
resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==}
engines: {node: '>=6'}
@@ -23032,8 +23130,8 @@ packages:
busboy: 1.6.0
dev: true
- /unfetch/4.1.0:
- resolution: {integrity: sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==}
+ /unfetch/4.2.0:
+ resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==}
dev: true
/unherit/1.1.2:
@@ -23312,7 +23410,7 @@ packages:
dev: true
/unpipe/1.0.0:
- resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=}
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
dev: true
diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
index b12561077e0ef..d78957b0d31a3 100644
--- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts
+++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts
@@ -49,7 +49,7 @@ describe('ReactRefreshLogBox app', () => {
)
await session.evaluate(() => document.querySelector('a').click())
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchSnapshot()
await cleanup()
@@ -228,7 +228,7 @@ describe('ReactRefreshLogBox app', () => {
`
)
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchSnapshot()
// TODO-APP: re-enable when error recovery doesn't reload the page.
@@ -325,7 +325,13 @@ describe('ReactRefreshLogBox app', () => {
export default ClassDefault;
`
)
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
+
+ await check(async () => {
+ const source = await session.getRedboxSource()
+ return source?.length > 1 ? 'success' : source
+ }, 'success')
+
expect(await session.getRedboxSource()).toMatchSnapshot()
await cleanup()
@@ -370,7 +376,7 @@ describe('ReactRefreshLogBox app', () => {
`
)
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
if (process.platform === 'win32') {
expect(await session.getRedboxSource()).toMatchSnapshot()
} else {
@@ -423,7 +429,7 @@ describe('ReactRefreshLogBox app', () => {
)
// We get an error because Foo didn't import React. Fair.
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchSnapshot()
// Let's add that to Foo.
@@ -475,7 +481,7 @@ describe('ReactRefreshLogBox app', () => {
)
await new Promise((resolve) => setTimeout(resolve, 1000))
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
if (process.platform === 'win32') {
expect(await session.getRedboxSource()).toMatchSnapshot()
} else {
@@ -562,7 +568,7 @@ describe('ReactRefreshLogBox app', () => {
`export default function FunctionDefault() { throw new Error('no'); }`
)
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchSnapshot()
expect(
await session.evaluate(() => document.querySelector('h2').textContent)
@@ -664,7 +670,7 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasErrorToast()).toBe(false)
+ expect(await session.hasRedbox()).toBe(false)
expect(
await session.evaluate(() => document.querySelector('p').textContent)
).toBe('hello')
@@ -681,7 +687,7 @@ describe('ReactRefreshLogBox app', () => {
`
)
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchSnapshot()
await session.patch(
@@ -764,9 +770,9 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasErrorToast()).toBe(false)
+ expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const header = await session.getRedboxDescription()
expect(header).toMatchSnapshot()
@@ -810,9 +816,9 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasErrorToast()).toBe(false)
+ expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const header2 = await session.getRedboxDescription()
expect(header2).toMatchSnapshot()
@@ -856,9 +862,9 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasErrorToast()).toBe(false)
+ expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const header3 = await session.getRedboxDescription()
expect(header3).toMatchSnapshot()
@@ -902,9 +908,9 @@ describe('ReactRefreshLogBox app', () => {
`
)
- expect(await session.hasErrorToast()).toBe(false)
+ expect(await session.hasRedbox()).toBe(false)
await session.evaluate(() => document.querySelector('button').click())
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const header4 = await session.getRedboxDescription()
expect(header4).toMatchInlineSnapshot(
diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts
index 9725b9b0580d2..88c19bf0cd53b 100644
--- a/test/development/acceptance-app/ReactRefreshRegression.test.ts
+++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts
@@ -280,7 +280,7 @@ describe('ReactRefreshRegression app', () => {
await browser.refresh()
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const source = await session.getRedboxSource()
expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(`
@@ -301,7 +301,7 @@ describe('ReactRefreshRegression app', () => {
await browser.refresh()
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const source = await session.getRedboxSource()
expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(`
@@ -323,7 +323,7 @@ describe('ReactRefreshRegression app', () => {
await browser.refresh()
- await session.waitForAndOpenRuntimeError()
+ expect(await session.hasRedbox(true)).toBe(true)
const source = await session.getRedboxSource()
expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(`
diff --git a/test/development/acceptance-app/helpers.ts b/test/development/acceptance-app/helpers.ts
index 0ad5c8db77e1c..f0787fb84b76d 100644
--- a/test/development/acceptance-app/helpers.ts
+++ b/test/development/acceptance-app/helpers.ts
@@ -2,9 +2,7 @@ import {
getRedboxDescription,
getRedboxHeader,
getRedboxSource,
- hasErrorToast,
hasRedbox,
- waitForAndOpenRuntimeError,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { NextInstance } from 'test/lib/next-modes/base'
@@ -102,12 +100,6 @@ export async function sandbox(
async hasRedbox(expected = false) {
return hasRedbox(browser, expected)
},
- async hasErrorToast(expected = false) {
- return hasErrorToast(browser, expected)
- },
- async waitForAndOpenRuntimeError() {
- await waitForAndOpenRuntimeError(browser)
- },
async getRedboxDescription() {
return getRedboxDescription(browser)
},
diff --git a/test/development/typescript-auto-install/index.test.ts b/test/development/typescript-auto-install/index.test.ts
index ec794f3a4aeb2..acfed68a05f33 100644
--- a/test/development/typescript-auto-install/index.test.ts
+++ b/test/development/typescript-auto-install/index.test.ts
@@ -2,7 +2,6 @@ import { createNext } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { check, renderViaHTTP } from 'next-test-utils'
import webdriver from 'next-webdriver'
-// @ts-expect-error missing types
import stripAnsi from 'strip-ansi'
describe('typescript-auto-install', () => {
diff --git a/test/e2e/app-dir/app-static.test.ts b/test/e2e/app-dir/app-static.test.ts
index c3f3f3a66eaf7..66a08b6c3a807 100644
--- a/test/e2e/app-dir/app-static.test.ts
+++ b/test/e2e/app-dir/app-static.test.ts
@@ -76,6 +76,10 @@ describe('app-dir static/dynamic handling', () => {
'ssr-auto/cache-no-store/page.js',
'ssr-auto/fetch-revalidate-zero/page.js',
'ssr-forced/page.js',
+ 'variable-revalidate/no-store/page.js',
+ 'variable-revalidate/revalidate-3.html',
+ 'variable-revalidate/revalidate-3.rsc',
+ 'variable-revalidate/revalidate-3/page.js',
])
})
@@ -167,6 +171,11 @@ describe('app-dir static/dynamic handling', () => {
initialRevalidateSeconds: false,
srcRoute: '/ssg-preview/[[...route]]',
},
+ '/variable-revalidate/revalidate-3': {
+ dataRoute: '/variable-revalidate/revalidate-3.rsc',
+ initialRevalidateSeconds: 3,
+ srcRoute: '/variable-revalidate/revalidate-3',
+ },
})
expect(manifest.dynamicRoutes).toEqual({
'/blog/[author]/[slug]': {
diff --git a/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js b/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js
index 34706f5284a4a..823d8131ba653 100644
--- a/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js
+++ b/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js
@@ -19,6 +19,3 @@ export default function Page() {
>
)
}
-
-// TODO-APP: remove revalidate config once next.revalidate is supported
-export const revalidate = 0
diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js b/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js
new file mode 100644
index 0000000000000..3f9ed053c5ad2
--- /dev/null
+++ b/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js
@@ -0,0 +1,18 @@
+import { cache, use } from 'react'
+
+export default function Layout({ children }) {
+ const getData = cache(() =>
+ fetch('https://next-data-api-endpoint.vercel.app/api/random?layout', {
+ next: { revalidate: 10 },
+ }).then((res) => res.text())
+ )
+ const dataPromise = getData()
+ const data = use(dataPromise)
+
+ return (
+ <>
+ revalidate 10: {data}
+ {children}
+ >
+ )
+}
diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js b/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js
new file mode 100644
index 0000000000000..b0c99184ef876
--- /dev/null
+++ b/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js
@@ -0,0 +1,19 @@
+import { cache, use } from 'react'
+
+export default function Page() {
+ const getData = cache(() =>
+ fetch('https://next-data-api-endpoint.vercel.app/api/random?page', {
+ cache: 'no-store',
+ }).then((res) => res.text())
+ )
+ const dataPromise = getData()
+ const data = use(dataPromise)
+
+ return (
+ <>
+ /variable-revalidate/no-cache
+ no-store: {data}
+ {Date.now()}
+ >
+ )
+}
diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js b/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js
new file mode 100644
index 0000000000000..da03c9760ef2d
--- /dev/null
+++ b/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js
@@ -0,0 +1,19 @@
+import { cache, use } from 'react'
+
+export default function Page() {
+ const getData = cache(() =>
+ fetch('https://next-data-api-endpoint.vercel.app/api/random?page', {
+ next: { revalidate: 3 },
+ }).then((res) => res.text())
+ )
+ const dataPromise = getData()
+ const data = use(dataPromise)
+
+ return (
+ <>
+ /variable-revalidate/revalidate-3
+ revalidate 3: {data}
+ {Date.now()}
+ >
+ )
+}
diff --git a/test/e2e/app-dir/app-static/next.config.js b/test/e2e/app-dir/app-static/next.config.js
index 771ea0e5638f9..e03d15af21fbd 100644
--- a/test/e2e/app-dir/app-static/next.config.js
+++ b/test/e2e/app-dir/app-static/next.config.js
@@ -1,6 +1,7 @@
module.exports = {
experimental: {
appDir: true,
+ fetchCache: true,
},
// assetPrefix: '/assets',
rewrites: async () => {
diff --git a/test/e2e/app-dir/dynamic-href.test.ts b/test/e2e/app-dir/dynamic-href.test.ts
index 3337e29d7098f..6615538c55e26 100644
--- a/test/e2e/app-dir/dynamic-href.test.ts
+++ b/test/e2e/app-dir/dynamic-href.test.ts
@@ -1,9 +1,6 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
-import {
- getRedboxDescription,
- waitForAndOpenRuntimeError,
-} from 'next-test-utils'
+import { getRedboxDescription, hasRedbox } from 'next-test-utils'
import path from 'path'
import webdriver from 'next-webdriver'
@@ -32,7 +29,7 @@ describe('dynamic-href', () => {
const browser = await webdriver(next.url, '/object')
// Error should show up
- await waitForAndOpenRuntimeError(browser)
+ expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
@@ -60,7 +57,7 @@ describe('dynamic-href', () => {
const browser = await webdriver(next.url, '/string')
// Error should show up
- await waitForAndOpenRuntimeError(browser)
+ expect(await hasRedbox(browser, true)).toBeTrue()
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"`
)
diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts
index a4e0278080c98..3bf7b92d542f8 100644
--- a/test/e2e/app-dir/index.test.ts
+++ b/test/e2e/app-dir/index.test.ts
@@ -5,9 +5,9 @@ import {
check,
fetchViaHTTP,
getRedboxHeader,
+ hasRedbox,
renderViaHTTP,
waitFor,
- waitForAndOpenRuntimeError,
} from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
@@ -2055,7 +2055,7 @@ describe('app dir', () => {
await browser.elementByCss('#error-trigger-button').click()
if (isDev) {
- await waitForAndOpenRuntimeError(browser)
+ expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatch(/this is a test/)
} else {
await browser
@@ -2107,7 +2107,7 @@ describe('app dir', () => {
await browser.elementByCss('#error-trigger-button').click()
if (isDev) {
- await waitForAndOpenRuntimeError(browser)
+ expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatch(/this is a test/)
} else {
expect(
diff --git a/test/e2e/app-dir/root-layout.test.ts b/test/e2e/app-dir/root-layout.test.ts
index 976ab8318c414..e75e15e1721cc 100644
--- a/test/e2e/app-dir/root-layout.test.ts
+++ b/test/e2e/app-dir/root-layout.test.ts
@@ -40,9 +40,9 @@ describe('app-dir root layout', () => {
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
- "Please make sure to include the following tags in your root layout: , .
+ "Please make sure to include the following tags in your root layout: , , .
- Missing required root layout tags: html, body"
+ Missing required root layout tags: html, head, body"
`)
})
@@ -54,9 +54,9 @@ describe('app-dir root layout', () => {
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
- "Please make sure to include the following tags in your root layout: , .
+ "Please make sure to include the following tags in your root layout: , , .
- Missing required root layout tags: html, body"
+ Missing required root layout tags: html, head, body"
`)
})
@@ -67,9 +67,9 @@ describe('app-dir root layout', () => {
expect(await hasRedbox(browser, true)).toBe(true)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
- "Please make sure to include the following tags in your root layout: , .
+ "Please make sure to include the following tags in your root layout: , , .
- Missing required root layout tags: html, body"
+ Missing required root layout tags: html, head, body"
`)
})
})
diff --git a/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js b/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js
index 7519206f5c7b3..f8fd9c6d9b3ea 100644
--- a/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js
+++ b/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js
@@ -4,6 +4,9 @@ export const revalidate = 0
export default function Root({ children }) {
return (
+
+ Hello World
+
{children}
)
diff --git a/test/e2e/next-font/app/pages/with-fallback.js b/test/e2e/next-font/app/pages/with-fallback.js
index e944fb3807dc7..8a5e258266c52 100644
--- a/test/e2e/next-font/app/pages/with-fallback.js
+++ b/test/e2e/next-font/app/pages/with-fallback.js
@@ -2,6 +2,7 @@ import { Open_Sans } from '@next/font/google'
const openSans = Open_Sans({
fallback: ['system-ui', 'Arial'],
variable: '--open-sans',
+ adjustFontFallback: false,
})
export default function WithFonts() {
diff --git a/test/e2e/next-font/index.test.ts b/test/e2e/next-font/index.test.ts
index 2298bcb133449..746a0d938d61d 100644
--- a/test/e2e/next-font/index.test.ts
+++ b/test/e2e/next-font/index.test.ts
@@ -108,7 +108,7 @@ describe('@next/font/google', () => {
className: expect.stringMatching(/__className_.{6}/),
style: {
fontFamily: expect.stringMatching(
- /^'__myFont1_.{6}', system-ui, '__myFont1_Fallback_.{6}'$/
+ /^'__myFont1_.{6}', '__myFont1_Fallback_.{6}', system-ui$/
),
fontStyle: 'italic',
fontWeight: 100,
@@ -275,27 +275,21 @@ describe('@next/font/google', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#with-fallback-fonts-classname")).fontFamily'
)
- ).toMatch(
- /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
- )
+ ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/)
// .style
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#with-fallback-fonts-style")).fontFamily'
)
- ).toMatch(
- /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
- )
+ ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/)
// .variable
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#with-fallback-fonts-variable")).fontFamily'
)
- ).toMatch(
- /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
- )
+ ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/)
})
})
diff --git a/test/e2e/trailingslash-with-rewrite/app/next.config.js b/test/e2e/trailingslash-with-rewrite/app/next.config.js
new file mode 100644
index 0000000000000..9f2a0ec8f0ac3
--- /dev/null
+++ b/test/e2e/trailingslash-with-rewrite/app/next.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ trailingSlash: true,
+ async rewrites() {
+ return [{ source: '/country/', destination: '/' }]
+ },
+}
diff --git a/test/e2e/trailingslash-with-rewrite/app/pages/index.js b/test/e2e/trailingslash-with-rewrite/app/pages/index.js
new file mode 100644
index 0000000000000..1236c5fdca1ae
--- /dev/null
+++ b/test/e2e/trailingslash-with-rewrite/app/pages/index.js
@@ -0,0 +1,7 @@
+export default function Home() {
+ return Welcome home
+}
+
+export async function getStaticProps() {
+ return { props: {} }
+}
diff --git a/test/e2e/trailingslash-with-rewrite/index.test.ts b/test/e2e/trailingslash-with-rewrite/index.test.ts
new file mode 100644
index 0000000000000..6929351f72fb3
--- /dev/null
+++ b/test/e2e/trailingslash-with-rewrite/index.test.ts
@@ -0,0 +1,25 @@
+import { join } from 'path'
+import { createNext, FileRef } from 'e2e-utils'
+import { NextInstance } from 'test/lib/next-modes/base'
+import { fetchViaHTTP } from 'next-test-utils'
+
+describe('trailingSlash:true with rewrites and getStaticProps', () => {
+ let next: NextInstance
+
+ if ((global as any).isNextDeploy) {
+ it('should skip for deploy mode for now', () => {})
+ return
+ }
+
+ beforeAll(async () => {
+ next = await createNext({
+ files: new FileRef(join(__dirname, './app')),
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it('should work', async () => {
+ const res = await fetchViaHTTP(next.url, '/country')
+ expect(await res.text()).toContain('Welcome home')
+ })
+})
diff --git a/test/integration/create-next-app/index.test.ts b/test/integration/create-next-app/index.test.ts
index 9fa555dc0f9c6..464b8e7a58e31 100644
--- a/test/integration/create-next-app/index.test.ts
+++ b/test/integration/create-next-app/index.test.ts
@@ -301,22 +301,29 @@ describe('create next app', () => {
it('should exit if the folder is not writable', async () => {
await useTempDir(async (cwd) => {
const projectName = 'not-writable'
+
+ // if the folder isn't able to be write restricted we can't test
+ // this so skip
+ if (
+ await fs
+ .writeFile(path.join(cwd, 'test'), 'hello')
+ .then(() => true)
+ .catch(() => false)
+ ) {
+ console.warn(
+ `Test folder is not write restricted skipping write permission test`
+ )
+ return
+ }
const res = await run([projectName, '--js', '--eslint'], {
cwd,
reject: false,
})
- if (process.platform === 'win32') {
- expect(res.exitCode).toBe(0)
- const files = ['package.json']
- projectFilesShouldExist({ cwd, projectName, files })
- return
- }
-
- expect(res.exitCode).toBe(1)
expect(res.stderr).toMatch(
/you do not have write permissions for this folder/
)
+ expect(res.exitCode).toBe(1)
}, 0o500)
})
diff --git a/test/integration/create-next-app/lib/specification.ts b/test/integration/create-next-app/lib/specification.ts
index dbdfd610aab09..ea529cabe9aec 100644
--- a/test/integration/create-next-app/lib/specification.ts
+++ b/test/integration/create-next-app/lib/specification.ts
@@ -28,7 +28,14 @@ export const projectSpecification: ProjectSpecification = {
'node_modules/next',
'.gitignore',
],
- deps: ['next', 'react', 'react-dom', 'eslint', 'eslint-config-next'],
+ deps: [
+ 'next',
+ '@next/font',
+ 'react',
+ 'react-dom',
+ 'eslint',
+ 'eslint-config-next',
+ ],
devDeps: [],
},
default: {
diff --git a/test/integration/getserversideprops-preview/test/index.test.js b/test/integration/getserversideprops-preview/test/index.test.js
index 7c7f68bbdf063..ff42f77db3a4c 100644
--- a/test/integration/getserversideprops-preview/test/index.test.js
+++ b/test/integration/getserversideprops-preview/test/index.test.js
@@ -197,6 +197,7 @@ describe('ServerSide Props Preview Mode', () => {
it('should return cookies to be expired after dev server reboot', async () => {
await killApp(app)
+ appPort = await findPort()
app = await launchApp(appDir, appPort)
const res = await fetchViaHTTP(
diff --git a/test/integration/initial-ref/pages/index.js b/test/integration/initial-ref/pages/index.js
index a0b6dab37292a..69b41de2d2b17 100644
--- a/test/integration/initial-ref/pages/index.js
+++ b/test/integration/initial-ref/pages/index.js
@@ -20,7 +20,7 @@ class App extends React.Component {
const { refHeight } = this.state
return (
-
+
DOM Ref test using 9.2.0
{`this component is ${refHeight}px tall`}
diff --git a/test/integration/prerender-preview/test/index.test.js b/test/integration/prerender-preview/test/index.test.js
index 32cc96ae23f6f..7ae614c826f63 100644
--- a/test/integration/prerender-preview/test/index.test.js
+++ b/test/integration/prerender-preview/test/index.test.js
@@ -290,6 +290,7 @@ describe('Prerender Preview Mode', () => {
it('should return cookies to be expired after dev server reboot', async () => {
await killApp(app)
+ appPort = await findPort()
app = await launchApp(appDir, appPort)
const res = await fetchViaHTTP(
diff --git a/test/integration/typescript/test/index.test.js b/test/integration/typescript/test/index.test.js
index 3268404f6c7d5..5f59da2fc2ba7 100644
--- a/test/integration/typescript/test/index.test.js
+++ b/test/integration/typescript/test/index.test.js
@@ -92,6 +92,7 @@ export default function EvilPage(): JSX.Element {
}
`
)
+ appPort = await findPort()
app = await launchApp(appDir, appPort)
const $ = await get$('/hello')
diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js
index c98686689a2c2..ec4592e1287c6 100644
--- a/test/lib/next-test-utils.js
+++ b/test/lib/next-test-utils.js
@@ -643,28 +643,6 @@ export async function hasRedbox(browser, expected = true) {
return false
}
-export async function hasErrorToast(browser, expected = true) {
- for (let i = 0; i < 30; i++) {
- const result = await evaluate(browser, () => {
- return Boolean(
- [].slice
- .call(document.querySelectorAll('nextjs-portal'))
- .find((p) => p.shadowRoot.querySelector('[data-nextjs-toast]'))
- )
- })
-
- if (result === expected) {
- return result
- }
- await waitFor(1000)
- }
- return false
-}
-
-export async function waitForAndOpenRuntimeError(browser) {
- return browser.waitForElementByCss('[data-nextjs-toast]').click()
-}
-
export async function getRedboxHeader(browser) {
return retry(
() =>
diff --git a/test/lib/use-temp-dir.ts b/test/lib/use-temp-dir.ts
index f53fcc6aebfc2..32db47b6c42a1 100644
--- a/test/lib/use-temp-dir.ts
+++ b/test/lib/use-temp-dir.ts
@@ -17,7 +17,7 @@ export async function useTempDir(
await fs.mkdirp(folder)
if (mode) {
- fs.chmod(folder, mode)
+ await fs.chmod(folder, mode)
}
try {