Skip to content

Next.js

How midcode runs a Next.js project, how each element learns where it's written, and what you can edit, including pages, site settings and the free canvas.

View as Markdown

Next.js is the stack midcode edits most completely. Text, styles, images, layout, components with their variants and states, new pages, site settings and the free canvas outside the breakpoints: each one is written into your .tsx and .jsx files. Nothing is installed in the project and next.config is never touched.

The full set needs the App Router. A project that only has the Pages Router is edited on the canvas the same way and its pages are listed, but new pages and Site settings are written for the App Router. The free canvas comes to the Pages Router in the next release.

At a glance

Next.js
Detected bynext in the dependencies or devDependencies of package.json
Runs withnext dev, started by midcode with one --require in front
Elements are marked byA loader added in memory to Turbopack (or webpack) when the dev server starts
EditingFull, in .tsx and .jsx files
ComponentsYes: instances, props, variants and states, variables
Pagesapp/**/page.* and pages/**. New pages and removing them in the App Router
StylesTailwind 4 classes, or mid: classes with midcode.css imported in the root layout
Tried withNext.js 16 on Turbopack (16.3 among them), with Tailwind 4 and without Tailwind

How midcode runs it

When you open a folder, midcode reads its package.json. If next is in dependencies or devDependencies, the project is Next.js. That check comes before Vite, Astro and the others, so a Next.js project that also has vite installed is still Next.js.

If the dependencies aren’t installed, the canvas says “Dependencies are missing” and offers “Install with pnpm” (or npm, yarn, bun: the one your lockfile belongs to).

Then midcode starts the dev server itself. It doesn’t run your dev script:

Terminal
node --require /Applications/midcode.app/Contents/Resources/injected/next-hook.cjs \
  node_modules/next/dist/bin/next dev --port 4310 --hostname localhost
  • node is the one on your login shell’s PATH: midcode asks your shell, so a version manager’s Node is found. If the Mac has no Node at all, midcode’s own stands in.

  • next is your project’s own, found the way Node resolves it (so a hoisted install works).

  • The port is the first free one from 4310 up.

The dev server log (the button with the status dot, on the right of the top bar) starts with that command and the Node that runs it: $ next dev --port 4310 (/opt/homebrew/bin/node).

The --require is all midcode adds. It preloads a hook into next dev and into the processes Next forks. The hook waits for Next to load your config and, on the object Next hands back, in memory, adds three things:

  • a Turbopack rule for *.tsx and *.jsx that runs midcode’s loader first. Rules you already have for those files still run after it.

  • the same loader as a webpack rule, when Next runs on webpack (its default up to Next 15). Your own webpack() function runs first.

  • devIndicators: false, so Next’s dev badge doesn’t sit on top of the canvas.

Nothing is written to disk: not next.config, not package.json, not the lockfile. The marks exist only in the dev server midcode started. next build, a next dev you run in a terminal and your deployed site have none. Telemetry is also off for that one process (NEXT_TELEMETRY_DISABLED=1). What midcode does add to a project, and when, is listed in What midcode adds to your project.

If midcode quits or crashes, the dev server notices within a few seconds and stops itself, so the port and Next’s lock are free again.

What the loader adds

The loader runs on every .tsx and .jsx file outside node_modules. It adds attributes to the compiled output and leaves everything else byte for byte where it was, so line numbers stay true.

app/page.tsx
import { Card } from '@/components/Card'

export default function Page() {
  return (
    <main className="mx-auto max-w-3xl px-6 py-24">
      <h1 className="text-5xl font-semibold">Selected work</h1>
      <Card title="Oak table" />
    </main>
  )
}
components/Card.tsx
export function Card({ title }: { title: string }) {
  return (
    <article className="rounded-2xl p-6">
      <h3>{title}</h3>
    </article>
  )
}

This is what reaches the page:

HTML
<main data-mc="app/page.tsx:5:5" class="mx-auto max-w-3xl px-6 py-24">
  <h1 data-mc="app/page.tsx:6:7" class="text-5xl font-semibold">Selected work</h1>
  <article data-mc="components/Card.tsx:3:5" data-mcu="Card|app/page.tsx:7:7" class="rounded-2xl p-6">
    <h3 data-mc="components/Card.tsx:4:7">Oak table</h3>
  </article>
</main>
  • data-mc goes on every element written in your JSX: the file (relative to the project), the line and the column of its <. A wrapped element like <motion.div> counts as an element.

  • data-mci is handed to every component instance as a prop: Card|app/page.tsx:7:7, the name and where it’s used. It only shows in the page when the component passes its props on to an element.

  • data-mcu goes on the element a component returns, and says where that component is used. Nested components make a chain, outermost first, separated by spaces: Hero|app/page.tsx:9:7 Section|components/Hero.tsx:4:5. This is how Layers names components and how the root of a component is moved where it’s used.

Fragments, Suspense, StrictMode, Profiler, providers and consumers get no mark: they draw no element of their own.

Server and client components are marked alike: the loader works on the source file, and so do the edits.

What you can edit

Click the <h1> and the right panel shows where it’s written under Code. Change its size and one class is swapped in that file:

app/page.tsx
-      <h1 className="text-5xl font-semibold">Selected work</h1>+      <h1 className="text-6xl font-semibold">Selected work</h1>

Everything in the editor works in a Next.js project:

  • Text, edited in place, including text that lives in an array or a content file: see Text, images and video.

  • Position, size, layout, type, fill and effects, per breakpoint: see The style panel.

  • Images and video. The new file is copied into public/ and the reference rewritten. An image your code imports (import hero from './hero.jpg') is replaced in place, with a file of the same type.

  • Structure: Insert, and moving, duplicating and deleting. Interactive components from Insert are written once to components/midcode/ (src/components/midcode/ when your app lives in src).

  • Components with variants, states and typed props, and Variables.

Every edit is one step of ⌘Z. What makes an element editable, and what doesn’t, is in Code that midcode can edit.

Pages

The page menu in the top bar lists what midcode finds in src/app, app, src/pages and pages.

FilePage
app/page.tsx/
app/(marketing)/pricing/page.tsx/pricing
app/work/[slug]/page.tsx/work/[slug], a dynamic route
pages/about.tsx/about
pages/blog/index.tsx/blog

Pages can be .tsx, .ts, .jsx, .js or .mdx. Left out: api folders, folders that start with _ or @, and in pages/ the files that start with _ (_app, _document). A dynamic route is one entry, and its pages are found in the running site (its sitemap and the links on its pages), then in the page’s own generateStaticParams.

In an App Router project you can also make and remove pages. “New page” writes a page.tsx in a new folder, next to the closest parent page that exists:

app/about/page.tsx
export default function Page() {
  return (
    <main className="mx-auto max-w-3xl px-6 py-24">
      <h1 className="text-4xl font-semibold">About</h1>
      <p className="mt-4">A new page, made in midcode.</p>
    </main>
  )
}

“Remove page” moves the file to the Trash, and its folder too when the page was the only thing in it. Reordering, dynamic routes and the Assets tab are in Pages and navigation.

Site settings

The globe in the top bar opens Site settings. In the released version it only shows in Next.js projects. Title, description, search engines, favicon and social image are written the way Next.js reads them: export const metadata in the root layout or in the page, generateMetadata when a dynamic page’s title uses its item’s fields, and image files by convention (app/icon.*, app/opengraph-image.*). A change replaces only the value:

app/layout.tsx
 export const metadata: Metadata = {-  title: 'Create Next App',+  title: 'Oak & Iron',   description: 'Furniture made to order.', }

It needs a root layout (app/layout.tsx). The details are in Site settings. In the next release other kinds of project get the same form, written as tags of their <head>: Next.js keeps writing metadata.

The free canvas

In an App Router project, anything you draw (F), drop or paste on the empty canvas floats there. It lives in one real page, app/midcode-scratch/page.tsx (under src/app if that’s where your app is), that answers 404 in production, stays out of the page list and is never offered in Publish. See The free canvas.

Styles

A project counts as Tailwind 4 when one of its CSS files has @import "tailwindcss" or an @theme block. There, midcode writes Tailwind’s own classes and reads your @theme tokens: see Tailwind CSS.

In any other Next.js project (plain CSS, CSS Modules, Tailwind 3) midcode writes its own prefixed utilities, like mid:p-6, and compiles them to midcode.css. On your first style edit that file is created next to the root layout and imported there, as one undoable edit:

app/layout.tsx
 import type { Metadata } from 'next' import './globals.css'+import './midcode.css'

Without a root layout the import goes in pages/_app.*. If midcode finds neither, it still writes midcode.css and tells you to import it yourself. See Without Tailwind.

Limits

  • Only .tsx and .jsx files are marked. JSX written in a .js file, common in Next.js projects without TypeScript, gets no marks: its elements show, but midcode can’t tell where they’re written until the file is renamed to .jsx. The content of .mdx pages isn’t marked either.

  • midcode runs next dev itself. Flags in your dev script (--turbopack, --webpack, --experimental-https, a port) aren’t read, and a custom server (node server.js) isn’t used. In the next release you can give midcode your own command (see Any other stack); the site then shows without marks.

  • There is no fallback to your own script, as there is for Vite projects. If next dev doesn’t start, the canvas shows the error.

  • On Next 15 and older, a dev server started this way runs on webpack. The loader is written for it, but only Next 16 on Turbopack is on record as tried.

  • With the Pages Router only: no Site settings, which needs a root layout in app/, and in the released version no free canvas (the next release keeps it in pages/midcode-scratch.tsx). New page is still offered, and writes app/<path>/page.tsx: an App Router page beside your pages/ folder.

  • A new page starts with Tailwind class names. In a project without Tailwind 4 they do nothing until you restyle it.

  • An element drawn by a package (a UI library in node_modules) has no mark. The right panel says a library draws it: edit its parent, or the component that uses it.

  • An element moves within its file and its function. Text and classes that are computed are edited in code.

Troubleshooting

“A Next server is already running for this project”. Next allows one dev server per project folder, and midcode needs its own. Click “Stop it and use midcode”, or stop yours in the terminal and press Restart in the dev server log.

“Next.js is not in node_modules. Install the dependencies.” Use the “Install with…” button on the same card.

An element has no file and line in the right panel. It has no mark. Check that its file is .tsx or .jsx, and that it isn’t drawn by a package. If nothing in the project has one, the hook couldn’t attach to this version of Next: the dev server then runs as it always would, without marks. Send the report from the dev server log (“Send report”).

A style doesn’t show. In a project without Tailwind 4, check that midcode.css is imported in the root layout.

More in Troubleshooting and How midcode works.