Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SSRで動的に
OGP画像を生成したい!
〜Cloudflare Workersから@vercel/og移行編〜

jiko21
June 14, 2023
78

SSRで動的に
OGP画像を生成したい!
〜Cloudflare Workersから@vercel/og移行編〜

KFUG Web Creators Meetup #2の資料です

jiko21

June 14, 2023
Tweet

Transcript

  1. SSRͰಈతʹ

    OGPը૾Λੜ੒͍ͨ͠ʂ

    ʙCloud
    fl
    are Workers͔Β@vercel/ogҠߦฤʙ
    KFUG MEETUP
    @jiko21

    View full-size slide

  2. About jiko21…
    Name: Daiki Kojima (jiko21)


    Multistack Engineer


    Love: Guitar, TypeScript
    @jiko21
    @jiko_21

    View full-size slide

  3. ͜ΜͳτΠΞϓϦ࡞ͬͯ·͢
    • Χϥʔίʔυͱ͔urlͰ؆୯ʹڞ༗Ͱ͖Δπʔϧ


    • OGը૾Ͱ։͔ͳͯ͘΋ͦͷ৔Ͱ৭͚ͩݟΕΔˣ

    View full-size slide

  4. OGPΘ͔ΒΜͷͰChatGPTʹฉ͍ͯΈͨ

    View full-size slide

  5. OGPΘ͔ΒΜͷͰChatGPTʹฉ͍ͯΈͨ

    View full-size slide


  6. ͬͯॻ͍ͨΒऴΘΓʂ

    View full-size slide

  7. ಈతʹOGPը૾Λੜ੒ͯ͠షΓ͍ͨΜ΍ʂ
    • ͍ͭ΋ಉ͡ը૾͡Όͳͯ͘


    • ϖʔδ


    • query parameter


    ɹͰOGPΛม͍͑ͨ

    View full-size slide

  8. 2020~2021ࠒͩͱ…
    • Cloud Functionsͱ͔Lambdaͱ͔


    • ϝϦοτ


    • ࣮૷͸(ଟ෼)ָͦ͏


    • σϝϦοτ


    • ॏͦ͏ (ͦΒͦ͏Α)

    View full-size slide

  9. ଎͍ͨ͘͠!!

    View full-size slide

  10. ଎͘͢Δʹ͸
    • ίʔϧυελʔτͳ͍ํ͕خ͍͠!


    • ίʔϧυελʔτͪ͠Ό͏ͱͲ͏ͯ͠΋…


    • ͳΔ͚ͩϢʔβʔ͔Β͍ۙͱ͜ΖͰ


    • ౦ژϦʔδϣϯͱ͔͡Όͳͯ͘΋ͬͱۙ͘ʹ!

    બ͹Εͨͷ͸&EHF8PSLFSͰͨ͠

    View full-size slide

  11. Edge Worker΋Θ͔ΒΜͷͰChatGPTʹฉ͍ͯΈͨ

    View full-size slide

  12. ଎͘͢Δʹ͸(࠶ܝ)
    • ίʔϧυελʔτͳ͍ํ͕خ͍͠!


    • ίʔϧυελʔτͪ͠Ό͏ͱͲ͏ͯ͠΋…

    㱺 (Cloud
    fl
    are workersͩͱ)ϗοτελʔτ


    • ͳΔ͚ͩϢʔβʔ͔Β͍ۙͱ͜ΖͰ


    • ౦ژϦʔδϣϯͱ͔͡Όͳͯ͘΋ͬͱۙ͘ʹ!

    㱺·͞ʹͦ͏(CDNͳͷͰ)

    View full-size slide

  13. cloud
    fl
    are
    workers

    View full-size slide

  14. Cloud
    fl
    are WorkersͬͯԿ?
    • CDN࡞ͬͯΔCloud
    fl
    are͕΍ͬͯΔEdge WorkerͷαʔϏεج൫


    • Nodeͱ͔Wasm͕Edge(CDN)্Ͱಈ͘ʂ


    • ҎԼ஫ҙ


    • NodeͷAPIͱ͔ݺͼग़ͤΔΘ͚͡Όͳ͍


    • ϑΝΠϧαΠζʹ্ݶ(ѹॖޙͰධՁ͞ΕΔ͚Ͳ)͋Γ

    View full-size slide

  15. Rust(Web AssemblyͰॻ͍ͨ
    fn get_params(text: String) -> Vec<[u8; 3]> {


    let mut rslt: Vec<[u8; 3]> = vec![];


    let re = Regex::new(r"color=%23([0-9A-Fa-f]
    {6})").unwrap();


    for mat in re.captures_iter(&text) {


    let rgb_string = mat.get(1).map_or("", |m|
    m.as_str()).trim().to_string();


    let mut decoded = [0; 3];


    hex::decode_to_slice(rgb_string, &mut
    decoded).expect("Decoding failed");


    rslt.push(decoded)


    }


    rslt


    }


    fn gen_img(colors: &Vec<[u8; 3]>) -> Vec {


    let mut img: RgbImage = ImageBuffer::new(1200, 630);


    let color_size = colors.len() as u32;


    let each_size = 1200 / color_size;


    for (x, _y, pixel) in img.enumerate_pixels_mut() {


    let cursor = (x / each_size) as usize;


    *pixel =
    image::Rgb(*colors.get(cursor).unwrap());


    }


    let mut img_bytes: Vec = Vec::new();


    img.write_to(&mut Cursor::new(&mut img_bytes),
    image::ImageOutputFormat::Png).unwrap();


    img_bytes


    }
    async fn hundle_ogp(colors: &Vec<[u8; 3]>) -> Result {


    let img_bytes = gen_img(colors);


    let mut resp = Response::from_bytes(img_bytes)?;


    resp.headers_mut().set("content-type", "image/png")?;


    Ok(resp)


    }


    #[event(fetch)]


    pub async fn main(req: Request, env: Env, _ctx: worker::Context) ->
    Result {


    utils::set_panic_hook();


    let router = Router::new();


    router


    .get_async("/", |req, _| async move {


    let url = req.url().unwrap();


    if let Some(query_params) = req.url()?.query() {


    let params = query_params;


    hundle_ogp(&get_params(params.to_string())).await


    } else {


    let colors: Vec<[u8; 3]> = vec![[0, 0, 255], [0, 255,
    0], [255, 255, 0], [239, 129, 15], [255, 0, 0]];


    hundle_ogp(&colors).await


    }


    })


    .run(req, env)


    .await


    }


    View full-size slide

  16. ࣮૷ͨ͠ࡶײ
    • Rustਏͬɺ


    • ͚Ͳ଎͍


    • ͚ͲDocument͸͔ͳΓἧͬͯΔ


    • σϓϩΠ΍ςϯϓϨʔτ΋ἧͬͯΔ

    View full-size slide

  17. ࣮૷ͯ͠

    ͠͹Βͯ͘͠…

    View full-size slide

  18. @vercel/ogͬͯԿ?
    • VercelͷEdge Worker্Ͱಈ͘ը૾ੜ੒ϥΠϒϥϦ


    • ϝϦοτ


    • EdgeͰಈ͘ͷͰcoldstart΄΅ͳ͠


    • HTML+CSSͰಈతʹը૾͕ੜ੒Ͱ͖ΔͷͰهड़ָ͕ʂ


    • Ωϟογϡͱ͔΋Α͠ͳʹ΍ͬͯ͘ΕΔ

    View full-size slide

  19. @vercel/ogͰॻ͍ͨ
    export default function (req: NextRequest, res: NextResponse) {


    const paramColors = new URLSearchParams(req.nextUrl.search).getAll('color');


    const colors = paramColors.length > 0 ? paramColors : ['#0000FF', '#00FF00', '#FFFF00', '#F0810F', '#FF0000']


    return new ImageResponse(


    (




    style={{


    fontSize: 128,


    width: '100%',


    height: '100%',


    display: 'flex',


    textAlign: 'center',


    alignItems: 'center',


    justifyContent: 'center',


    }}


    >


    {colors.map((color) => (

    height: '100%',


    width: '100%',


    background: `${color}`,


    color: `${color}`,


    }


    } key={color}> ))}





    ),


    {


    width: 1200,


    height: 630,


    },


    );


    }


    $44ࡶʹॻ͍ͨΒ

    0(ੜ੒Ͱ͖Δͷ
    ͸Α͍

    View full-size slide

  20. ࣮૷ͨ͠ࡶײ
    • ָ


    • (VercelͰΞϓϦಈ͔͍ͯͨ͠Β)Vercel͚ͩͰ׬݁͢Δͷخ͍͠


    • Vercel࠷ߴʂ

    View full-size slide

  21. ͩͩ͠…
    • ຊ౰ʹύϑΥʔϚϯε͍͍ͷ͔?


    • Ωϟογϡͷ༗ແ΋ͦ͏͕ͩॳճͷੜ੒ίετͱ͔


    • (ͳ͍͚Ͳ)େྔʹΞΫηεདྷͨͱ͖ͱ͔Ͳʔ͠Α

    View full-size slide

  22. ࣮ࡍʹݕূͯ͠ΈΔ

    View full-size slide

  23. Ծઆ
    • RustͰbinaryΛ௚઀ੜ੒ͯ͠Δ͔Βੜ੒ࣗମ͸଎͍͸ͣ


    • @vercel/ogͩͱΩϟογϡ͸͏·͘࠷దԽ͞Ε͍ͯΔ͔Β2ճ໨Ҏ߱
    ͷऔಘͱ͔͸଎͍͸ͣ

    View full-size slide

  24. ݁Ռ
    • ༧૝௨Γ(https://jiko21-tech-blog.monster/blog/2022-12-24/
    cloud
    fl
    are-to-vecel-og)


    • ॳճ͸cloud
    fl
    are workers͕͸΍͍


    • ͦΕҎ߱͸@vercel/og͕͸΍͍

    View full-size slide

  25. Cloud
    fl
    are Workers΋


    Cacheޮ͔ͤͨΒ͍͍ͷͰ͸ʁ

    View full-size slide

  26. ͦΕ͸ͦ͏ɺͳΜ͚ͩͲ…
    • ݱ࣌఺ͰRustଆ(Wasm)ʹ͸cacheػೳ͕ͳ͍


    • Durable ObjectsͳͲผ్ػೳͰؤுΔ͔͠ͳͦ͞͏


    • ແཧ΍ΓલஈʹjsͰॻ͍ͨworkerΛڬΜ͚ͩͲ…


    • ͔֬ͩΊͩͬͨ…

    View full-size slide

  27. ݁࿦
    • OGPը૾࡞Γ͍ͨͳΒNext.js+Vercelͷ૊Έ߹Θ͕ͤ࠷ڧ


    • ↑ແཧͳΒRustͰؤுΔͱ͍͏ͷ΋खͷ಺

    View full-size slide