Skip to content

JS/TS: npm package subpath imports (next/image, next/link) produce no edge, while bare imports of the same package resolve #3595

Description

@Thaps07

Version: graphifyy 0.9.62 (PyPI) · Python 3.13.3 · macOS (darwin) · TypeScript extractor (tree-sitter)

Summary

A static import of an npm package subpath (next/image, next/link) produces no edge at all, while a bare import of the same package (next) resolves correctly to the package.json node.

The importing file keeps its node, so nothing looks wrong — the file is simply disconnected from the framework it depends on. graphify query "what uses next/image?" returns nothing, with no warning.

This is a big blind spot for Next.js / React codebases, where next/image, next/link and next/navigation carry most of the framework coupling. In a real 327-node Next.js site I measured 21 dropped import edges across 7 subpaths (next/link ×10, next/navigation ×3, next/server ×2, next/image ×2, next/headers ×2, next/og ×1, next/font/google ×1).

Note this is not the same as #3084, which reports that bare static imports also dangle — on 0.9.62 the bare case works (verified below), so that half appears to have been fixed since 0.9.39. The subpath case remains.

Minimal reproduction

Three files in an empty directory:

package.json

{
  "name": "mre-subpath-import",
  "version": "1.0.0",
  "dependencies": { "next": "16.2.7", "react": "19.2.4" }
}

bare.ts

import type { Metadata } from "next";
import { useState } from "react";
export const meta: Metadata = {};
export const s = useState;

subpath.ts

import Image from "next/image";
import Link from "next/link";
export const i = Image;
export const l = Link;

Then:

graphify extract . --code-only

Actual result

All four import edges should exist; only the two bare ones do.

NODES: ['bare.ts', 'dependencies', 'i', 'l', 'meta', 'name', 'next', 'next',
        'package.json', 'react', 'react', 's', 'subpath.ts', 'version']

IMPORT EDGES:
  package.json --imports--> next
  package.json --imports--> react
  bare.ts --imports_from--> next     <-- bare resolves
  bare.ts --imports_from--> react    <-- bare resolves
                                     <-- subpath.ts has NO import edges

subpath.ts is present as a node with zero outbound import edges.

Expected result

A subpath specifier should resolve to its owning package node, so that
subpath.ts --imports_from--> next exists (ideally twice, or once with the
subpath retained as edge metadata).

Stripping the specifier to its package root before canonicalization would cover
the common cases: next/image -> next, @scope/pkg/sub -> @scope/pkg
(first two segments for scoped packages).

Why it matters

Framework surface is exactly what an architecture graph should show. For a
Next.js project the questions you most want to ask — which routes use
next/headers, which pages client-navigate via next/link, which components
render next/image — all silently return nothing. An empty result is
indistinguishable from "no usages", so the gap is invisible unless you already
know the answer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions