Loading...
Loading...
Build and review Remix 3 applications using the `remix` npm package and subpath imports. Use when working on Remix app structure, routes, controllers, middleware, validation, data access, auth, sessions, file uploads, server setup, UI components, hydration, navigation, or tests.
npx skill4agent add remix-run/remix remixRequestResponseURLFormDataremixremixapp/routes.tshref()Responsecontext.set(Key, value)remix/uihandlehandle.props| Task involves... | Start with |
|---|---|
| Defining URLs, writing controllers and actions, returning responses | |
| Composing the request lifecycle, ordering middleware, bridging to a server | |
| Compiling and serving browser modules, asset URL namespaces, preloads | |
| Parsing input, validating with schemas, defining tables, querying, migrations | |
| Per-browser state, login flows, route protection, identity | |
Component setup, state, lifecycle, updates, | |
| Event handlers, styles, refs, click/key behavior, simple animations | |
| |
| Router tests, component tests, test isolation | |
| Spring physics, tweens, layout transitions | |
| Authoring custom reusable mixins | |
app/routes.tsResponserouter.fetch(...)clientEntry(...)AppContextRequestFormDataclientEntry(...)run(...)app/db/public/test/tmp/app/assets/controllers/data/middleware/ui/utils/routes.tsrouter.tsapp/ui/app/middleware/app/data/app/utils/app/controllers/app/controllers/home.tsxcontroller.tsxapp/controllers/account/controller.tsxapp/controllers/auth/login/controller.tsxapp/controllers/contact/page.tsxapp/ui/app/lib/app/components/app/ui/app/controllers/app/utils/remix/<subpath>import { ... } from 'remix'app/routes.tsroutes.<name>.href(...)ResponseResponseclientEntry(...)remix/data-schemaremix/data-schema/form-dataparseSafeAppContextget(Database)get(Session)get(Auth)getContext()asyncContext()handle.propshandle.update()queueTask(...)mix={mixin(...)}mix={[...]}clientEntry(...)httpOnlysameSitesecurerequireAuth()remix/html-templaterouter.fetch(new Request(...))Responseroutes.<name>.href(...)createMemorySessionStorage()createRoot(...)root.flush()remixclientEntry(...)clientEntry(...)getContext()asyncContext()FormDataResponseErrorcreateCookieremix/session<Frame>utils.tshelpers.tscommon.tsremix/fetch-routercreateRouterremix/fetch-router/routesroutegetpostputdelformresourcesapp/routes.tsremix/node-fetch-serverhttpcreateRequestListenerserver.tsremix/assetscreateAssetServertargetsourceMapssourceMapSourcePathsminifyremix/headersAcceptCookieCacheControlVaryremix/response/redirectredirect(href, status?)remix/response/htmlcreateHtmlResponseResponseremix/uiremix/response/compresscompressResponsecompression()remix/response/fileContent-Disposition: attachmentremix/route-patternremix/fetch-proxyxForwardedHeadersremix/data-schemaparseparseSafe.transform(...)remix/data-schema/checksemailminLengthmaxLengthremix/data-schema/coerceremix/data-schema/form-dataf.objectf.fieldFormDataremix/data-tableDatabasetablecolumncreateDatabaseremix/data-table-sqliteremix/data-table-postgresremix/data-table-mysqlcreateDatabaseprepareexecremix/data-table/migrationscreateMigrationcreateMigrationRunnerremix/data-table/migrations/nodeloadMigrationsremix/data-table/operatorsinList(...)whereremix/sessionSessiongetsetflashunsetregenerateIdremix/session-middlewaresession(cookie, storage)remix/session/fs-storageremix/session/memory-storageremix/session/cookie-storagefs-storagememory-storagecookie-storageremix/session-storage-redisremix/session-storage-memcacheremix/cookiecreateCookieremix/sessionremix/authrefreshExternalAuth(...)remix/auth-middlewareauth({ schemes })requireAuthAuthremix/uiclientEntryrun<Frame>createRootremix/ui/serverrenderToStreamrenderToStringrender(...)remix/ui/animationanimateEntranceanimateExitanimateLayoutspringtweeneasingsremix/ui/<primitive>remix/ui/accordionremix/ui/buttonremix/ui/selectremix/ui/testrenderremix/ui/jsx-runtimetsconfig.jsonremix/html-templateremix/file-storageFileremix/file-storage/fsremix/file-storage/memoryremix/file-storage-s3remix/static-middlewarestaticFiles(dir)public/remix/form-data-middlewareformData()FormDataget(FormData)await request.formData()remix/form-data-parserparseFormDataFileUploadremix/multipart-parserremix/multipart-parser/nodeMultipartPart.headerspart.headers['content-type']remix/compression-middlewarecompression()remix/logger-middlewarelogger()colorsremix/method-override-middlewaremethodOverride()PUTPATCHDELETEremix/async-context-middlewareasyncContext()getContext()remix/cors-middlewarecors(opts?)remix/csrf-middlewarecsrf(opts?)remix/cop-middlewareremix/testdescribeitremix/test/clirunRemixTestremix/cliremixremix testremix routesremix doctorremix/assertnode:assertremix/terminalimport { form, get, post, resources, route } from 'remix/fetch-router/routes'
export const routes = route({
home: '/',
contact: form('contact'),
books: {
index: '/books',
show: '/books/:slug',
},
auth: route('auth', {
login: form('login'),
logout: post('logout'),
}),
admin: route('admin', {
index: get('/'),
books: resources('books', { param: 'bookId' }),
}),
})import type { Controller } from 'remix/fetch-router'
import type { AppContext } from '../router.ts'
import { routes } from '../routes.ts'
export default {
actions: {
async index({ get }) {
let db = get(Database)
let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] })
return render(<BooksIndexPage allBooks={allBooks} />)
},
async show({ get, params }) {
let db = get(Database)
let book = await db.findOne(books, { where: { slug: params.slug } })
if (!book) return new Response('Not Found', { status: 404 })
return render(<BookShowPage book={book} />)
},
},
} satisfies Controller<typeof routes.books, AppContext>import {
createRouter,
type AnyParams,
type MiddlewareContext,
type WithParams,
} from 'remix/fetch-router'
export type RootMiddleware = [
ReturnType<typeof formData>,
ReturnType<typeof session>,
ReturnType<typeof loadDatabase>,
ReturnType<typeof loadAuth>,
]
export type AppContext<params extends AnyParams = AnyParams> = WithParams<
MiddlewareContext<RootMiddleware>,
params
>
let middleware = []
if (process.env.NODE_ENV === 'development') {
middleware.push(logger())
}
middleware.push(compression())
middleware.push(staticFiles('./public'))
middleware.push(formData())
middleware.push(methodOverride())
middleware.push(session(cookie, storage))
middleware.push(asyncContext())
middleware.push(loadDatabase())
middleware.push(loadAuth())
let router = createRouter({ middleware })import { redirect } from 'remix/response/redirect'
import * as s from 'remix/data-schema'
import * as f from 'remix/data-schema/form-data'
import { Session } from 'remix/session'
import { Database } from 'remix/data-table'
let bookSchema = f.object({
slug: f.field(s.string()),
title: f.field(s.string()),
})
export default {
actions: {
async create({ get }) {
let parsed = s.parseSafe(bookSchema, get(FormData))
if (!parsed.success) {
return render(<NewBookPage errors={parsed.issues} />, { status: 400 })
}
let db = get(Database)
let book = await db.create(books, parsed.value)
let session = get(Session)
session.flash('message', `Added ${book.title}.`)
return redirect(routes.books.show.href({ slug: book.slug }))
},
},
} satisfies Controller<typeof routes.books, AppContext>ResponseclientEntry(...)import { on, type Handle } from 'remix/ui'
function Counter(handle: Handle<{ initialCount?: number; label: string }>) {
let count = handle.props.initialCount ?? 0
return () => (
<button
mix={on('click', () => {
count++
handle.update()
})}
>
{handle.props.label}: {count}
</button>
)
}clientEntry(...)run(...)