next.js/packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance.tsx
instant-guidance.tsx244 lines6.0 KB
import { css } from '../../utils/css'

const DOCS = 'https://nextjs.org/docs/messages/blocking-route'

type CardColor = 'blue' | 'purple' | 'red'

type FixCard = {
  title: string
  color: CardColor
  snippets: Snippet[]
  conditional?: boolean
}

type Snippet = {
  text: string
  highlight?: boolean
}

const runtimeCards: FixCard[] = [
  {
    title: 'Move within Suspense',
    color: 'purple',
    snippets: [
      { text: '<Suspense fallback={…}>', highlight: true },
      { text: '  <DataChild />' },
      { text: '</Suspense>', highlight: true },
    ],
  },
  {
    title: 'Make route params static',
    color: 'blue',
    conditional: true,
    snippets: [
      { text: 'export async function' },
      { text: '  generateStaticParams() {', highlight: true },
      { text: '  return [{ slug: "…" }]' },
      { text: '}' },
    ],
  },
  {
    title: 'Allow blocking route',
    color: 'red',
    snippets: [
      { text: 'export const instant = false', highlight: true },
      { text: '' },
      { text: 'export default async function Page() {' },
    ],
  },
]

const dynamicCards: FixCard[] = [
  {
    title: 'Cache dynamic data',
    color: 'blue',
    snippets: [
      { text: 'async function getData() {' },
      { text: '  "use cache"', highlight: true },
      { text: '  return db.query(…)' },
      { text: '}' },
    ],
  },
  {
    title: 'Move within Suspense',
    color: 'purple',
    snippets: [
      { text: '<Suspense fallback={…}>', highlight: true },
      { text: '  <DataChild />' },
      { text: '</Suspense>', highlight: true },
    ],
  },
  {
    title: 'Allow blocking route',
    color: 'red',
    snippets: [
      { text: 'export const instant = false', highlight: true },
      { text: '' },
      { text: 'export default async function Page() {' },
    ],
  },
]

function CardGrid({ cards }: { cards: FixCard[] }) {
  return (
    <div data-nextjs-card-grid>
      {cards.map((card) => (
        <div
          data-nextjs-fix-card
          data-card-color={card.color}
          data-card-conditional={card.conditional || undefined}
          key={card.title}
        >
          <pre data-nextjs-fix-snippet>
            {card.snippets.map((s, i) => (
              <span
                key={i}
                data-snippet-line
                data-snippet-highlight={s.highlight || undefined}
              >
                {s.text}
                {'\n'}
              </span>
            ))}
          </pre>
          <span data-nextjs-fix-card-title>{card.title}</span>
        </div>
      ))}
    </div>
  )
}

export function InstantGuidance({
  variant,
}: {
  variant: 'runtime' | 'navigation'
}) {
  const cards = variant === 'navigation' ? dynamicCards : runtimeCards

  return (
    <div data-nextjs-instant-guidance>
      <p data-nextjs-instant-explanation>
        This blocks navigation, leading to a slower user experience.{' '}
        <a href={DOCS} target="_blank" rel="noopener noreferrer">
          Learn more
        </a>
      </p>

      <p data-nextjs-instant-fix-heading>Ways to fix this:</p>

      <CardGrid cards={cards} />
    </div>
  )
}

export const INSTANT_GUIDANCE_STYLES = css`
  [data-nextjs-instant-guidance] {
    margin-top: 16px;
    padding: 0 16px 16px;
  }

  [data-nextjs-instant-explanation] {
    font-size: var(--size-14);
    line-height: var(--size-20);
    color: var(--color-gray-900);
    margin: 0 0 16px;
  }

  [data-nextjs-instant-explanation] a {
    color: var(--color-blue-900);
    text-decoration: none;
  }

  [data-nextjs-instant-explanation] a:hover {
    text-decoration: underline;
  }

  [data-nextjs-instant-fix-heading] {
    font-size: var(--size-14);
    font-weight: 400;
    color: var(--color-gray-900);
    margin: 0 0 20px;
    padding-top: 16px;
    border-top: 1px solid var(--color-gray-alpha-400);
  }

  /* ── Grid ───────────────────────────────────── */
  [data-nextjs-card-grid] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 12px;
  }

  /* ── Card ─────────────────────────────────────── */
  [data-nextjs-fix-card] {
    min-width: 0;
    overflow: hidden;
  }

  [data-nextjs-fix-card-title] {
    display: block;
    margin-top: 10px;
    font-size: var(--size-13);
    color: var(--color-gray-900);
    text-align: center;
  }

  [data-card-conditional] [data-nextjs-fix-snippet] {
    border-style: dashed;
  }

  /* ── Snippet ──────────────────────────────────── */
  [data-nextjs-fix-snippet] {
    font-family: var(--font-stack-monospace);
    font-size: 11.5px;
    line-height: 1.6;
    margin: 0;
    padding: 14px;
    white-space: pre;
    overflow: hidden;
    background: var(--color-background-200);
    border: 1px solid var(--color-gray-alpha-400);
    border-radius: var(--rounded-lg);
    height: 100px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: left;
  }

  /* ── Card colors (border + highlight text only) ── */
  [data-card-color='blue'] [data-nextjs-fix-snippet] {
    border-color: var(--color-instant-border-blue);
  }

  [data-card-color='purple'] [data-nextjs-fix-snippet] {
    border-color: var(--color-instant-border-purple);
  }

  [data-card-color='red'] [data-nextjs-fix-snippet] {
    border-color: var(--color-instant-border-red);
  }

  [data-snippet-line] {
    display: block;
    color: var(--color-gray-800);
  }

  [data-snippet-line][data-snippet-highlight] {
    color: var(--color-gray-1000);
    font-weight: 500;
  }

  [data-card-color='blue'] [data-snippet-line][data-snippet-highlight] {
    color: var(--color-blue-800);
  }

  [data-card-color='purple'] [data-snippet-line][data-snippet-highlight] {
    color: var(--color-instant-text-purple);
  }

  [data-card-color='red'] [data-snippet-line][data-snippet-highlight] {
    color: var(--color-red-800);
  }
`
Quest for Codev2.0.0
/
SIGN IN