create-react-adam 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Setup Node.js
18
18
  uses: actions/setup-node@v6
19
19
  with:
20
- node-version: "24.11.1"
20
+ node-version: "24.18.0"
21
21
  cache: "npm"
22
22
 
23
23
  - name: Install dependencies
@@ -36,7 +36,7 @@ jobs:
36
36
  run: npm run test:e2e
37
37
 
38
38
  - name: Upload Playwright report
39
- uses: actions/upload-artifact@v5
39
+ uses: actions/upload-artifact@v6
40
40
  if: always()
41
41
  with:
42
42
  name: playwright-report
@@ -44,7 +44,7 @@ jobs:
44
44
  retention-days: 30
45
45
 
46
46
  - name: Upload Allure results
47
- uses: actions/upload-artifact@v5
47
+ uses: actions/upload-artifact@v6
48
48
  if: always()
49
49
  with:
50
50
  name: allure-results
@@ -0,0 +1,33 @@
1
+ name: Lighthouse CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ lighthouse:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v6
19
+ with:
20
+ node-version: "24.18.0"
21
+ cache: "npm"
22
+
23
+ - name: Install dependencies
24
+ run: npm install
25
+
26
+ - name: Build
27
+ run: npm run build
28
+
29
+ - name: Run Lighthouse CI
30
+ uses: treosh/lighthouse-ci-action@v12
31
+ with:
32
+ configPath: ./lighthouserc.json
33
+ uploadArtifacts: true
package/template/.nvmrc CHANGED
@@ -1,2 +1,2 @@
1
- 24.11.1
1
+ 24.18.0
2
2
 
@@ -1,6 +1,6 @@
1
- # **PROJECT_NAME**
1
+ # {{PROJECT_NAME}}
2
2
 
3
- A React application created with create-react-adam.
3
+ A React application created with [create-react-adam](https://github.com/adamjsturge/create-react-adam).
4
4
 
5
5
  ## Getting Started
6
6
 
@@ -12,14 +12,6 @@ This will start the development server at `http://localhost:5173`.
12
12
 
13
13
  ## Available Scripts
14
14
 
15
- ### `npm run itDoesNotWork`
16
-
17
- Runs basic troubleshooting checks:
18
-
19
- - Installs dependencies
20
- - Checks if Vite dev server is running
21
- - Provides helpful diagnostic information
22
-
23
15
  ### `npm run dev`
24
16
 
25
17
  Starts the development server with hot module replacement.
@@ -32,49 +24,200 @@ Builds the app for production. The build artifacts will be stored in the `dist/`
32
24
 
33
25
  Previews the production build locally.
34
26
 
35
- ### `npm run lint`
27
+ ### `npm run lint` / `npm run lint:check`
28
+
29
+ Runs ESLint with auto-fix / check-only (for CI).
30
+
31
+ ### `npm run format` / `npm run format:check`
32
+
33
+ Formats all files with Prettier (organizes imports, sorts Tailwind classes) / check-only (for CI).
34
+
35
+ ### `npm run setFormatToPrecommitHook`
36
36
 
37
- Runs ESLint to check for code quality issues.
37
+ Installs a git pre-commit hook that formats and lints staged files. If this
38
+ project was scaffolded outside an existing repository, the hook was already
39
+ installed for you.
38
40
 
39
- ### `npm run format`
41
+ ### `npm run test:e2e` (if E2E was included)
40
42
 
41
- Formats all files with Prettier, organizing imports and sorting Tailwind classes.
43
+ Runs Playwright E2E tests from the `e2e/` directory. `test:e2e:ui` opens
44
+ Playwright UI mode and `test:e2e:report` generates an Allure report.
42
45
 
43
- ## Tech Stack
46
+ ### `npm run itDoesNotWork`
44
47
 
45
- - **React** - UI library
46
- - **TypeScript** - Type safety
47
- - **Vite** - Build tool and dev server
48
- - **Wouter** - Lightweight routing
49
- - **Tailwind CSS** - Utility-first CSS framework
50
- - **ESLint & Prettier** - Code quality and formatting
48
+ Runs basic troubleshooting checks: installs dependencies, checks whether the
49
+ Vite dev server is running, and prints diagnostic information.
51
50
 
52
51
  ## Project Structure
53
52
 
54
53
  ```
55
54
  src/
56
- ├── pages/ # Page components
57
- ├── Home/index.tsx
58
- │ └── About/index.tsx
59
- ├── App.tsx # Main app with routes
60
- ├── main.tsx # Entry point
61
- └── app.css # Global styles (Tailwind imports)
55
+ ├── pages/ # Route pages (Home, About, NotFound)
56
+ ├── components/ # Reusable components (Button, PreloadLink)
57
+ ├── types/ # Shared TypeScript types
58
+ ├── utils/ # Utility functions (if included)
59
+ ├── App.tsx # Routing, skip link, <main> landmark
60
+ ├── routes.ts # Route import map + preload helpers
61
+ ├── main.tsx # Entry point
62
+ └── app.css # Tailwind theme + base styles
62
63
  ```
63
64
 
65
+ Routing uses [Wouter](https://github.com/molefrog/wouter). Pages are
66
+ code-split with `React.lazy`, and each page sets its own `document.title`.
67
+
64
68
  ## Adding New Pages
65
69
 
66
70
  1. Create a new component in `src/pages/`
67
- 2. Import and add a route in `src/App.tsx`
68
-
69
- Example:
71
+ 2. Add its import thunk to `src/routes.ts` (this both registers it for
72
+ preloading and gives `App.tsx` its lazy import)
73
+ 3. Add a lazy component and route in `src/App.tsx`
70
74
 
71
75
  ```tsx
72
- import NewPage from "./pages/NewPage";
76
+ // src/routes.ts
77
+ export const routeImports: Record<string, PageLoader> = {
78
+ // ...
79
+ "/new": () => import("./pages/NewPage"),
80
+ };
81
+
82
+ // src/App.tsx
83
+ const NewPage = lazy(routeImports["/new"]);
73
84
 
74
85
  // In the Switch component:
75
86
  <Route path="/new" component={NewPage} />;
76
87
  ```
77
88
 
89
+ 4. Link to it with `<PreloadLink href="/new">` so the chunk preloads on
90
+ hover/focus/touch (wouter's plain `<Link>` works too, just without the
91
+ preload).
92
+
93
+ ## Route Preloading
94
+
95
+ Pages are lazy-loaded, so without preloading every navigation waits for a
96
+ network round trip. The template closes that gap in three layers:
97
+
98
+ 1. **One import map** (`src/routes.ts`): the router and the preloaders share
99
+ the same import thunks, and dynamic imports dedupe by module — so a
100
+ preloaded chunk is never downloaded twice.
101
+ 2. **Preload on intent** (`src/components/PreloadLink.tsx`): links fire the
102
+ target page's import on hover, focus, or touch. Hover-to-click is
103
+ typically 200-400ms — usually enough for the chunk to arrive first.
104
+ 3. **Idle backstop** (`preloadAllRoutesWhenIdle` in `src/routes.ts`, called
105
+ from `App.tsx`): after the window `load` event, remaining chunks are
106
+ fetched one at a time during idle periods. It never competes with
107
+ first-paint resources (so Lighthouse is unaffected) and skips users on
108
+ data-saver or 2G connections.
109
+
110
+ `App.tsx` also routes against a `useDeferredValue`-deferred location, so the
111
+ previous page stays painted until the next page is ready instead of flashing
112
+ the blank `Suspense` fallback. Preloading makes navigation fast; the deferred
113
+ location makes it smooth.
114
+
115
+ **To remove preloading**, follow the steps in the header comment of
116
+ `src/routes.ts` — in short: inline the imports back into `lazy()` in
117
+ `App.tsx`, swap `<PreloadLink>` back to wouter's `<Link>`, and delete
118
+ `src/routes.ts` and `src/components/PreloadLink.tsx`.
119
+
120
+ ## Performance, SEO, and Accessibility
121
+
122
+ - **Fonts**: Inter is self-hosted from `public/fonts/` (variable `woff2`,
123
+ `font-display: swap`, preloaded in `index.html`). No third-party requests.
124
+ - **SEO**: `index.html` ships meta description, Open Graph, and Twitter card
125
+ tags — update them as your app takes shape. `public/robots.txt` is included.
126
+ - **Accessibility**: a skip-to-content link, `<main>` landmark, and visible
127
+ `:focus-visible` styles are wired into `App.tsx` / `app.css`.
128
+
129
+ ## ESLint: prefer-webp-images (if included)
130
+
131
+ A local rule in `eslint-rules/prefer-webp-images.js` errors on references to
132
+ `.png`, `.jpg`, `.jpeg`, `.gif`, or `.bmp` images in imports, JSX attributes,
133
+ and string literals. Convert images to WebP at
134
+ [tools.sturge.dev/webp](https://tools.sturge.dev/webp).
135
+
136
+ For the rare image that must stay in another format:
137
+
138
+ ```tsx
139
+ // eslint-disable-next-line adamjsturge/prefer-webp-images
140
+ import legacyLogo from "./legacy-logo.png";
141
+ ```
142
+
143
+ ## Utilities (if included)
144
+
145
+ ### `classNames`
146
+
147
+ Joins class names, skipping falsy values, with clsx-style conditional objects:
148
+
149
+ ```tsx
150
+ import classNames from "./utils/classNames";
151
+
152
+ classNames("btn", isActive && "btn-active", { hidden: !isOpen });
153
+ // => "btn btn-active hidden" depending on the conditions
154
+ ```
155
+
156
+ ### `storage` and `useReactPersist`
157
+
158
+ `localStorage` wrapper with JSON serialization and optional expiry, plus a
159
+ `useState`-compatible hook that persists across reloads:
160
+
161
+ ```tsx
162
+ import storage, { useReactPersist } from "./utils/Storage";
163
+
164
+ storage.save("token", "abc123", { secondsTillExpiry: 3600 });
165
+ storage.load("token", null);
166
+
167
+ const [count, setCount] = useReactPersist("counter", 0);
168
+ ```
169
+
170
+ ### `useUrlState`
171
+
172
+ `useState`-compatible hook that syncs a value with a URL query parameter.
173
+ Supports string and number values (the type follows the default value):
174
+
175
+ ```tsx
176
+ import { useUrlState } from "./utils/useUrlState";
177
+
178
+ const [page, setPage] = useUrlState("page", 1);
179
+ const [tab, setTab] = useUrlState("tab", "overview");
180
+ ```
181
+
182
+ ### `useInternetConnected`
183
+
184
+ Runs a callback when the browser is online and every time the connection
185
+ comes back:
186
+
187
+ ```tsx
188
+ import { useInternetConnected } from "./utils/Internet";
189
+
190
+ useInternetConnected(() => refetch(), [refetch]);
191
+ ```
192
+
193
+ ### `safeTimeout`
194
+
195
+ Drop-in `setTimeout` replacement that clamps delays above the 32-bit signed
196
+ integer maximum (~24.8 days) instead of letting them overflow and fire
197
+ immediately:
198
+
199
+ ```tsx
200
+ import { safeTimeout } from "./utils/helpers";
201
+
202
+ safeTimeout(() => expireSession(), thirtyDaysInMs);
203
+ ```
204
+
205
+ ## CI Workflows
206
+
207
+ - **Code Checks** (`.github/workflows/check.yml`): type checking, formatting,
208
+ linting, and a production build on every push and PR to `main`.
209
+ - **E2E Tests** (`.github/workflows/e2e.yml`, if included): Playwright tests
210
+ with report artifacts.
211
+ - **Lighthouse CI** (`.github/workflows/lighthouse.yml`, if included): audits
212
+ the production build and fails below accessibility ≥ 95, SEO ≥ 95, and
213
+ performance ≥ 85. Budgets live in `lighthouserc.json`.
214
+
215
+ ## Dependency Management
216
+
217
+ Dependencies are pinned to exact versions and `.npmrc` sets
218
+ `save-exact=true`, so future installs stay pinned. The repo is
219
+ Renovate-friendly.
220
+
78
221
  ## Learn More
79
222
 
80
223
  - [React Documentation](https://react.dev)
@@ -0,0 +1,13 @@
1
+ # Dependencies
2
+ node_modules
3
+
4
+ # Test results
5
+ test-results
6
+ playwright-report
7
+ allure-results
8
+ allure-report
9
+ playwright/.cache
10
+
11
+ # Logs
12
+ *.log
13
+
@@ -9,7 +9,7 @@
9
9
  "test:e2e:report": "allure generate ./allure-results --clean && allure open"
10
10
  },
11
11
  "devDependencies": {
12
- "@playwright/test": "1.57.0",
13
- "allure-playwright": "3.4.2"
12
+ "@playwright/test": "1.61.1",
13
+ "allure-playwright": "3.10.2"
14
14
  }
15
15
  }
@@ -0,0 +1,66 @@
1
+ const NON_WEBP_IMAGE_PATTERN = /\.(png|jpe?g|gif|bmp)$/i;
2
+
3
+ /**
4
+ * Flags references to non-WebP raster images (.png, .jpg, .jpeg, .gif, .bmp)
5
+ * in imports, JSX attributes, and string literals. WebP/AVIF/SVG are allowed.
6
+ */
7
+ function findNonWebpImage(value) {
8
+ if (typeof value !== "string") {
9
+ return null;
10
+ }
11
+
12
+ // Handle srcSet-style values ("a.png 1x, b.png 2x") and Vite suffixes
13
+ // ("./a.png?url") by checking each path-like token.
14
+ for (const token of value.split(/[\s,]+/)) {
15
+ const path = token.split(/[?#]/)[0];
16
+ if (NON_WEBP_IMAGE_PATTERN.test(path)) {
17
+ return token;
18
+ }
19
+ }
20
+
21
+ return null;
22
+ }
23
+
24
+ const preferWebpImages = {
25
+ meta: {
26
+ type: "suggestion",
27
+ docs: {
28
+ description:
29
+ "Prefer WebP images over PNG/JPEG/GIF/BMP for smaller file sizes and faster page loads",
30
+ },
31
+ messages: {
32
+ preferWebp:
33
+ 'Image "{{path}}" is not WebP. Convert it at https://tools.sturge.dev/webp',
34
+ },
35
+ schema: [],
36
+ },
37
+ create(context) {
38
+ function check(node, value) {
39
+ const match = findNonWebpImage(value);
40
+ if (match) {
41
+ context.report({
42
+ node,
43
+ messageId: "preferWebp",
44
+ data: { path: match },
45
+ });
46
+ }
47
+ }
48
+
49
+ return {
50
+ Literal(node) {
51
+ check(node, node.value);
52
+ },
53
+ TemplateLiteral(node) {
54
+ if (node.quasis.length === 1) {
55
+ check(node, node.quasis[0].value.cooked);
56
+ }
57
+ },
58
+ };
59
+ },
60
+ };
61
+
62
+ export default {
63
+ rules: {
64
+ "prefer-webp-images": preferWebpImages,
65
+ },
66
+ };
@@ -8,6 +8,7 @@ import reactHooks from "eslint-plugin-react-hooks";
8
8
  import reactRefresh from "eslint-plugin-react-refresh";
9
9
  import unicorn from "eslint-plugin-unicorn";
10
10
  import tseslint from "typescript-eslint";
11
+ import adamjsturge from "./eslint-rules/prefer-webp-images.js";
11
12
 
12
13
  export default tseslint.config({
13
14
  extends: [
@@ -25,6 +26,7 @@ export default tseslint.config({
25
26
  "jsx-a11y": jsxA11y,
26
27
  "react-hooks": reactHooks,
27
28
  "react-refresh": reactRefresh,
29
+ adamjsturge,
28
30
  },
29
31
  languageOptions: {
30
32
  parserOptions: {
@@ -48,6 +50,7 @@ export default tseslint.config({
48
50
  "warn",
49
51
  { allowConstantExport: true },
50
52
  ],
53
+ "adamjsturge/prefer-webp-images": "error",
51
54
  },
52
55
  files: ["**/*.{ts,tsx}"],
53
56
  ignores: ["dist"],
@@ -0,0 +1,54 @@
1
+ import eslint from "@eslint/js";
2
+ import prettier from "eslint-config-prettier";
3
+ import deMorgan from "eslint-plugin-de-morgan";
4
+ import importPlugin from "eslint-plugin-import";
5
+ import jsxA11y from "eslint-plugin-jsx-a11y";
6
+ import promisePlugin from "eslint-plugin-promise";
7
+ import reactHooks from "eslint-plugin-react-hooks";
8
+ import reactRefresh from "eslint-plugin-react-refresh";
9
+ import unicorn from "eslint-plugin-unicorn";
10
+ import tseslint from "typescript-eslint";
11
+
12
+ export default tseslint.config({
13
+ extends: [
14
+ eslint.configs.recommended,
15
+ ...tseslint.configs.recommended,
16
+ ...tseslint.configs.strict,
17
+ prettier,
18
+ promisePlugin.configs["flat/recommended"],
19
+ importPlugin.flatConfigs.recommended,
20
+ importPlugin.flatConfigs.typescript,
21
+ unicorn.configs["unopinionated"],
22
+ deMorgan.configs.recommended,
23
+ ],
24
+ plugins: {
25
+ "jsx-a11y": jsxA11y,
26
+ "react-hooks": reactHooks,
27
+ "react-refresh": reactRefresh,
28
+ },
29
+ languageOptions: {
30
+ parserOptions: {
31
+ ecmaFeatures: {
32
+ jsx: true,
33
+ },
34
+ },
35
+ },
36
+ rules: {
37
+ "@typescript-eslint/no-unused-vars": [
38
+ "error",
39
+ {
40
+ argsIgnorePattern: "^_",
41
+ varsIgnorePattern: "^_",
42
+ caughtErrorsIgnorePattern: "^_",
43
+ },
44
+ ],
45
+ ...jsxA11y.flatConfigs.strict.rules,
46
+ ...reactHooks.configs.recommended.rules,
47
+ "react-refresh/only-export-components": [
48
+ "warn",
49
+ { allowConstantExport: true },
50
+ ],
51
+ },
52
+ files: ["**/*.{ts,tsx}"],
53
+ ignores: ["dist"],
54
+ });
@@ -3,6 +3,25 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta
7
+ name="description"
8
+ content="__PROJECT_NAME__ — a React app built with create-react-adam"
9
+ />
10
+ <meta name="theme-color" content="#2563eb" />
11
+ <meta property="og:title" content="__PROJECT_NAME__" />
12
+ <meta
13
+ property="og:description"
14
+ content="__PROJECT_NAME__ — a React app built with create-react-adam"
15
+ />
16
+ <meta property="og:type" content="website" />
17
+ <meta name="twitter:card" content="summary" />
18
+ <link
19
+ rel="preload"
20
+ href="/fonts/InterVariable.woff2"
21
+ as="font"
22
+ type="font/woff2"
23
+ crossorigin
24
+ />
6
25
  <title>__PROJECT_NAME__</title>
7
26
  </head>
8
27
  <body>
@@ -0,0 +1,17 @@
1
+ {
2
+ "ci": {
3
+ "collect": {
4
+ "startServerCommand": "npm run preview",
5
+ "startServerReadyPattern": "Local",
6
+ "url": ["http://localhost:4173/"],
7
+ "numberOfRuns": 3
8
+ },
9
+ "assert": {
10
+ "assertions": {
11
+ "categories:accessibility": ["error", { "minScore": 0.95 }],
12
+ "categories:seo": ["error", { "minScore": 0.95 }],
13
+ "categories:performance": ["error", { "minScore": 0.85 }]
14
+ }
15
+ }
16
+ }
17
+ }
package/template/npmrc ADDED
@@ -0,0 +1,2 @@
1
+ save-exact=true
2
+
@@ -19,32 +19,32 @@
19
19
  "test:e2e:report": "cd e2e && allure generate ./allure-results --clean && allure open"
20
20
  },
21
21
  "dependencies": {
22
- "react": "19.2.0",
23
- "react-dom": "19.2.0",
24
- "react-icons": "5.5.0",
25
- "wouter": "3.8.0"
22
+ "react": "19.2.7",
23
+ "react-dom": "19.2.7",
24
+ "react-icons": "5.7.0",
25
+ "wouter": "3.10.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@eslint/js": "9.39.1",
29
- "@tailwindcss/vite": "4.1.17",
30
- "@types/react": "19.2.7",
28
+ "@eslint/js": "9.39.4",
29
+ "@tailwindcss/vite": "4.3.2",
30
+ "@types/react": "19.2.17",
31
31
  "@types/react-dom": "19.2.3",
32
- "@vitejs/plugin-react": "5.1.1",
33
- "eslint": "9.39.1",
32
+ "@vitejs/plugin-react": "5.2.0",
33
+ "eslint": "9.39.4",
34
34
  "eslint-config-prettier": "10.1.8",
35
- "eslint-plugin-de-morgan": "2.0.0",
35
+ "eslint-plugin-de-morgan": "2.1.2",
36
36
  "eslint-plugin-import": "2.32.0",
37
37
  "eslint-plugin-jsx-a11y": "6.10.2",
38
- "eslint-plugin-promise": "7.2.1",
39
- "eslint-plugin-react-hooks": "7.0.1",
40
- "eslint-plugin-react-refresh": "0.4.24",
38
+ "eslint-plugin-promise": "7.3.0",
39
+ "eslint-plugin-react-hooks": "7.1.1",
40
+ "eslint-plugin-react-refresh": "0.5.3",
41
41
  "eslint-plugin-unicorn": "62.0.0",
42
- "prettier": "3.6.2",
42
+ "prettier": "3.9.5",
43
43
  "prettier-plugin-organize-imports": "4.3.0",
44
- "prettier-plugin-tailwindcss": "0.7.1",
45
- "tailwindcss": "4.1.17",
44
+ "prettier-plugin-tailwindcss": "0.8.0",
45
+ "tailwindcss": "4.3.2",
46
46
  "typescript": "5.9.3",
47
- "typescript-eslint": "8.48.0",
48
- "vite": "7.2.4"
47
+ "typescript-eslint": "8.63.0",
48
+ "vite": "7.3.6"
49
49
  }
50
50
  }
@@ -0,0 +1,5 @@
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ # Add your sitemap URL once deployed:
5
+ # Sitemap: https://example.com/sitemap.xml
@@ -1,17 +1,51 @@
1
- import { Redirect, Route, Switch } from "wouter";
2
- import About from "./pages/About";
3
- import Home from "./pages/Home";
4
- import NotFound from "./pages/NotFound";
1
+ import { lazy, Suspense, useDeferredValue, useEffect } from "react";
2
+ import { Redirect, Route, Switch, useLocation } from "wouter";
3
+ import { preloadAllRoutesWhenIdle, routeImports } from "./routes";
5
4
 
6
- const App = () => (
7
- <Switch>
8
- <Route path="/" component={Home} />
9
- <Route path="/about" component={About} />
10
- <Route path="/home">
11
- <Redirect to="/" />
12
- </Route>
13
- <Route component={NotFound} />
14
- </Switch>
15
- );
5
+ // Pages are code-split: each chunk downloads on demand. The import thunks
6
+ // live in src/routes.ts so the preload helpers can warm the same chunks
7
+ // ahead of navigation (routes.ts documents how it works and how to remove).
8
+ const Home = lazy(routeImports["/"]);
9
+ const About = lazy(routeImports["/about"]);
10
+ const NotFound = lazy(routeImports["/404"]);
11
+
12
+ const App = () => {
13
+ const [location] = useLocation();
14
+ // Routing against a deferred location means React keeps the previous page
15
+ // painted while the next page's chunk loads and initializes, instead of
16
+ // flashing the blank Suspense fallback. Preloading makes navigation fast;
17
+ // this makes it smooth. To remove: pass nothing to <Switch> below and
18
+ // delete these two hook calls.
19
+ const deferredLocation = useDeferredValue(location);
20
+
21
+ // Idle backstop — quietly fetch all remaining page chunks once the page
22
+ // has fully loaded. Delete this effect to remove (see src/routes.ts).
23
+ useEffect(() => {
24
+ preloadAllRoutesWhenIdle();
25
+ }, []);
26
+
27
+ return (
28
+ <>
29
+ <a
30
+ href="#main-content"
31
+ className="bg-brand-primary text-brand-white sr-only z-50 rounded-lg px-4 py-2 focus:not-sr-only focus:fixed focus:top-4 focus:left-4"
32
+ >
33
+ Skip to main content
34
+ </a>
35
+ <main id="main-content">
36
+ <Suspense fallback={null}>
37
+ <Switch location={deferredLocation}>
38
+ <Route path="/" component={Home} />
39
+ <Route path="/about" component={About} />
40
+ <Route path="/home">
41
+ <Redirect to="/" />
42
+ </Route>
43
+ <Route component={NotFound} />
44
+ </Switch>
45
+ </Suspense>
46
+ </main>
47
+ </>
48
+ );
49
+ };
16
50
 
17
51
  export default App;