Вопрос: Как типизировать в ts?

Лучший ответ: Вопрос конечно в духе "я его того, а он мне нихрна, чаво это он". Где конкретика блин? Что именно за ошибка? Любая ошибка TS - это "ошибка типов", он для этого и существует. Что у тебя в route?

Но окей, предположим, что route у тебя - это текущий роут vue-рутера, тогда ошибка у тебя скорее всего выглядит как-то так:
TS7053: Element implicitly has an 'any' type because expression of type 'RouteRecordName' can't be used to index type '{ first: string; second: string; }'.   No index signature with a parameter of type 'string' was found on type '{ first: string; second: string; }'.
Собственно прочитав текст ошибки можно уже догадаться, в чём проблема: у (неявно выведенного за отсутствием явного объявления) типа объекта pages есть ключи типа 'first' | 'second' | ..., но нет index signature типа string, т.е. не указано, что ключом может быть любая строка, а не только конкретные'first' | 'second' | .... 
route.name же после проверки на пустоту имеет тип string | symbol. Ты не можешь у объекта с чётко ограниченным набором ключей брать значение по произвольному строковому/символьному.

Прямое решение в лоб: 
Задать тип pages позволяющий рандомные ключи, например const pages: Record<PropertyKey, string> = {
  first: 'Первая',
  second: 'Вторая',
  ...
}
Всё сразу заработает, но это не спасёт тебя от ошибок(например опечаток).


Не менее прямой вариант(но с другой стороны):
Кастануть нужный тип руками: ... = pages[route.name as keyof typeof pages] 
Ведь мы уверены, что name в route всегда будет одним из ключей pages. Уверены же?..

Энтерпрайз решение(ничем не лучше предыдущих, зато выглядит солидно):
Твой файл routes.ts: 
export enum ERoutes {
  FIRST = 'first',
  SECOND = 'second'
}

const routes = [
  {
     name: ERoutes.FIRST,
     ...
  },
  {
     name: ERoutes.SECOND,
     ...
  },
  ...
]
В коде:const pages: Record<ERoutes, string> = {
  [ERoutes.FIRST]: 'Первая',
  [ERoutes.SECOND]: 'Вторая',
  ...
}

... = pages[route.name as ERoutes] 

Надмозговое решение("как батька"):
Твой файл routes.ts: const routes = [
  {
     name: 'first',
     ...
  },
  {
     name: 'second',
     ...
  },
  ...
] as const satisfies ReadonlyArray<ReadonlyRouteRecordRaw>;

type ReadonlyRouteRecordRaw = Omit<RouteRecordRaw, 'children'> & {
  children?: ReadonlyArray<ReadonlyRouteRecordRaw>;
};

type ExtractNames<Route> = Route extends { name: infer Name } ? Name : never;
type FlattenChildren<Route> = Route extends { children: ReadonlyArray<infer Children> }
  ? FlattenChildren<Children> | Route
  : Route;

// с помощью магии ts вытаскиваем в тип RouteNames все заданные у нас имена маршрутов
export type RouteNames = ExtractNames<FlattenChildren<typeof routes[number]>>;

// с помощью магии же прокидывем их прямо в декларацию vue-router
declare module 'vue-router' {
  export interface RouteLocationNormalizedLoaded {
    name: RouteNames | null | undefined;
  }
}
satisfiessatisfies - новая фича ts 4.9, в предыдущих версиях того же можно добиться сделав обёртку вида:
const narrowRoutesTypeWrapper = <T extends ReadonlyArray<ReadonlyRouteRecordRaw>>(routes: T) => routes;
const routes = narrowRoutesTypeWrapper([ ... ] as const);
И твой код заработает вообще без изменений (если в pages есть все нужные ключи).) 
Однако для удобства можно написать так:
const pages: Record<RouteNames, string> = {
  first: 'Первая',
  second: 'Вторая',
  ...
}

Худший ответ: Проблема в том, что TypeScript не знает тип route.name