1.5K Views
May 21, 25
スライド概要
未来大×企業エンジニア 大LT2025 で発表したLT資料です
完全サーバーレスで作る オレオレスライド配信システム🔥 uiro (@famisics) はこだて未来大×企業エンジニア 大LT2025
はじめまして! uiroです!
はじめまして! uiroです! Webフロント芸人+αです 冷やし自宅鯖 ゆるふわバックエンド
はじめまして! uiroです! Webフロント芸人+αです https://uiro.dev を見てください!
はじめまして! uiroです!
はじめまして! uiroです! 最近函館がアツいです
はじめまして! uiroです! 最近函館がアツいです 家が25度を超えはじめました オープンスペースもアツい
はじめまして! アツい技術といえば?🔥🔥🔥
はじめまして! アツい技術といえば?🔥🔥🔥 Hono.js🔥
Hono.jsとは? 1. Node.js や Bun, Cloudflare Workers など 様々なランタイムで同じコードが動く 2. Web標準で軽量 3. 超軽量で爆速🔥 4. サーバーレスな環境との相性が良い
Hono.jsとは? 1. Node.js や Bun, Cloudflare Workers など 様々なランタイムで同じコードが動く 2. Web標準で軽量 3. 超軽量で爆速🔥 4. サーバーレスな環境との相性が良い アツいですね〜🔥🔥🔥
ところで…
スライドって、使いづらくないですか?
スライドって、使いづらくないですか? 1. 共有がむずい(フォント、OS) 2. 特定のサービスに縛られる(保存場所も) 3. 便利そうなやつは、使っている人が いないので、共有がむずい(再放送) 4. Git管理は、ちょっと人類には早すぎる →すでにやっている人はやばい(褒め言葉)
🤔みんな使っていて、 共有が楽で、GUIに対応している…
ブラウザ!
ブラウザで動作するスライドを作りたい! ・どんなブラウザでもスライドが崩れない ・スライドを作ってすぐ配信できる ・既存のCMSに乗せるのは大変そう…
使用する構成 ・Next.js ・Hono.js ・データベース:Neon (サーバーレスなPostgreSQL) ・ORM:Drizzle ・オブジェクトストレージ:Vercel Blob
お気づきかもしれませんが、このスライドは オレオレスライドシステムから配信されています!
スライドを共有 slidex.uiro.dev/s/cQf7yN
famisics / ui-slidex Save Visit uiro.dev 完全サーバーレスで作る オレオレスライド配信システム🔥 uiro (@famisics) はこだて未来大×企業エンジニア 大LT2025 エディター text 完全サーバーレスで作る<br>オレオレスライド配信システム🔥 text uiro (@famisics) text はこだて未来大×企業エンジニア 大LT2025 X 4 Y 9 Width 100 Height 15 Content 完全サーバーレスで作る オレオレスライド配信システム🔥 Font Size 6 Line Height auto Weight 800 Align left ページ1 ページ2 ページ3 ページ4 ページ5 ページ6 ページ7 1/20
完全サーバーレスで作る オレオレスライド配信システム🔥 uiro (@famisics) はこだて未来大×企業エンジニア 大LT2025 ページ1 ページ2 ページ3 ページ4 ページ5 スライドを共有 slidex.uiro.dev/s/cQf7yN ページ8 1/20
Hono RPCのすごいところ 制作途中なので、 実装を間違えている可能性が一定以上あります
const app = new Hono()
.get('/', async c => {
const res: SlideModel[] = await db.select().from(slides)
if (res.length === 0) {
return c.notFound()
}
return c.json(res)
})
import slides from './slides'
export const app = new Hono()
.basePath('/api')
export const route = app
.get('/', async c => {
// GET /api/
return c.json({
message: 'Hello from the API!',
})
})
.route('/blogs', blogs)
.route('/slides', slides)
.route('/images', images)
import { handle } from 'hono/vercel'
import { app, route } from '@/api'
export const runtime = 'edge'
export const GET = handle(app)
export const POST = handle(app)
export const PUT = handle(app)
export const DELETE = handle(app)
export type AppType = typeof route
import { hc } from 'hono/client'
import type { AppType } from '@/app/api/[[...route]]/route'
export const client = hc<AppType>(getBaseUrl(), {
headers: {
...(typeof window !== 'undefined' && {
Authorization: getAuthHeader(),
}),
},
})
import { client } from '@/lib/client'
const slideRes = await client.api.slides.$get()
if (!slideRes.ok) {
throw new Error(`Failed to fetch slides: ${slideRes.statusText}`)
}
const slideData = (await slideRes.json()) as SlideModel[]
<div className="mt-4">
{slideData.length !== 0 ? slideData.map(slide => (
<Link href={ROUTES.SLIDES.GETONE.SLUG(slide.slug)} key={slide.id} className="mb-4 bl
<h2 className="text-xl font-semibold">{slide.title}</h2>
<p>{slide.description}</p>
<p className="mt-2 text-sm text-gray-500">Slug: {slide.slug}</p>
<p className="mt-2 text-sm text-gray-500">Code: {slide.code}</p>
{slide.createdAt && <p className="mt-2 text-sm text-gray-500">Created at: {new D
</Link>
)) : <p className="mt-2 text-gray-600">No slides available</p>}
</div>
export const slides = pgTable('slides', {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
code: varchar({ length: 6 }).notNull().unique(),
slug: varchar({ length: 255 }).notNull().unique(),
title: varchar({ length: 255 }).notNull(),
description: varchar({ length: 255 }),
image: varchar({ length: 255 }),
data: json(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
userId: integer('user_id')
.notNull()
.references(() => users.id),
})
export const userRelations = relations(users, ({ many }) => ({ slides: many(slides), blogs: many(blogs), })) export const slideRelations = relations(slides, ({ one }) => ({ user: one(users, { fields: [slides.userId], references: [users.id], }), }))
Tables neondb public Search... blogs slides user_id ASC slug ASC users Table name slides COLUMNS Add column id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY code VARCHAR(6) UNIQUE NOT NULL slug VARCHAR(255) UNIQUE NOT NULL title VARCHAR(255) NOT NULL description VARCHAR(255) image VARCHAR(255) created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() user_id INTEGER NOT NULL data JSON CONSTRAINTS Add constraint
import { drizzle } from 'drizzle-orm/neon-http'
const db = drizzle(process.env.DATABASE_URL!)
export { db }
export * from './schema'
.get('/', async c => {
const res: SlideModel[] = await db.select().from(slides)
if (res.length === 0) {
return c.notFound()
}
return c.json(res)
})
感想 ・Hono → RPCが神すぎ ・Next.js + Hono + Vercel の相性が良すぎる ・Drizzle+Neon → サクッとポスグレを使える、壊せ
今後の展望 ・技術的には、どんなレイアウトでも実装できる →テンプレートを増やしたい ・図形やらテーブルやらにも対応します ・スライド編集を完全にGUIからのみ行えるようにする
今後の展望 ・内部的に、スライドはユーザーに紐づいているので、 登録すれば誰でも使えるようになる →夢が広がる
今後の展望 ・内部的に、スライドはユーザーに紐づいているので、 登録すれば誰でも使えるようになる →夢が広がる ・致命傷:認証周りが追いついておらず、極めて脆弱
今後の展望 ・内部的に、スライドはユーザーに紐づいているので、 登録すれば誰でも使えるようになる →夢が広がる ・致命傷:認証周りが追いついておらず、極めて脆弱 →なんとかします
今後の野望 ・Webフロント芸人→*芸人に進化したい ・Hono, Go, Ruby(Rails) をもっと使いたい… Rails の ActiveRecord、気になる
今後の野望 ・Webフロント芸人→*芸人に進化したい ・Hono, Go, Ruby(Rails) をもっと使いたい… Rails の ActiveRecord、気になる ・突然バックエンドが切り替わっているかも
完全サーバーレスで作る オレオレスライド配信システム🔥 uiro (@famisics) ご清聴ありがとうございました