diff --git a/.github/.react-version b/.github/.react-version index 2818c978d4b77..2de43dcf77c1f 100644 --- a/.github/.react-version +++ b/.github/.react-version @@ -1 +1 @@ -19.0.0-rc-dfd30974ab-20240613 \ No newline at end of file +19.0.0-rc-6230622a1a-20240610 \ No newline at end of file diff --git a/crates/next-custom-transforms/src/chain_transforms.rs b/crates/next-custom-transforms/src/chain_transforms.rs index ebcdb62bd0ff5..f2576f90165f5 100644 --- a/crates/next-custom-transforms/src/chain_transforms.rs +++ b/crates/next-custom-transforms/src/chain_transforms.rs @@ -109,6 +109,9 @@ pub struct TransformOptions { #[serde(default)] pub optimize_server_react: Option, + + #[serde(default)] + pub debug_function_name: bool, } pub fn custom_before_pass<'a, C>( @@ -297,6 +300,7 @@ where }, None => Either::Right(noop()), }, + Optional::new(crate::transforms::debug_fn_name::debug_fn_name(), opts.debug_function_name), as_folder(crate::transforms::pure::pure_magic(comments)), ) } diff --git a/crates/next-custom-transforms/src/transforms/debug_fn_name.rs b/crates/next-custom-transforms/src/transforms/debug_fn_name.rs new file mode 100644 index 0000000000000..769e3fec585a9 --- /dev/null +++ b/crates/next-custom-transforms/src/transforms/debug_fn_name.rs @@ -0,0 +1,185 @@ +use std::fmt::Write; + +use swc_core::{ + atoms::Atom, + common::{util::take::Take, DUMMY_SP}, + ecma::{ + ast::{ + CallExpr, Callee, ExportDefaultExpr, Expr, FnDecl, FnExpr, KeyValueProp, MemberProp, + ObjectLit, PropOrSpread, VarDeclarator, + }, + utils::ExprFactory, + visit::{as_folder, Fold, VisitMut, VisitMutWith}, + }, +}; + +pub fn debug_fn_name() -> impl VisitMut + Fold { + as_folder(DebugFnName::default()) +} + +#[derive(Default)] +struct DebugFnName { + path: String, + in_target: bool, + in_var_target: bool, + in_default_export: bool, +} + +impl VisitMut for DebugFnName { + fn visit_mut_call_expr(&mut self, n: &mut CallExpr) { + if self.in_var_target || (self.path.is_empty() && !self.in_default_export) { + n.visit_mut_children_with(self); + return; + } + + if let Some(target) = is_target_callee(&n.callee) { + let old_in_target = self.in_target; + self.in_target = true; + let orig_len = self.path.len(); + if !self.path.is_empty() { + self.path.push('.'); + } + self.path.push_str(&target); + + n.visit_mut_children_with(self); + + self.path.truncate(orig_len); + self.in_target = old_in_target; + } else { + n.visit_mut_children_with(self); + } + } + + fn visit_mut_export_default_expr(&mut self, n: &mut ExportDefaultExpr) { + let old_in_default_export = self.in_default_export; + self.in_default_export = true; + + n.visit_mut_children_with(self); + + self.in_default_export = old_in_default_export; + } + + fn visit_mut_expr(&mut self, n: &mut Expr) { + n.visit_mut_children_with(self); + + if self.in_target { + match n { + Expr::Arrow(..) | Expr::Fn(FnExpr { ident: None, .. }) => { + // useLayoutEffect(() => ...); + // + // becomes + // + // + // useLayoutEffect({'MyComponent.useLayoutEffect': () => + // ...}['MyComponent.useLayoutEffect']); + + let orig = n.take(); + let key = Atom::from(&*self.path); + + *n = Expr::Object(ObjectLit { + span: DUMMY_SP, + props: vec![PropOrSpread::Prop(Box::new( + swc_core::ecma::ast::Prop::KeyValue(KeyValueProp { + key: swc_core::ecma::ast::PropName::Str(key.clone().into()), + value: Box::new(orig), + }), + ))], + }) + .computed_member(key) + .into(); + } + + _ => {} + } + } + } + + fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) { + let orig_len = self.path.len(); + if !self.path.is_empty() { + self.path.push('.'); + } + self.path.push_str(n.ident.sym.as_str()); + + n.visit_mut_children_with(self); + + self.path.truncate(orig_len); + } + + fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) { + if let Some(Expr::Call(call)) = n.init.as_deref() { + let name = is_target_callee(&call.callee).and_then(|target| { + let name = n.name.as_ident()?; + + Some((name.sym.clone(), target)) + }); + + if let Some((name, target)) = name { + let old_in_var_target = self.in_var_target; + self.in_var_target = true; + + let old_in_target = self.in_target; + self.in_target = true; + let orig_len = self.path.len(); + if !self.path.is_empty() { + self.path.push('.'); + } + let _ = write!(self.path, "{target}({name})"); + + n.visit_mut_children_with(self); + + self.path.truncate(orig_len); + self.in_target = old_in_target; + self.in_var_target = old_in_var_target; + return; + } + } + + if let Some(Expr::Arrow(..) | Expr::Fn(FnExpr { ident: None, .. }) | Expr::Call(..)) = + n.init.as_deref() + { + let name = n.name.as_ident(); + + if let Some(name) = name { + let orig_len = self.path.len(); + if !self.path.is_empty() { + self.path.push('.'); + } + self.path.push_str(name.sym.as_str()); + + n.visit_mut_children_with(self); + + self.path.truncate(orig_len); + return; + } + } + + n.visit_mut_children_with(self); + } +} + +fn is_target_callee(e: &Callee) -> Option { + match e { + Callee::Expr(e) => match &**e { + Expr::Ident(i) => { + if i.sym.starts_with("use") { + Some(i.sym.clone()) + } else { + None + } + } + Expr::Member(me) => match &me.prop { + MemberProp::Ident(i) => { + if i.sym.starts_with("use") { + Some(i.sym.clone()) + } else { + None + } + } + _ => None, + }, + _ => None, + }, + _ => None, + } +} diff --git a/crates/next-custom-transforms/src/transforms/mod.rs b/crates/next-custom-transforms/src/transforms/mod.rs index 92f7fcd165a0c..a3acb4ce0a389 100644 --- a/crates/next-custom-transforms/src/transforms/mod.rs +++ b/crates/next-custom-transforms/src/transforms/mod.rs @@ -1,6 +1,7 @@ pub mod amp_attributes; pub mod cjs_finder; pub mod cjs_optimizer; +pub mod debug_fn_name; pub mod disallow_re_export_all_in_page; pub mod dynamic; pub mod fonts; diff --git a/crates/next-custom-transforms/tests/fixture.rs b/crates/next-custom-transforms/tests/fixture.rs index 72cac88b8563a..945758906e223 100644 --- a/crates/next-custom-transforms/tests/fixture.rs +++ b/crates/next-custom-transforms/tests/fixture.rs @@ -6,6 +6,7 @@ use std::{ use next_custom_transforms::transforms::{ amp_attributes::amp_attributes, cjs_optimizer::cjs_optimizer, + debug_fn_name::debug_fn_name, dynamic::{next_dynamic, NextDynamicMode}, fonts::{next_font_loaders, Config as FontLoaderConfig}, named_import_transform::named_import_transform, @@ -630,3 +631,24 @@ fn next_transform_strip_page_exports_fixture_default(output: PathBuf) { run_stip_page_exports_test(&input, &output, ExportFilter::StripDataExports); } + +#[fixture("tests/fixture/debug-fn-name/**/input.js")] +fn test_debug_name(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + + test_fixture( + syntax(), + &|_| { + let top_level_mark = Mark::fresh(Mark::root()); + let unresolved_mark = Mark::fresh(Mark::root()); + + chain!( + swc_core::ecma::transforms::base::resolver(unresolved_mark, top_level_mark, true), + debug_fn_name() + ) + }, + &input, + &output, + Default::default(), + ); +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/input.js new file mode 100644 index 0000000000000..fd558273f211a --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/input.js @@ -0,0 +1,6 @@ +const Component = () => { + useLayoutEffect(() => {}) + useEffect(() => {}) + const onClick = useCallback(() => []) + const computed = useMemo(() => {}) +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/output.js new file mode 100644 index 0000000000000..cd0b8ba7c6212 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/anonymous-component/output.js @@ -0,0 +1,14 @@ +const Component = ()=>{ + useLayoutEffect({ + "Component.useLayoutEffect": ()=>{} + }["Component.useLayoutEffect"]); + useEffect({ + "Component.useEffect": ()=>{} + }["Component.useEffect"]); + const onClick = useCallback({ + "Component.useCallback(onClick)": ()=>[] + }["Component.useCallback(onClick)"]); + const computed = useMemo({ + "Component.useMemo(computed)": ()=>{} + }["Component.useMemo(computed)"]); +}; diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/input.js new file mode 100644 index 0000000000000..260b2578123e5 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/input.js @@ -0,0 +1,6 @@ +function useSomething() { + useLayoutEffect(() => {}) + useEffect(() => {}) + const onClick = useCallback(() => []) + const computed = useMemo(() => {}) +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/output.js new file mode 100644 index 0000000000000..dff6ea08cdc61 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/composite-hook/output.js @@ -0,0 +1,14 @@ +function useSomething() { + useLayoutEffect({ + "useSomething.useLayoutEffect": ()=>{} + }["useSomething.useLayoutEffect"]); + useEffect({ + "useSomething.useEffect": ()=>{} + }["useSomething.useEffect"]); + const onClick = useCallback({ + "useSomething.useCallback(onClick)": ()=>[] + }["useSomething.useCallback(onClick)"]); + const computed = useMemo({ + "useSomething.useMemo(computed)": ()=>{} + }["useSomething.useMemo(computed)"]); +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/input.js new file mode 100644 index 0000000000000..cd1cf9d7969e6 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/input.js @@ -0,0 +1,6 @@ +export default () => { + useLayoutEffect(() => {}) + useEffect(() => {}) + const onClick = useCallback(() => []) + const computed = useMemo(() => {}) +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/output.js new file mode 100644 index 0000000000000..4323650b80467 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/export-default/output.js @@ -0,0 +1,14 @@ +export default (()=>{ + useLayoutEffect({ + "useLayoutEffect": ()=>{} + }["useLayoutEffect"]); + useEffect({ + "useEffect": ()=>{} + }["useEffect"]); + const onClick = useCallback({ + "useCallback(onClick)": ()=>[] + }["useCallback(onClick)"]); + const computed = useMemo({ + "useMemo(computed)": ()=>{} + }["useMemo(computed)"]); +}); diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/input.js new file mode 100644 index 0000000000000..bcc7d088b0f47 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/input.js @@ -0,0 +1,6 @@ +const Component = someHoC(() => { + useLayoutEffect(() => {}) + useEffect(() => {}) + const onClick = useCallback(() => []) + const computed = useMemo(() => {}) +}) diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/output.js new file mode 100644 index 0000000000000..0a84ae7849d71 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/hoc/output.js @@ -0,0 +1,14 @@ +const Component = someHoC(()=>{ + useLayoutEffect({ + "Component.useLayoutEffect": ()=>{} + }["Component.useLayoutEffect"]); + useEffect({ + "Component.useEffect": ()=>{} + }["Component.useEffect"]); + const onClick = useCallback({ + "Component.useCallback(onClick)": ()=>[] + }["Component.useCallback(onClick)"]); + const computed = useMemo({ + "Component.useMemo(computed)": ()=>{} + }["Component.useMemo(computed)"]); +}); diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/input.js new file mode 100644 index 0000000000000..5fa6eb48a710f --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/input.js @@ -0,0 +1 @@ +useMemo(() => {}) diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/output.js new file mode 100644 index 0000000000000..d086de46a51d5 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/lone-memo/output.js @@ -0,0 +1 @@ +useMemo(()=>{}); diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/input.js new file mode 100644 index 0000000000000..0100bc58cd6e7 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/input.js @@ -0,0 +1,6 @@ +function MyComponent() { + useLayoutEffect(() => {}) + useEffect(() => {}) + const onClick = useCallback(() => []) + const computed = useMemo(() => {}) +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/output.js new file mode 100644 index 0000000000000..8a88618f87afc --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/normal/output.js @@ -0,0 +1,14 @@ +function MyComponent() { + useLayoutEffect({ + "MyComponent.useLayoutEffect": ()=>{} + }["MyComponent.useLayoutEffect"]); + useEffect({ + "MyComponent.useEffect": ()=>{} + }["MyComponent.useEffect"]); + const onClick = useCallback({ + "MyComponent.useCallback(onClick)": ()=>[] + }["MyComponent.useCallback(onClick)"]); + const computed = useMemo({ + "MyComponent.useMemo(computed)": ()=>{} + }["MyComponent.useMemo(computed)"]); +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/input.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/input.js new file mode 100644 index 0000000000000..9252f7481b06f --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/input.js @@ -0,0 +1,4 @@ +function C() { + function handleClick() {} + const onClick = useCallback(handleClick, []) +} diff --git a/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/output.js b/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/output.js new file mode 100644 index 0000000000000..239a7688cda50 --- /dev/null +++ b/crates/next-custom-transforms/tests/fixture/debug-fn-name/outlined-functions/output.js @@ -0,0 +1,4 @@ +function C() { + function handleClick() {} + const onClick = useCallback(handleClick, []); +} diff --git a/crates/next-custom-transforms/tests/full.rs b/crates/next-custom-transforms/tests/full.rs index a0a2bb784b777..155d17313ce7d 100644 --- a/crates/next-custom-transforms/tests/full.rs +++ b/crates/next-custom-transforms/tests/full.rs @@ -81,6 +81,7 @@ fn test(input: &Path, minify: bool) { optimize_barrel_exports: None, optimize_server_react: None, prefer_esm: false, + debug_function_name: false, }; let unresolved_mark = Mark::new(); diff --git a/docs/02-app/01-building-your-application/04-caching/index.mdx b/docs/02-app/01-building-your-application/04-caching/index.mdx index aec4bbb1e94b0..fa203143acb29 100644 --- a/docs/02-app/01-building-your-application/04-caching/index.mdx +++ b/docs/02-app/01-building-your-application/04-caching/index.mdx @@ -335,13 +335,13 @@ With the Router Cache: The cache is stored in the browser's temporary memory. Two factors determine how long the router cache lasts: - **Session**: The cache persists across navigation. However, it's cleared on page refresh. -- **Automatic Invalidation Period**: The cache of layouts and loading states is automatically invalidated after a specific time. The duration depends on how the resource was [prefetched](/docs/app/api-reference/components/link#prefetch): - - **Default Prefetching** (`prefetch={null}` or unspecified): 0 seconds - - **Full Prefetching**: (`prefetch={true}` or `router.prefetch`): 5 minutes +- **Automatic Invalidation Period**: The cache of layouts and loading states is automatically invalidated after a specific time. The duration depends on how the resource was [prefetched](/docs/app/api-reference/components/link#prefetch), and if the resource was [statically generated](/docs/app/building-your-application/rendering/server-components#static-rendering-default): + - **Default Prefetching** (`prefetch={null}` or unspecified): not cached for dynamic pages, 5 minutes for static pages. + - **Full Prefetching** (`prefetch={true}` or `router.prefetch`): 5 minutes for both static & dynamic pages. While a page refresh will clear **all** cached segments, the automatic invalidation period only affects the individual segment from the time it was prefetched. -> **Good to know**: The experimental [`staleTimes`](/docs/app/api-reference/next-config-js/staleTimes) config option can be used to enable caching of page segments. +> **Good to know**: The experimental [`staleTimes`](/docs/app/api-reference/next-config-js/staleTimes) config option can be used to adjust the automatic invalidation times mentioned above. ### Invalidation diff --git a/docs/02-app/01-building-your-application/11-upgrading/04-app-router-migration.mdx b/docs/02-app/01-building-your-application/11-upgrading/04-app-router-migration.mdx index 3511d95683d2a..3d8c046dc0aa7 100644 --- a/docs/02-app/01-building-your-application/11-upgrading/04-app-router-migration.mdx +++ b/docs/02-app/01-building-your-application/11-upgrading/04-app-router-migration.mdx @@ -435,7 +435,7 @@ In `app`, you should use the three new hooks imported from `next/navigation`: [` - The new `useRouter` hook is imported from `next/navigation` and has different behavior to the `useRouter` hook in `pages` which is imported from `next/router`. - The [`useRouter` hook imported from `next/router`](/docs/pages/api-reference/functions/use-router) is not supported in the `app` directory but can continue to be used in the `pages` directory. - The new `useRouter` does not return the `pathname` string. Use the separate `usePathname` hook instead. -- The new `useRouter` does not return the `query` object. Use the separate `useSearchParams` hook instead. +- The new `useRouter` does not return the `query` object. Search parameters and dynamic route parameters are now separate. Use the `useSearchParams` and `useParams` hooks instead. - You can use `useSearchParams` and `usePathname` together to listen to page changes. See the [Router Events](/docs/app/api-reference/functions/use-router#router-events) section for more details. - These new hooks are only supported in Client Components. They cannot be used in Server Components. diff --git a/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx b/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx index 53576ebac1f0a..232765688ad5e 100644 --- a/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx +++ b/docs/02-app/02-api-reference/05-next-config-js/staleTimes.mdx @@ -25,9 +25,9 @@ module.exports = nextConfig The `static` and `dynamic` properties correspond with the time period (in seconds) based on different types of [link prefetching](/docs/app/api-reference/components/link#prefetch). -- The `dynamic` property is used when the `prefetch` prop on `Link` is left unspecified or is set to `false`. +- The `dynamic` property is used when the page is neither statically generated nor fully prefetched (i.e., with prefetch={true}). - Default: 0 seconds (not cached) -- The `static` property is used when the `prefetch` prop on `Link` is set to `true`, or when calling [`router.prefetch`](/docs/app/building-your-application/caching#routerprefetch). +- The `static` property is used for statically generated pages, or when the `prefetch` prop on `Link` is set to `true`, or when calling [`router.prefetch`](/docs/app/building-your-application/caching#routerprefetch). - Default: 5 minutes > **Good to know:** @@ -35,13 +35,12 @@ The `static` and `dynamic` properties correspond with the time period (in second > - [Loading boundaries](/docs/app/api-reference/file-conventions/loading) are considered reusable for the `static` period defined in this configuration. > - This doesn't affect [partial rendering](/docs/app/building-your-application/routing/linking-and-navigating#4-partial-rendering), **meaning shared layouts won't automatically be refetched on every navigation, only the page segment that changes.** > - This doesn't change [back/forward caching](/docs/app/building-your-application/caching#client-side-router-cache) behavior to prevent layout shift and to prevent losing the browser scroll position. -> - The different properties of this config refer to variable levels of "liveness" and are unrelated to whether the segment itself is opting into static or dynamic rendering. In other words, the current `static` default of 5 minutes suggests that data feels static by virtue of it being revalidated infrequently. You can learn more about the Client Router Cache [here](/docs/app/building-your-application/caching#client-side-router-cache). ### Version History -| Version | Changes | -| --------- | ------------------------------------------------------------------ | -| `v15.0.0` | `staleTimes` enables and configures the duration for page segments | -| `v14.2.0` | experimental `staleTimes` introduced | +| Version | Changes | +| --------- | ------------------------------------------------------ | +| `v15.0.0` | The "dynamic" staleTime default changed from 30s to 0s | +| `v14.2.0` | experimental `staleTimes` introduced | diff --git a/docs/03-pages/01-building-your-application/09-deploying/04-ci-build-caching.mdx b/docs/03-pages/01-building-your-application/09-deploying/04-ci-build-caching.mdx index ca6a154062cdd..0f65780d04173 100644 --- a/docs/03-pages/01-building-your-application/09-deploying/04-ci-build-caching.mdx +++ b/docs/03-pages/01-building-your-application/09-deploying/04-ci-build-caching.mdx @@ -13,7 +13,7 @@ Here are some example cache configurations for common CI providers: ## Vercel -Next.js caching is automatically configured for you. There's no action required on your part. +Next.js caching is automatically configured for you. There's no action required on your part. If you are using Turborepo on Vercel, [learn more here](https://vercel.com/docs/monorepos/turborepo). ## CircleCI diff --git a/examples/reproduction-template/package.json b/examples/reproduction-template/package.json index 42c43bfdaf725..9c0776817d8a6 100644 --- a/examples/reproduction-template/package.json +++ b/examples/reproduction-template/package.json @@ -7,8 +7,8 @@ }, "dependencies": { "next": "canary", - "react": "19.0.0-rc-dfd30974ab-20240613", - "react-dom": "19.0.0-rc-dfd30974ab-20240613" + "react": "19.0.0-rc.0", + "react-dom": "19.0.0-rc.0" }, "devDependencies": { "@types/node": "20.12.12", diff --git a/examples/with-slate/app/api/editor-state/route.ts b/examples/with-slate/app/api/editor-state/route.ts new file mode 100644 index 0000000000000..a07ea10895948 --- /dev/null +++ b/examples/with-slate/app/api/editor-state/route.ts @@ -0,0 +1,8 @@ +export async function POST(req: Request) { + const editorState = await req.json(); + console.log("TODO: Save editorState on the server", editorState); + + return Response.json({ + status: "ok", + }); +} diff --git a/examples/with-slate/app/layout.tsx b/examples/with-slate/app/layout.tsx new file mode 100644 index 0000000000000..5ff842ef07b83 --- /dev/null +++ b/examples/with-slate/app/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: "Next.js", + description: "Generated by Next.js", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/examples/with-slate/pages/index.tsx b/examples/with-slate/app/page.tsx similarity index 56% rename from examples/with-slate/pages/index.tsx rename to examples/with-slate/app/page.tsx index 4471917c2c7d0..b3c3c67c83974 100644 --- a/examples/with-slate/pages/index.tsx +++ b/examples/with-slate/app/page.tsx @@ -1,39 +1,32 @@ +"use client"; + import { useState } from "react"; import { createEditor, Descendant } from "slate"; import { Slate, Editable, withReact } from "slate-react"; -import { withHistory } from "slate-history"; -import { InferGetServerSidePropsType } from "next"; -export async function getServerSideProps() { - return { - props: { - editorState: [ - { - children: [ - { text: "This is editable plain text, just like a