Welcome to the EpicWeb.dev Workshop app!

This is the deployed version. Run locally for full experience.

Routing

Loading "Intro to Routing"

The web

One of the critical pieces of the web is the URL (which is short for Uniform Resource Locator ๐Ÿ“œ What is a URL). The URL is what allows us to navigate to different pages on the web. It allows users to bookmark and share those pages with one another. As a result, using the URL as the primary mechanism for storing state is not only common, but often necessary for an excellent user experience. Users of web apps often expect to be able to return to a specific page and see the same content they did before.
URLs can be broken into segments. On the "What is a URL" article, we find a helpful diagram:
an example url color coding different segments of the URL including, scheme, domain name, port, path to the file, parameters, and anchor
For more on these sections, check the article.
The domain name is something typically configured once for an entire site, so you don't typically end up working with that part of the URL much.
The anchor (the part following #) is used to link to specific elements on a page by their ID (so when the user opens their browser, it automatically scrolls to that element on the page). We don't often work with that part of the URL in day-to-day web development either.
What we spend most of our time working with is the pathname and parameters.
The pathname can be broken into what we call "segments" which are separated by /. For example, the pathname /about has one segment, and the pathname /about/team has two segments. The pathname segments can also be used to store state. For example, if we wanted to show a specific user's profile, we could use the pathname /users/123 where 123 is the user's ID. The user's ID segment is often referred to as a "route parameter".
๐Ÿฆ‰ As you can tell, there's a bit of an unfortunate term overload in web development. "Parameters" in the diagram above refers to the part of the URL after the ?. But we also just talked about "route parameters." And then of course there are "function parameters." All of these serve a similar purpose. To avoid confusion, we typically call "route parameters" simply "params" and the bit after the ? is often called "query params" or "search params."
To navigate around the web, we use <a> tags ("a" is short for "anchor", please don't ask why it's not called "link" ๐Ÿคทโ€โ™‚๏ธ). For example, if we wanted to link to the user's profile page, we could use the following:
<a href="/users/123">User Profile</a>
You can also navigate to other pages on the web using a <form> tag. For example, if we wanted to search for users, we could use the following:
<form action="/users/search" method="GET">
	<input type="text" name="q" />
	<button type="submit">Search</button>
</form>
This will take the value of the input and add it to the URL as a query param (?q=...). The method attribute tells the browser to use the GET HTTP method when submitting the form. We'll get into forms more in a later exercise.
You can also navigate using JavaScript, but generally you should use anchors and forms.

Routers

Most web applications use what is called a "router" to associate specific code with the URL segment. This allows us to both organize our code, and optimize it for loading performance (to ensure we only load the code that is necessary based on what our user needs on a specific route). Routers are also often used to help load the data that will be used on the page. The Router typically allows you to define certain URL segments as "params" which can be any value, and the Router will parse the URL (and those params), and execute the code that has been configured for that specific URL (passing along the params to the code). This is what allows GitHub to load a specific repository when you visit https://github.com/epicweb-dev/full-stack-foundations for example. Route params are often represented by a : in the URL, for example: https://github.com/:username/:repo. This tells the router that the username and repo segments are params, and that they can be any value.

In Remix

Remix has a built-in router that enables you to easily map URLs and route parameters to files in your application's app/routes directory. Thanks to the file system convention from Remix, we don't have to spend any time configuring the routes for our application. That said, it can be quite helpful to know what the generated routes are, so the Remix command line interface (CLI) has a routes command that will print out all of the routes in your application. To use it, open your terminal in a Remix project, and run:
npx remix routes
Here's an example of the output in a bigger application:
<Routes>
	<Route file="root.tsx">
		<Route path="*" file="routes/$.tsx" />
		<Route path="auth/:provider" file="routes/_auth+/auth.$provider.ts">
			<Route path="callback" file="routes/_auth+/auth.$provider.callback.ts" />
		</Route>
		<Route path="forgot-password" file="routes/_auth+/forgot-password.tsx" />
		<Route path="login" file="routes/_auth+/login.tsx" />
		<Route path="logout" file="routes/_auth+/logout.tsx" />
		<Route path="onboarding" file="routes/_auth+/onboarding.tsx" />
		<Route
			path="onboarding/:provider"
			file="routes/_auth+/onboarding_.$provider.tsx"
		/>
		<Route path="reset-password" file="routes/_auth+/reset-password.tsx" />
		<Route path="signup" file="routes/_auth+/signup.tsx" />
		<Route path="verify" file="routes/_auth+/verify.tsx" />
		<Route path="about" file="routes/_marketing+/about.tsx" />
		<Route index file="routes/_marketing+/index.tsx" />
		<Route path="privacy" file="routes/_marketing+/privacy.tsx" />
		<Route path="support" file="routes/_marketing+/support.tsx" />
		<Route path="tos" file="routes/_marketing+/tos.tsx" />
		<Route path="admin/cache" file="routes/admin+/cache.tsx" />
		<Route
			path="admin/cache/lru/:cacheKey"
			file="routes/admin+/cache_.lru.$cacheKey.ts"
		/>
		<Route path="admin/cache/sqlite" file="routes/admin+/cache_.sqlite.tsx">
			<Route path=":cacheKey" file="routes/admin+/cache_.sqlite.$cacheKey.ts" />
		</Route>
		<Route path="me" file="routes/me.tsx" />
		<Route
			path="resources/download-user-data"
			file="routes/resources+/download-user-data.tsx"
		/>
		<Route
			path="resources/healthcheck"
			file="routes/resources+/healthcheck.tsx"
		/>
		<Route
			path="resources/note-images/:imageId"
			file="routes/resources+/note-images.$imageId.tsx"
		/>
		<Route
			path="resources/user-images/:imageId"
			file="routes/resources+/user-images.$imageId.tsx"
		/>
		<Route path="settings/profile" file="routes/settings+/profile.tsx">
			<Route
				path="change-email"
				file="routes/settings+/profile.change-email.tsx"
			/>
			<Route
				path="connections"
				file="routes/settings+/profile.connections.tsx"
			/>
			<Route index file="routes/settings+/profile.index.tsx" />
			<Route path="password" file="routes/settings+/profile.password.tsx" />
			<Route
				path="password/create"
				file="routes/settings+/profile.password_.create.tsx"
			/>
			<Route path="photo" file="routes/settings+/profile.photo.tsx" />
			<Route path="two-factor" file="routes/settings+/profile.two-factor.tsx">
				<Route
					path="disable"
					file="routes/settings+/profile.two-factor.disable.tsx"
				/>
				<Route index file="routes/settings+/profile.two-factor.index.tsx" />
				<Route
					path="verify"
					file="routes/settings+/profile.two-factor.verify.tsx"
				/>
			</Route>
		</Route>
		<Route path="users/:username" file="routes/users+/$username.tsx" />
		<Route
			path="users/:username/notes"
			file="routes/users+/$username_+/notes.tsx"
		>
			<Route
				path=":noteId"
				file="routes/users+/$username_+/notes.$noteId.tsx"
			/>
			<Route
				path=":noteId/edit"
				file="routes/users+/$username_+/notes.$noteId_.edit.tsx"
			/>
			<Route index file="routes/users+/$username_+/notes.index.tsx" />
			<Route path="new" file="routes/users+/$username_+/notes.new.tsx" />
		</Route>
		<Route path="users/" index file="routes/users+/index.tsx" />
	</Route>
</Routes>
Because sometimes you need to reach outside the conventions (for example the file system can be a bit limited on the types of characters we can have in our URL segments), it is good to know that you can programmatically generate routes as well in your remix.config.js.
Remix's router supports what's called "nested routing." Often, the UI of an application is nested in a similar fashion to the URL. For example, let's take a URL like this: https://example.com/sales/invoices/:invoiceId. The "root" of the application (the part of the app that's on all pages) is considered the "parent route" of all the routes on the page. The /sales portion is a child of the root, the /invoices is a child of the /sales route, and the /:invoiceId is a child of the /invoices route. The UI could resemble something like this: A left navigation section (root), then a top navigation (sales), then a list (invoices), and finally a details portion of the page (invoice ID).
We'll be exploring how to create these relationships in the exercise.
To facilitate navigation, Remix provides a <Link> component that you can use to create links (<a>) to other pages in your application without triggering a full-page refresh (which is what clicking a regular <a> will do). This is a nice performance optimization and it also has better accessibility characteristics and gives a better user experience.
Remix also has a built-in <Form> component which improves upon the browser's handling of forms. We'll dive deeper into that in a future exercise.

The Route Convention

It's important for you to know that we're not using the built-in Remix file routing convention. If you go into this thinking we are then you will be surprised. Read this section.
Remix originally shipped with a route convention, but Remix v2 will be shipping a new one that enables better colocation of related files (which helps with long-term maintenance).
We will actually be using a different convention for our app that is based on the v2 convention but allows for even better colocation of related files. The library we're using is called remix-flat-routes. This has already been configured in our remix.config.js (as it uses the programmatic interface for defining routes).
We'll guide you along in creating route files that match this convention, but it wouldn't hurt to have the remix-flat-routes documentation open in another tab to reference.
At any time, remember if you get lost, you can run npx remix routes in the project directory to check the generated routes configuration based on your file system.