I'm building a Shopify app using Remix without any third-party routing plugins. My project structure follows Remix's flat routing convention. For example, I have this folder structure:
custom-signs
└── app
└── routes
└── app.customisers
├── $customiserId.jsx
├── index.jsx
└── new.jsx
Only index.jsx loads correctly in the Shopify dev server. When I try to visit /app/customisers/new or /app/customisers/123, I get a 404 error, even though those files exist and are named according to Remix's flat routes spec.
To clarify:
I am not using any third-party plugins for routing.
The app works fine for other routes like /app, /auth.login, etc.
It seems like Remix is not picking up the subroutes under app.customisers.
Is there something I'm missing in how Remix resolves nested flat routes within a Shopify app context?
My app directory can be found in the image I've attached:
My app.jsx file:
export default function App() {
const { apiKey } = useLoaderData();
return (
<AppProvider isEmbeddedApp apiKey={apiKey}>
<NavMenu>
<Link to="/app" rel="home">
Home
</Link>
<Link to="/app/customisers">Customisers</Link>
<Link to="/app/additional">Additional page</Link>
</NavMenu>
<Outlet />
</AppProvider>
);
}
export function ErrorBoundary() {
return boundary.error(useRouteError());
}
export const headers = (headersArgs) => {
return boundary.headers(headersArgs);
};
Thank you!
Yes, you still need <Outlet />
even if you're not sharing components or layout. When you have a folder structure app.customisers/
, Remix creates a parent-child relationship between the routes, regardless of whether you want to share components.
Here's what happens when someone visits /app/customisers/new
:
Remix renders app.js
(which has <Outlet />
)
Inside that outlet, Remix renders app.customisers/index.jsx
Inside app.customisers/index.jsx
's <Outlet />
, Remix renders app.customisers/new.jsx
Without <Outlet />
in step 3, the new.jsx
component has nowhere to render, causing the 404.
You can do two things.
1st one is:
Add <Outlet />
to index:
// app.customisers/index.jsx
import { Outlet } from "@remix-run/react";
export default function CustomisersIndex() {
return <Outlet />; // Just pass through to child routes
}
2nd one is:
Restructure to avoid nesting. Move files to:
app.customisers._index.jsx
(for /app/customisers
)
app.customisers.new.jsx
(for /app/customisers/new
)
app.customisers.$customersId.jsx
(for /app/customisers/$customiserId
)
This creates sibling routes instead of parent-child routes, so no <Outlet />
needed.