Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions extensions/react-react-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# React Router v7 Extension

This extension adds React Router v7 (BrowserRouter setup, client-side routing) to your React template.

## Features

- **BrowserRouter Setup**: Configures global router at the root.
- **Client-Side Routing**: Demonstrates route mapping and code-splitting ready structure.
- **Sample Pages**: Includes a template Landing page and a sample `About` page to showcase navigation.

## Usage

### Route Configuration

All routes are declared in `src/routes/AppRoutes.tsx`:

```typescript
import React from 'react';
import { Route, Routes } from 'react-router-dom';

import Landing from '@/pages/Landing';
import About from '@/pages/About';

const AppRoutes = () => (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/about" element={<About />} />
</Routes>
);

export default AppRoutes;
```

### Navigating between pages

Use the `<Link>` component from `react-router-dom`:

```typescript
import { Link } from 'react-router-dom';

export default function MyComponent() {
return (
<Link to="/about">About Page</Link>
);
}
```

## Documentation

See [React Router v7 Documentation](https://reactrouter.com) for more details.
19 changes: 19 additions & 0 deletions extensions/react-react-router/[src]/App.tsx.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { Suspense } from 'react';
import { BrowserRouter } from 'react-router-dom';

import '<%= projectImportPath %>theme';

import { composeAppProviders } from '<%= projectImportPath %>app-providers';
import Loading from '<%= projectImportPath %>components/Loading';
import AppRoutes from '<%= projectImportPath %>routes/AppRoutes';

const App = () =>
composeAppProviders(
<Suspense fallback={<Loading />}>
<BrowserRouter basename={import.meta.env.BASE_URL}>
<AppRoutes />
</BrowserRouter>
</Suspense>,
);

export default App;
31 changes: 31 additions & 0 deletions extensions/react-react-router/[src]/pages/About.tsx.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { Link } from 'react-router-dom';

import './Landing.css';

const About = () => (
<div className="cna-landing cna-landing--react-vite">
<header className="cna-landing__header">
<img src="/logo.svg" alt="Create Awesome Node App" className="cna-landing__logo" width="40" height="40" />
<span className="cna-landing__brand">Create Awesome Node App</span>
</header>

<main className="cna-landing__main">
<section className="cna-landing__hero">
<span className="cna-landing__eyebrow">React Router v7</span>
<h1 className="cna-landing__title">About</h1>
<p className="cna-landing__lead">
Sample page from the React Router extension — add more routes in{' '}
<code className="cna-landing__code">src/routes/AppRoutes.tsx</code>.
</p>
<div className="cna-landing__actions">
<Link to="/" className="cna-landing__button cna-landing__button--primary">
Back to Home
</Link>
</div>
</section>
</main>
</div>
);

export default About;
139 changes: 139 additions & 0 deletions extensions/react-react-router/[src]/pages/Landing.tsx.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React from 'react';
import { Link } from 'react-router-dom';
import './Landing.css';

interface Feature {
icon: string;
title: string;
description: string;
}

const features: Feature[] = [
{
icon: '⚡',
title: 'Vite-native HMR',
description: 'Edit a component and see it update in milliseconds — no full reload tax.',
},
{
icon: '⚛️',
title: 'React 19 SPA',
description: 'Client-rendered React with modern hooks and a lean runtime footprint.',
},
{
icon: '🧭',
title: 'React Router 7',
description: 'Nested client routes already wired so you can grow screens without a rewrite.',
},
{
icon: '🛡️',
title: 'TypeScript + lint',
description: 'ESLint and Prettier ship configured so style debates stay out of PRs.',
},
{
icon: '📦',
title: 'Feature folders',
description: 'Co-locate routes, state, and UI per feature as the SPA grows.',
},
{
icon: '🧩',
title: 'Addon surface',
description: 'Layer UI kits, state libraries, tests, or Electron when you need them.',
},
];

type Doc = { label: string; href: string } | { label: string; to: string };

const docs: Doc[] = [
{ label: 'Project overview', href: '/docs/README.md' },
{ label: 'Project structure', href: '/docs/PROJECT_STRUCTURE.md' },
{ label: 'Components & styling', href: '/docs/COMPONENTS_AND_STYLING.md' },
{ label: 'State management', href: '/docs/STATE_MANAGEMENT.md' },
{ label: 'About Page (React Router Demo)', to: '/about' },
];

const withIndex = (index: number) => ({ '--index': index }) as React.CSSProperties;

const Landing = () => (
<div className="cna-landing cna-landing--react-vite">
<header className="cna-landing__header">
<img src="/logo.svg" alt="Create Awesome Node App" className="cna-landing__logo" width="40" height="40" />
<span className="cna-landing__brand">Create Awesome Node App</span>
</header>

<main className="cna-landing__main">
<section className="cna-landing__hero" style={withIndex(0)}>
<span className="cna-landing__eyebrow">React + Vite</span>
<h1 className="cna-landing__title"><%= projectName %></h1>
<p className="cna-landing__lead">
A client-side React SPA tuned for instant HMR. Reach for this when you want a fast browser app without server
rendering in the critical path.
</p>
<div className="cna-landing__actions">
<a href="/docs/README.md" className="cna-landing__button cna-landing__button--primary">
Read the docs
</a>
<span className="cna-landing__button cna-landing__button--secondary">
Run <code className="cna-landing__code">npm run dev</code>
</span>
<a
href="https://create-awesome-node-app.vercel.app/templates"
className="cna-landing__button cna-landing__button--ghost"
target="_blank"
rel="noreferrer"
>
Template docs
</a>
</div>
</section>

<section className="cna-landing__features">
<h2 className="cna-landing__section-title">Built for SPA velocity</h2>
<div className="cna-landing__grid">
{features.map((feature, index) => (
<article key={feature.title} className="cna-landing__card" style={withIndex(index + 1)}>
<span className="cna-landing__card-icon" aria-hidden="true">
{feature.icon}
</span>
<h3 className="cna-landing__card-title">{feature.title}</h3>
<p className="cna-landing__card-text">{feature.description}</p>
</article>
))}
</div>
</section>

<section className="cna-landing__docs" style={withIndex(features.length + 1)}>
<h2 className="cna-landing__section-title">Next steps</h2>
<nav className="cna-landing__doc-list" aria-label="Documentation">
{docs.map((doc) =>
'to' in doc ? (
<Link key={doc.label} to={doc.to} className="cna-landing__doc-link">
<span className="cna-landing__doc-label">{doc.label}</span>
<span className="cna-landing__doc-arrow" aria-hidden="true">
→
</span>
</Link>
) : (
<a key={doc.label} href={doc.href} className="cna-landing__doc-link">
<span className="cna-landing__doc-label">{doc.label}</span>
<span className="cna-landing__doc-arrow" aria-hidden="true">
→
</span>
</a>
),
)}
</nav>
</section>
</main>

<footer className="cna-landing__footer" style={withIndex(features.length + 2)}>
<p>
Generated with{' '}
<a href="https://www.npmjs.com/package/create-awesome-node-app" target="_blank" rel="noreferrer">
create-awesome-node-app
</a>
</p>
</footer>
</div>
);

export default Landing;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React from 'react';
import { Route, Routes } from 'react-router-dom';

import Landing from '<%= projectImportPath %>pages/Landing';
import About from '<%= projectImportPath %>pages/About';

const AppRoutes = () => (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/about" element={<About />} />
</Routes>
);

Expand Down
26 changes: 26 additions & 0 deletions extensions/react-react-router/docs/README.md.append
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

## React Router v7

This project includes [React Router v7](https://reactrouter.com) for client-side routing.

### Quick Start

Add new routes in `src/routes/AppRoutes.tsx`:

```tsx
import About from '@/pages/About';
// ...
<Route path="/about" element={<About />} />
```

Navigate using `<Link>`:

```tsx
import { Link } from 'react-router-dom';
// ...
<Link to="/about">About</Link>
```

### Learn More

- [React Router v7 Documentation](https://reactrouter.com)
5 changes: 5 additions & 0 deletions extensions/react-react-router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"react-router-dom": "^7.1.5"
}
}
6 changes: 5 additions & 1 deletion scripts/ci/run-scaffold-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function run(phase, cmd, cmdArgs, options = {}) {
console.log(`\n▶ [${phase}] ${cmd} ${cmdArgs.join(' ')}`);
const result = spawnSync(cmd, cmdArgs, {
stdio: 'inherit',
shell: process.platform === 'win32',
env: {
...process.env,
CI: 'true',
Expand All @@ -73,7 +74,10 @@ function resolveFileTarget(fileUrl) {
fail('scaffold', `URL must be file://, got: ${fileUrl}`);
}
const parsed = new URL(fileUrl);
const pathname = decodeURIComponent(parsed.pathname);
let pathname = decodeURIComponent(parsed.pathname);
if (process.platform === 'win32' && pathname.startsWith('/')) {
pathname = pathname.replace(/^\/([A-Za-z]:)/, '$1');
}
const subdir = parsed.searchParams.get('subdir');
return subdir ? path.join(pathname, subdir) : pathname;
}
Expand Down
17 changes: 16 additions & 1 deletion templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@
{
"name": "Auth.js v5 for Next.js",
"slug": "nextjs-auth",
"description": "Add Auth.js v5 (next-auth@beta, latest: 5.0.0-beta.31) to your Next.js project with OAuth providers support, database sessions, and TypeScript-typed session. Note: v5 remains in beta as of 2026 \u2014 see https://authjs.dev for updates.",
"description": "Add Auth.js v5 (next-auth@beta, latest: 5.0.0-beta.31) to your Next.js project with OAuth providers support, database sessions, and TypeScript-typed session. Note: v5 remains in beta as of 2026 — see https://authjs.dev for updates.",
"url": "https://github.com/Create-Node-App/cna-templates/tree/main/extensions/nextjs-auth",
"type": "nextjs",
"category": "Auth",
Expand Down Expand Up @@ -1069,6 +1069,21 @@
"Edge",
"Drizzle"
]
},
{
"name": "React Router v7",
"slug": "react-react-router",
"description": "Adds React Router v7 with BrowserRouter setup and sample pages.",
"url": "https://github.com/Create-Node-App/cna-templates/tree/main/extensions/react-react-router",
"type": "react",
"category": "Tooling",
"labels": [
"React",
"React Router",
"Routing",
"Vite",
"SPA"
]
}
]
}
7 changes: 2 additions & 5 deletions templates/react-vite-starter/[src]/App.tsx.template
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import React, { Suspense } from 'react';
import { BrowserRouter } from 'react-router-dom';

import '<%= projectImportPath %>theme';

import { composeAppProviders } from '<%= projectImportPath %>app-providers';
import Loading from '<%= projectImportPath %>components/Loading';
import AppRoutes from '<%= projectImportPath %>routes/AppRoutes';
import Landing from '<%= projectImportPath %>pages/Landing';

const App = () =>
composeAppProviders(
<Suspense fallback={<Loading />}>
<BrowserRouter basename={import.meta.env.BASE_URL}>
<AppRoutes />
</BrowserRouter>
<Landing />
</Suspense>,
);

Expand Down
10 changes: 5 additions & 5 deletions templates/react-vite-starter/[src]/pages/Landing.tsx.template
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const features: Feature[] = [
description: 'Client-rendered React with modern hooks and a lean runtime footprint.',
},
{
icon: '🧭',
title: 'React Router 7',
description: 'Nested client routes already wired so you can grow screens without a rewrite.',
icon: '🎨',
title: 'Styling Ready',
description: 'Tailwind CSS, Sass, and Less support built-in and ready for your components.',
},
{
icon: '🛡️',
Expand Down Expand Up @@ -61,8 +61,8 @@ const Landing = () => (
<span className="cna-landing__eyebrow">React + Vite</span>
<h1 className="cna-landing__title"><%= projectName %></h1>
<p className="cna-landing__lead">
A client-side React SPA tuned for instant HMR. Reach for this when you want a fast browser app without
server rendering in the critical path.
A client-side React SPA tuned for instant HMR. Reach for this when you want a fast browser app without server
rendering in the critical path.
</p>
<div className="cna-landing__actions">
<a href="/docs/README.md" className="cna-landing__button cna-landing__button--primary">
Expand Down
1 change: 0 additions & 1 deletion templates/react-vite-starter/package/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ module.exports = {
react: "^19.0.0",
"react-dom": "^19.0.0",
"react-error-boundary": "^6.1.2",
"react-router-dom": "^7.1.5",
"web-vitals": "^4.2.4",
};